<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>JDHeyburn</title>
    <link>https://jdheyburn.co.uk/</link>
    <description>Recent content on JDHeyburn</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <lastBuildDate>Wed, 26 Mar 2025 00:00:00 +0000</lastBuildDate>
    
	<atom:link href="https://jdheyburn.co.uk/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>Reorganising Obsidian Journal Notes</title><enclosure url="https://jdheyburn.co.uk/images/jdheyburn_co_uk_card.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/blog/reorganising-obsidian-journal-notes/</link>
      <pubDate>Wed, 26 Mar 2025 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/reorganising-obsidian-journal-notes/</guid>-->
      <description>&lt;p&gt;I finished off my &lt;a href=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal/&#34;&gt;previous post on journalling&lt;/a&gt; in Obsidian with a couple of wish lists:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I do have some improvements I wish to make such as moving notes away from their respective flat directories. i.e.:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Daily notes move from &lt;code&gt;planner/daily&lt;/code&gt; to be grouped by year and month: &lt;code&gt;planner/daily/YYYY/MM&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Weekly notes move from &lt;code&gt;planner/weekly&lt;/code&gt; to &lt;code&gt;planner/weekly/YYYY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Monthly notes move from &lt;code&gt;planner/monthly&lt;/code&gt; to &lt;code&gt;planner/monthly/YYYY&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&amp;rsquo;ve been playing around with AI code generation, specifically the Claude 3.7 Sonnet model, and I wanted to see if it could produce scripts to help with this.&lt;/p&gt;
&lt;p&gt;Unfortunately I didn&amp;rsquo;t capture the exact prompts I used, but it was something along these lines:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There are markdown files in the date format &lt;code&gt;YYYY-MM-DD.md&lt;/code&gt; found in &lt;code&gt;planner/daily&lt;/code&gt;. Create a Python script that will group them in directories by their respective year and month. The Python script should include user confirmation before moving the files.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I repeated this for each note type, and finished with asking it to consolidate all the scripts into one for simplicity.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Consolidate all the scripts you&amp;rsquo;ve created into one.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can find all the scripts on the Github gist link below.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://gist.github.com/jdheyburn/f64effd0b606a68044d7d28960becc77&#34;&gt;https://gist.github.com/jdheyburn/f64effd0b606a68044d7d28960becc77&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I strongly recommend backing up your vault and pausing your sync tool of choice before executing any of the scripts.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;organised-daily-notes.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/reorganising-obsidian-journal-notes//blog/reorganising-obsidian-journal-notes/organised-daily-notes.png&#34;
    alt=&#34;Output of `ls -l` command, showing directories named after months and the daily notes underneath months&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;After the scripts run, notes are now grouped into tidier directories&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;group-new-files&#34;&gt;Group new files&lt;/h2&gt;
&lt;p&gt;Now that existing files had been moved, I had to change the settings for the Periodic Notes plugin to template new files at their correct location:&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;updated-periodic-notes-settings.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/reorganising-obsidian-journal-notes//blog/reorganising-obsidian-journal-notes/updated-periodic-notes-settings.png&#34;
    alt=&#34;Periodic Notes plugin in Obsidian, the format of each note has been updated as per below this image&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;I had to update the format of each periodic note as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Daily
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;YYYY/MM/YYYY-MM-DD&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;e.g.: &lt;code&gt;2025/03/2025-03-21&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Weekly
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;YYYY/YYYY-[W]ww&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;e.g.: &lt;code&gt;2025/2025-W12&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Monthly
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;YYYY/YYYY-MM-[M]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;e.g.: &lt;code&gt;2025/2025-03-M&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Quarterly
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;YYYY/YYYY-[Q]Q&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;e.g.: &lt;code&gt;2025/2025-Q1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These get created under their respective note folder, e.g. &lt;code&gt;planner/monthly&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With all but daily notes, I&amp;rsquo;m prepending &lt;code&gt;YYYY/&lt;/code&gt; to the filename. Periodic Notes will see this as a directory and template out the format. Daily is prepended by &lt;code&gt;YYYY/MM/&lt;/code&gt; to allow for grouping on months too.&lt;/p&gt;
&lt;h2 id=&#34;updated-explicit-daily-note-links&#34;&gt;Updated explicit daily note links&lt;/h2&gt;
&lt;p&gt;From my &lt;a href=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal/#daily&#34;&gt;previous post&lt;/a&gt; you would have seen daily notes link to adjacent days through an explicit link under &lt;code&gt;planner/daily&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# Monday 20th November 2023
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&amp;lt; [[planner/daily/2023-11-19|2023-11-19]] || [[planner/daily/2023-11-21|2023-11-21]] &amp;gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[[2023-W47]]  || [[2023-11-M]]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These links broke after the restructure because the notes are no longer directly underneath &lt;code&gt;planner/daily&lt;/code&gt;. I can&amp;rsquo;t remember the reason why I set it up this way; it might&amp;rsquo;ve been because I expected more daily notes in various locations. In either case I don&amp;rsquo;t have a need for it, so we can change these to implicit links. There are also implicit links to weekly and monthly notes, but these links will work fine as the files are still in the vault, Obsidian will be able to find them.&lt;/p&gt;
&lt;p&gt;I also asked Claude to write me a bash one-liner to rename these.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There are markdown files under &lt;code&gt;planner/daily&lt;/code&gt; that have text in the format &lt;code&gt;[[planner/daily/2023-11-19|2023-11-19]] || [[planner/daily/2023-11-21|2023-11-21]]&lt;/code&gt;. Write a bash command that converts them to &lt;code&gt;[[2023-11-19]] || [[2023-11-21]]&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I thought I&amp;rsquo;d have to be explicit with the date format, but it seemed to understand the context enough to imply it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find planner/daily -name &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;*.md&amp;#34;&lt;/span&gt; -type f -exec sed -i &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt; -E &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;s/\[\[planner\/daily\/([0-9]{4}-[0-9]{2}-[0-9]{2})\|([0-9]{4}-[0-9]{2}-[0-9]{2})\]\]/[[\1]]/g&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;{}&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;\;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This took just 2 seconds to run! I also made sure to backup my Obsidian vault before hand, in case it went a bit haywire.&lt;/p&gt;
&lt;p&gt;My daily note template also needed updating so that new notes follow this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# &amp;lt;% thisDayLong %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&amp;lt; [[&amp;lt;% previousDayFmt %&amp;gt;]] || [[&amp;lt;% nextDayFmt %&amp;gt;]] &amp;gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[[&amp;lt;% thisWeek %&amp;gt;]]  || [[&amp;lt;% thisMonth %&amp;gt;]]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, I enabled syncing and watched as all the files were updated.&lt;/p&gt;
&lt;p&gt;Thanks for reading!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Adding aria2 download manager to my NixOS homelab</title><enclosure url="https://jdheyburn.co.uk/images/jdheyburn_co_uk_card.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/blog/adding-aria2-download-manager/</link>
      <pubDate>Sun, 22 Sep 2024 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/adding-aria2-download-manager/</guid>-->
      <description>&lt;p&gt;In this blog post I talk about how I host aria2 on a NixOS server. If you&amp;rsquo;re coming here for the first time you can see &lt;a href=&#34;https://jdheyburn.co.uk/tags/nix/&#34;&gt;other posts related to nix&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;aria2-introduction&#34;&gt;aria2 introduction&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://aria2.github.io/&#34;&gt;aria2&lt;/a&gt; is command-line download tool that can support a wide variety of protocols - that can be ran as a server to allow remote invocation of downloads. Their home page describes it as:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;aria2 is a lightweight multi-protocol &amp;amp; multi-source command-line download utility. It supports HTTP/HTTPS, FTP, SFTP, BitTorrent and Metalink. aria2 can be manipulated via built-in JSON-RPC and XML-RPC interfaces.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Working in the command line is cool and definitely how I prefer doing things, however sometimes its nice to work with a UI for when you don&amp;rsquo;t have a terminal ready. Additionally you would need to remember the commands to be able to get aria2 to download the files.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/mayswind/AriaNg&#34;&gt;AriaNg&lt;/a&gt; is a front-end that enables a UI for aria2. It&amp;rsquo;s written in HTML and JavaScript so there is no complexity in getting it up and running. You tell a web server to host the files, and then configure AriaNg to connect to your aria2 instance!&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;aria2-landing-page.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/adding-aria2-download-manager//blog/adding-aria2-download-manager/aria2-landing-page.png&#34;
    alt=&#34;Screenshot of the aria-ng web interface, showing two files are being downloaded&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Also by fronting it with a service, any of the devices on my &lt;a href=&#34;https://tailscale.com/&#34;&gt;Tailscale VPN&lt;/a&gt; can access aria2 at a nice URL and download files in the background.&lt;/p&gt;
&lt;p&gt;The primary use case this helps me solve is downloading music I&amp;rsquo;ve bought from Bandcamp. Since I download both the flac and mp3 zips, after a bulk purchase I download a load of files at once. My internet speed at home isn&amp;rsquo;t great so this can take a while. Having a service where I can dump a list of files to download means I can set the download off, then come back to it another time.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;aria2-example-download.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/adding-aria2-download-manager//blog/adding-aria2-download-manager/aria2-example-download.png&#34;
    alt=&#34;Starting new downloads in aria-ng, showing a text box accepting multiple URLS for download&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;deploying-with-nixos&#34;&gt;Deploying with NixOS&lt;/h2&gt;
&lt;p&gt;Now that I&amp;rsquo;m all-in on NixOS for managing my services at home, let&amp;rsquo;s get together the config for deploying this set up.&lt;/p&gt;
&lt;p&gt;Firstly I&amp;rsquo;ll need to deploy aria2 as a headless daemon, which is available in &lt;a href=&#34;https://github.com/NixOS/nixpkgs/blob/2057814051972fa1453ddfb0d98badbea9b83c06/nixos/modules/services/networking/aria2.nix&#34;&gt;nixpkgs&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;aria2 = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    enable &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    rpcSecretFile &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; config&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;age&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;secrets&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aria2-password&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;path;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;users&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;users&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;jdheyburn&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;extraGroups = [ &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aria2&amp;#34;&lt;/span&gt; ];
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;rpcSecretFile&lt;/code&gt; expects a file path containing a string of the password which should be set for aria2. I manage my secrets with &lt;a href=&#34;https://github.com/ryantm/agenix&#34;&gt;agenix&lt;/a&gt;, but explaining how it works is beyond the scope of this blog post. If you&amp;rsquo;re trying it out I suggest just creating a temporary file containing the password to use.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m also adding my user to the &lt;code&gt;aria2&lt;/code&gt; group so that I have permissions to modify the files after they&amp;rsquo;ve been downloaded.&lt;/p&gt;
&lt;p&gt;After that I need to deploy the frontend, &lt;a href=&#34;https://github.com/mayswind/AriaNg&#34;&gt;AriaNg&lt;/a&gt;. This is available in &lt;a href=&#34;https://github.com/NixOS/nixpkgs/blob/2057814051972fa1453ddfb0d98badbea9b83c06/pkgs/servers/ariang/default.nix&#34;&gt;nixpkgs&lt;/a&gt; too, but only as a package and not as a service. Or rather, the package in nixpkgs only contains the HTML and JavaScript files for the frontend. We would need to roll our own web server to be able to host it.&lt;/p&gt;
&lt;p&gt;Now I&amp;rsquo;ve mentioned &lt;a href=&#34;https://caddyserver.com/&#34;&gt;Caddy&lt;/a&gt; on my blog &lt;a href=&#34;https://jdheyburn.co.uk/tags/caddy/&#34;&gt;before&lt;/a&gt; where I&amp;rsquo;m using it to reverse proxy my services at home. But here I can use it as a very simple web server - hosting files from a location!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;caddy&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;virtualHosts&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aria2.svc.joannet.casa&amp;#34;&lt;/span&gt; = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#6272a4&#34;&gt;# Routing config inspired from below:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#6272a4&#34;&gt;# https://github.com/linuxserver/reverse-proxy-confs/blob/20c5dbdcff92442262ed8907385e477935ea9336/aria2-with-webui.subdomain.conf.sample&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    extraConfig &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        file_server {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;            root &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;pkgs&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;ariang&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;/share/ariang
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        tls {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;            dns cloudflare {env.CLOUDFLARE_API_TOKEN}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        reverse_proxy /jsonrpc localhost:&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;toString&lt;/span&gt; config&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;services&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;aria2&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;settings&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;rpc-listen-port&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;    &amp;#39;&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A big thanks to &lt;a href=&#34;https://github.com/linuxserver/reverse-proxy-confs/blob/20c5dbdcff92442262ed8907385e477935ea9336/aria2-with-webui.subdomain.conf.sample&#34;&gt;LinuxServer.io&lt;/a&gt; through which the &lt;a href=&#34;https://github.com/linuxserver/reverse-proxy-confs/blob/20c5dbdcff92442262ed8907385e477935ea9336/aria2-with-webui.subdomain.conf.sample&#34;&gt;Caddy config&lt;/a&gt; was inspired from.&lt;/p&gt;
&lt;p&gt;In that config I&amp;rsquo;m using the &lt;a href=&#34;https://caddyserver.com/docs/caddyfile/directives/file_server&#34;&gt;file_server directive&lt;/a&gt; to specify that the root of the server should be served from the resulting directory that builds the AriaNg package as defined in &lt;a href=&#34;https://github.com/NixOS/nixpkgs/blob/2057814051972fa1453ddfb0d98badbea9b83c06/pkgs/servers/ariang/default.nix&#34;&gt;nixpkgs&lt;/a&gt;. You can see that &lt;a href=&#34;https://github.com/NixOS/nixpkgs/blob/2057814051972fa1453ddfb0d98badbea9b83c06/pkgs/servers/ariang/default.nix#L27&#34;&gt;this line in particular&lt;/a&gt; is what copies the compiled AriaNg package to the directory. Once this config is built, the directive looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-caddyfile&#34; data-lang=&#34;caddyfile&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;file_server&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;root&lt;/span&gt; /nix/store/i8ln23gyx6icjmv78lfg7pn9nnibrnzp-ariang-1.3.7/share/ariang
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After &lt;code&gt;file_server&lt;/code&gt; we have the &lt;code&gt;tls&lt;/code&gt; directive which instructs Caddy how to authenticate that the domain is owned by me - I&amp;rsquo;ve introduced this in a &lt;a href=&#34;https://jdheyburn.co.uk/blog/reverse-proxy-multiple-domains-using-caddy-2/#proving-domain-ownership&#34;&gt;previous blog post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Lastly we have a &lt;code&gt;reverse_proxy&lt;/code&gt; that will route any calls made to &lt;code&gt;https://aria2.svc.joannet.casa/jsonrpc&lt;/code&gt; to the port &lt;code&gt;6800&lt;/code&gt;, which is the port that the aria2 headless service is listening on for remote download invocations. You&amp;rsquo;ll see us config AriaNg to use this endpoint later.&lt;/p&gt;
&lt;p&gt;Once these configs are defined, you can have NixOS deploy out the changes:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;nixos-rebuild switch
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;configuring-ariang&#34;&gt;Configuring AriaNg&lt;/h2&gt;
&lt;p&gt;Remember when I said that AriaNg was just a bunch of HTML and JavaScript? Well it doesn&amp;rsquo;t hold any state on the server-side, that is all managed by &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage&#34;&gt;local storage in your browser&lt;/a&gt;. This includes configurations such as authentication, appearance, and even where AriaNg should look for aria2. So if we were to load up the frontend after the install, it won&amp;rsquo;t be able to connect.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;aria2-unconfigured.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/adding-aria2-download-manager//blog/adding-aria2-download-manager/aria2-unconfigured.png&#34;
    alt=&#34;Landing page for aria-ng immediately after setup. The aria2 status is labelled as disconnected&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Looking at dev tools we can see why it&amp;rsquo;s failing.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;aria2-fresh-install-failures.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/adding-aria2-download-manager//blog/adding-aria2-download-manager/aria2-fresh-install-failures.png&#34;
    alt=&#34;Dev tools console output for aria-ng after setup. There are failed requests going to the default aria2 URL&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;By default AriaNg is trying to connect to aria2 at &lt;code&gt;https://aria2.svc.joannet.casa:6800/jsonrpc&lt;/code&gt;, and failing. This is expected because we configured the reverse proxy for aria2 at &lt;code&gt;https://aria2.svc.joannet.casa:443/jsonrpc&lt;/code&gt; - with port &lt;code&gt;443&lt;/code&gt; being the port that HTTPS runs on (i.e. our Caddy server).&lt;/p&gt;
&lt;p&gt;AriaNg has an &lt;a href=&#34;https://ariang.mayswind.net/command-api.html&#34;&gt;API&lt;/a&gt; for configuring the aria2 RPC (remote procedure call). We take the endpoint that AriaNg is available at and configure it using path parameters in this format:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;/#!/settings/rpc/set/${protocol}/${rpcHost}/${rpcPort}/${rpcInterface}/${secret}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Populating with what we have configured previously, we end up with:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;https://aria2.svc.joannet.casa/#!/settings/rpc/set/https/aria2.svc.joannet.casa/443/jsonrpc/REPLACE_WITH_ENCODED_PASSWORD
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;REPLACE_WITH_ENCODED_PASSWORD&lt;/code&gt; is the password for aria2 that we set in the password file located at &lt;code&gt;rpcSecretFile&lt;/code&gt;, but base64 encoded. You can use the below command to find out what that would be.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# the -n flag will remove the newline from the echo command&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; -n &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;PASSWORD&amp;#34;&lt;/span&gt; | base64
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once we navigate to that URL in our browser, it authenticates us and creates that all important local storage to cache the settings for next time.&lt;/p&gt;
&lt;h2 id=&#34;creating-an-aria2-nixos-module&#34;&gt;Creating an aria2 NixOS module&lt;/h2&gt;
&lt;p&gt;NixOS is great for packaging all of this together into a module. In my repo I have an &lt;a href=&#34;https://github.com/jdheyburn/nixos-configs/tree/main/modules/aria2&#34;&gt;aria2 module&lt;/a&gt; which contains this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{ config&lt;span style=&#34;color:#ff79c6&#34;&gt;,&lt;/span&gt; pkgs&lt;span style=&#34;color:#ff79c6&#34;&gt;,&lt;/span&gt; lib&lt;span style=&#34;color:#ff79c6&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt; }:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt; lib;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;let&lt;/span&gt; cfg &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; config&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;modules&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;aria2;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  options&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;modules&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;aria2 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; { enable &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; mkEnableOption &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;enable aria2 with a web client for managing downloads&amp;#34;&lt;/span&gt;; };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  config &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; mkIf cfg&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;enable {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    services&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;caddy&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;virtualHosts&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aria2.svc.joannet.casa&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#6272a4&#34;&gt;# Routing config inspired from below:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#6272a4&#34;&gt;# https://github.com/linuxserver/reverse-proxy-confs/blob/20c5dbdcff92442262ed8907385e477935ea9336/aria2-with-webui.subdomain.conf.sample&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      extraConfig &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        tls {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          dns cloudflare {env.CLOUDFLARE_API_TOKEN}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        reverse_proxy /jsonrpc localhost:&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;toString&lt;/span&gt; config&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;services&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;aria2&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;settings&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;rpc-listen-port&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        file_server {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          root &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;pkgs&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;ariang&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;/share/ariang
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;      &amp;#39;&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#6272a4&#34;&gt;# TODO retrieve primary user programatically&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#6272a4&#34;&gt;# Needed so that I can modify downloaded files&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    users&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;users&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;jdheyburn&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;extraGroups &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [ &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aria2&amp;#34;&lt;/span&gt; ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    age&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;secrets&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aria2-password&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;file &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f1fa8c&#34;&gt;../../secrets/aria2-password.age&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    services&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;aria2 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      enable &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      rpcSecretFile &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; config&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;age&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;secrets&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aria2-password&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;path;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Any modules defined in this directory are &lt;a href=&#34;https://github.com/jdheyburn/nixos-configs/blob/f59b9cdfb1bb1557343841e139c4a3ba9108e370/flake.nix#L50-L55&#34;&gt;automatically loaded&lt;/a&gt; into each host. For a host to deploy the module, I simply need to add this line.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;modules&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;aria2&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;enable = true;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;fin&#34;&gt;Fin&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re in the market for a download manager then definitely check out aria2 + AriaNg to see if they fit the bill. I&amp;rsquo;m not sure if it&amp;rsquo;s something I&amp;rsquo;ll stick with forever, but it&amp;rsquo;s simplicity is a pull factor for me.&lt;/p&gt;
&lt;p&gt;The only downside to it is the manual step to get AriaNg configured with the backend. If you have any tips on how that could be improved then feel free to &lt;a href=&#34;https://jdheyburn.co.uk/contact/&#34;&gt;reach out&lt;/a&gt; to me!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>I&#39;m going to KubeConEU 2024</title><enclosure url="https://jdheyburn.co.uk/images/jdheyburn_co_uk_card.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/blog/attending-kubeconeu-2024/</link>
      <pubDate>Fri, 15 Mar 2024 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/attending-kubeconeu-2024/</guid>-->
      <description>&lt;p&gt;I was lucky enough to win a &lt;a href=&#34;https://www.dragonflydb.io/&#34;&gt;DragonflyDB&lt;/a&gt; competition for a free ticket to &lt;a href=&#34;https://events.linuxfoundation.org/kubecon-cloudnativecon-europe/&#34;&gt;KubeCon Europe 2024&lt;/a&gt; in Paris!&lt;/p&gt;
&lt;p&gt;Feel free to &lt;a href=&#34;https://jdheyburn.co.uk/contact/&#34;&gt;reach out&lt;/a&gt; if you fancy meeting up for a chat about everything and anything! I&amp;rsquo;m more than happy to chat the below, with some good starting points.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kubernetes
&lt;ul&gt;
&lt;li&gt;Sample installation of third-party agents on nodes&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Redis
&lt;ul&gt;
&lt;li&gt;Backups with Velero&lt;/li&gt;
&lt;li&gt;Migrating data into Kubernetes&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Observability
&lt;ul&gt;
&lt;li&gt;Prometheus, Thanos, and Grafana&lt;/li&gt;
&lt;li&gt;Datadog&lt;/li&gt;
&lt;li&gt;OpenTelemetry
&lt;ul&gt;
&lt;li&gt;I&amp;rsquo;m &lt;em&gt;really&lt;/em&gt; interested in finding out more about this&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And anything that I might&amp;rsquo;ve blogged about before, including Nix more recently.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll be at the Paris Expo from Tuesday for the co-located events. Hope to see some of you there!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Simplifying Caddy configs in NixOS</title><enclosure url="https://jdheyburn.co.uk/images/jdheyburn_co_uk_card.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/blog/caddy-nix-config-refactor/</link>
      <pubDate>Thu, 07 Mar 2024 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/caddy-nix-config-refactor/</guid>-->
      <description>&lt;h2 id=&#34;tldr&#34;&gt;tl;dr&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;My Caddy configs before were &lt;a href=&#34;https://jdheyburn.co.uk/blog/automating-service-configurations-with-nixos/#caddy-config&#34;&gt;bloody awfully&lt;/a&gt; complicated&lt;/li&gt;
&lt;li&gt;I scrapped that way of doing it and made it simpler in &lt;a href=&#34;https://github.com/jdheyburn/nixos-configs/pull/66&#34;&gt;this pull request&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;a-gentle-reminder&#34;&gt;A gentle reminder&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve written before about how I use Caddy to &lt;a href=&#34;https://jdheyburn.co.uk/blog/reverse-proxy-multiple-domains-using-caddy-2/&#34;&gt;reverse proxy&lt;/a&gt; my internal services. Since then I&amp;rsquo;ve migrated to NixOS and am using that to &lt;a href=&#34;https://jdheyburn.co.uk/blog/automating-service-configurations-with-nixos/#caddy-config&#34;&gt;automatically generate&lt;/a&gt; the Caddy config.&lt;/p&gt;
&lt;p&gt;However the approach I used was extremely over-engineered and unnecessarily complicates one of the benefits of Caddy, which is the simplicity of its &lt;a href=&#34;https://caddyserver.com/docs/caddyfile&#34;&gt;Caddyfile DSL&lt;/a&gt;. It was interesting to see how I could generate configs from Nix attribute sets, which served as a learning exercise and the basis for the &lt;a href=&#34;https://jdheyburn.co.uk/blog/nix-cheat-sheet/&#34;&gt;Nix cheat sheet&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;NixOS allows you to &lt;a href=&#34;https://github.com/NixOS/nixpkgs/blob/abde03c6b3d19be0a69c57e5a1f475b03a09dd71/nixos/modules/services/web-servers/caddy/default.nix#L11-L48&#34;&gt;specify Caddy config&lt;/a&gt; in a way that allows you to modularise it. Migrating to this structure will provide cleaner code for future iterations.&lt;/p&gt;
&lt;p&gt;Previously I had created the &lt;a href=&#34;https://github.com/jdheyburn/nixos-configs/blob/5175593745a27de7afc5249bc130a2f1c5edb64c/modules/caddy/default.nix&#34;&gt;module for Caddy&lt;/a&gt; in such a way that it would discover what services required a reverse proxy fronting it on said host. This snippet from the previous config would look up from the catalog for any services that were due to be hosted on this machine, then build the Caddy config from there.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;host_services = attrValues (filterAttrs (svc_name: svc_def:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  svc_def &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;host&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; svc_def&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;host&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;hostName &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; config&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;networking&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;hostName
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; svc_def&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;caddify&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;enable) catalog&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;services);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;i-have-seen-the-light&#34;&gt;I have seen the light&lt;/h2&gt;
&lt;p&gt;Instead of doing all the building of the Caddy JSON config by hand, I can declare a block in each service module &lt;em&gt;that requires it&lt;/em&gt; which will ensure Caddy will create a reverse proxy for it. This means a module now encapsulates everything that is required for this service to function properly.&lt;/p&gt;
&lt;p&gt;Such a module would then look like the below, with some lines removed for brevity. You can see it in full in &lt;a href=&#34;https://github.com/jdheyburn/nixos-configs/blob/46eca0686b735ff74a19867c625e7ecca2d9034a/modules/plex/default.nix&#34;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{ config&lt;span style=&#34;color:#ff79c6&#34;&gt;,&lt;/span&gt; pkgs&lt;span style=&#34;color:#ff79c6&#34;&gt;,&lt;/span&gt; lib&lt;span style=&#34;color:#ff79c6&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt; }:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt; lib;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;let&lt;/span&gt; cfg &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; config&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;modules&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;plex;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  options&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;modules&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;plex &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; { enable &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; mkEnableOption &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Deploy plex&amp;#34;&lt;/span&gt;; };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  config &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; mkIf cfg&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;enable {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    services&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;caddy&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;virtualHosts&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;plex.svc.joannet.casa&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;extraConfig &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;      tls {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        dns cloudflare {env.CLOUDFLARE_API_TOKEN}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;      reverse_proxy localhost:32400
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;    &amp;#39;&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    services&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;plex &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      enable &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      openFirewall &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is a lot simpler, and it meant that I was able to remove a &lt;strong&gt;lot&lt;/strong&gt; of complicated Nix code in the &lt;a href=&#34;https://github.com/jdheyburn/nixos-configs/pull/66&#34;&gt;pull request&lt;/a&gt; to achieve the same thing. The end result is a solution that is much easier to understand and pragmatic.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m still using a &lt;a href=&#34;https://github.com/jdheyburn/nixos-configs/blob/46eca0686b735ff74a19867c625e7ecca2d9034a/modules/dns/default.nix#L8-L31&#34;&gt;similar method&lt;/a&gt; for when writing the AdGuardHome DNS config, but I changed the functions to more appropriate names. You can &lt;a href=&#34;https://jdheyburn.co.uk/blog/automating-service-configurations-with-nixos/#dns-rewrites&#34;&gt;revisit the previous blog post&lt;/a&gt; where I walk through that.&lt;/p&gt;
&lt;p&gt;This refactoring to more complete modules isn&amp;rsquo;t ground-breaking by any means, but I wanted to share this win I gained :thumbs_up:.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How I use Obsidian to journal</title><enclosure url="https://jdheyburn.co.uk/images/jdheyburn_co_uk_card.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal/</link>
      <pubDate>Fri, 12 Jan 2024 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal/</guid>-->
      <description>&lt;h2 id=&#34;edits&#34;&gt;Edits&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;2025-03-26
&lt;ul&gt;
&lt;li&gt;Periodic Notes are now grouped in directories by their year (and if applicable, month)&lt;/li&gt;
&lt;li&gt;See blog post: &lt;a href=&#34;https://jdheyburn.co.uk/blog/reorganising-obsidian-journal-notes/&#34;&gt;Reorganising Obsidian Journal Notes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2025-01-26
&lt;ul&gt;
&lt;li&gt;Updated moment locale from &lt;code&gt;gb&lt;/code&gt; to &lt;code&gt;en-gb&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Sundays were incorrectly being templated as the first day of the week, instead of last&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;tldr&#34;&gt;tl;dr&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;I use Obsidian for journalling&lt;/li&gt;
&lt;li&gt;You can view the &lt;a href=&#34;https://gist.github.com/jdheyburn/f0e21fa2671625a21941074f1c94ada1&#34;&gt;templates&lt;/a&gt; I use for creating my daily, weekly, monthly, and quarterly notes&lt;/li&gt;
&lt;li&gt;See &lt;a href=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal/#deep-dive-into-note-templates&#34;&gt;Deep dive into note templates&lt;/a&gt; on how these templates work&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;introduction-to-obsidian&#34;&gt;Introduction to Obsidian&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve been using &lt;a href=&#34;https://obsidian.md/&#34;&gt;Obsidian&lt;/a&gt; for over 2 years now as my primary note-taking tool. Previously I was using &lt;a href=&#34;https://www.notion.so/&#34;&gt;Notion&lt;/a&gt; but then I got frustrated with the clunkiness of it (of which I am sure it has improved since then) and also required Internet connectivity to run - none of the files were local. Before that, I&amp;rsquo;d been keeping a physical notebook which wasn&amp;rsquo;t actually of much use for recalling information from time gone by.&lt;/p&gt;
&lt;p&gt;Obsidian is just one application used in personal knowledge management (PKM) and is a tool for thought (TfT). It is &lt;em&gt;not&lt;/em&gt; open-source, however, it is built by a very small, independent team of developers who are passionate about privacy, and user feedback, and who have &lt;a href=&#34;https://obsidian.md/about&#34;&gt;promised&lt;/a&gt; to never accept VC investment to remain independent.&lt;/p&gt;
&lt;p&gt;Since &lt;a href=&#34;https://www.youtube.com/watch?v=z58FsjT-UO8&#34;&gt;kepano became CEO of Obsidian&lt;/a&gt;, they have been highlighting what sets Obsidian apart from other PKM tools. Ensuring that it is local-first means that you can use Obsidian without your data being stored anywhere. The files are in &lt;a href=&#34;https://en.wikipedia.org/wiki/Markdown&#34;&gt;markdown&lt;/a&gt; format, which is the de-facto standard for documentation - no proprietary file formats are used here. With these main points, if Obsidian were to cease to exist, you could pick your files up and move them to another app. These make Obsidian work &lt;strong&gt;blazingly quick&lt;/strong&gt; - no loading screens or sluggish navigating; everything loads immediately.&lt;/p&gt;
&lt;p&gt;Obsidian&amp;rsquo;s functionality is propelled by the amazing community which has written several plugins that provide it the flexibility to be used for many use cases. Including databases, &lt;a href=&#34;https://github.com/mgmeyers/obsidian-kanban&#34;&gt;Kanban boards&lt;/a&gt;, project management&amp;hellip; there are &lt;a href=&#34;https://obsidian.md/plugins&#34;&gt;over 1,000&lt;/a&gt; to choose from.&lt;/p&gt;
&lt;p&gt;The application itself is free, but if you want to synchronise notes between different desktops or even the mobile app, they offer a &lt;a href=&#34;https://obsidian.md/sync&#34;&gt;Sync feature&lt;/a&gt; for a cost. You could also achieve file sync using a third party, but given that it is a small independent team working on the app, I&amp;rsquo;m happy to pay for Sync as a means of supporting them too. There are also options for &lt;a href=&#34;https://help.obsidian.md/Licenses+and+payment/Catalyst+license&#34;&gt;one time payments&lt;/a&gt; to help assist them too (I am a Supporter!).&lt;/p&gt;
&lt;p&gt;While I don&amp;rsquo;t see myself necessarily as a power user (at least, my notes are somewhat all-over-the-shop and unlinked), I wanted to write up how I&amp;rsquo;ve been using it for journalling over the last 2 years. I hope this then becomes a series of my journey through PKM, and to share additional blog posts which I hope inspire others.&lt;/p&gt;
&lt;h2 id=&#34;why-journal&#34;&gt;Why journal?&lt;/h2&gt;
&lt;p&gt;Everyone has their reasons for journalling; some people use it to track thoughts, emotions, etc. For myself though as I started approaching my late twenties I felt that life was passing by very quickly - unsure of what I had accomplished or even done on a particular day, and what the general theme of a week or month was to me. I use journalling as a reference back to see how far I have progressed whether in my career or my personal life.&lt;/p&gt;
&lt;p&gt;Obsidian has made this extremely easy for me to do with its &lt;a href=&#34;https://help.obsidian.md/Plugins/Daily+notes&#34;&gt;Daily Notes feature&lt;/a&gt;. These are simply notes that are specific to one day only and can be created from a template so that you don&amp;rsquo;t need to populate them in the boilerplate.&lt;/p&gt;
&lt;p&gt;In this post, I&amp;rsquo;ll walk through the setup that I use for daily journalling, which then bubbles up to weekly, monthly, and then quarterly notes. You can see some sample screenshots below.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;daily-note-overview.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal//blog/how-i-use-obsidian-to-journal/daily-note-overview.png&#34;
    alt=&#34;Daily note&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Daily note&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;weekly-note-overview.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal//blog/how-i-use-obsidian-to-journal/weekly-note-overview.png&#34;
    alt=&#34;Weekly note&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Weekly note&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;monthly-note-overview.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal//blog/how-i-use-obsidian-to-journal/monthly-note-overview.png&#34;
    alt=&#34;Monthly note&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Monthly note&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;quarterly-note-overview.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal//blog/how-i-use-obsidian-to-journal/quarterly-note-overview.png&#34;
    alt=&#34;Quarterly note&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Quarterly note&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This is achieved with these community plugins:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Calendar
&lt;ul&gt;
&lt;li&gt;Provides a calendar widget in the right pane of Obsidian&lt;/li&gt;
&lt;li&gt;Clicking on a day or week takes you to its respective note&lt;/li&gt;
&lt;li&gt;I find it&amp;rsquo;s an easy way to generate historical daily and weekly notes&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Dataview
&lt;ul&gt;
&lt;li&gt;Enables querying of properties from notes and displaying them into another&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Periodic Notes
&lt;ul&gt;
&lt;li&gt;Quickly create daily, weekly, monthly, quarterly, and yearly notes&lt;/li&gt;
&lt;li&gt;Configurable to choose the name format of the notes, which template to use, and where to place the notes&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Templater
&lt;ul&gt;
&lt;li&gt;Creates files from a template&lt;/li&gt;
&lt;li&gt;Allows you to dynamically generate the content of notes from logic&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;setting-up-the-workflow&#34;&gt;Setting up the workflow&lt;/h2&gt;
&lt;p&gt;In this section I&amp;rsquo;ll run through the templates, plugins, and configuration you need to replicate the same workflow in the folder that you have open in Obsidian (known as a &lt;a href=&#34;https://help.obsidian.md/Getting+started/Create+a+vault&#34;&gt;vault&lt;/a&gt;).&lt;/p&gt;
&lt;h3 id=&#34;template-files&#34;&gt;Template files&lt;/h3&gt;
&lt;p&gt;I use templates extensively for dynamically generating notes, especially on notes that are created every day.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;templates/DailySummary.md
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;templates/WeeklySummary.md
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;templates/MonthlySummary.md
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;templates/QuarterlySummary.md
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;See the links below for the templates needed for journalling.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://gist.github.com/jdheyburn/f0e21fa2671625a21941074f1c94ada1#file-dailysummary-txt&#34;&gt;Daily&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://gist.github.com/jdheyburn/f0e21fa2671625a21941074f1c94ada1#file-weeklysummary-txt&#34;&gt;Weekly&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://gist.github.com/jdheyburn/f0e21fa2671625a21941074f1c94ada1#file-monthlysummary-txt&#34;&gt;Monthly&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://gist.github.com/jdheyburn/f0e21fa2671625a21941074f1c94ada1#file-quarterlysummary-txt&#34;&gt;Quarterly&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I run through the sections of each of these templates later on.&lt;/p&gt;
&lt;h3 id=&#34;plugins-and-configuration&#34;&gt;Plugins and configuration&lt;/h3&gt;
&lt;p&gt;Now that we have the template files, we can configure the plugins. Make sure you &lt;a href=&#34;https://help.obsidian.md/Extending+Obsidian/Community+plugins&#34;&gt;install them first&lt;/a&gt; if you&amp;rsquo;re following along as a guide. As a reminder the plugins you&amp;rsquo;ll need are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/liamcain/obsidian-calendar-plugin&#34;&gt;Calendar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/blacksmithgu/obsidian-dataview&#34;&gt;Dataview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/liamcain/obsidian-periodic-notes&#34;&gt;Periodic Notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/SilentVoid13/Templater&#34;&gt;Templater&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;calendar&#34;&gt;Calendar&lt;/h4&gt;
&lt;p&gt;For this I enable &lt;strong&gt;Show week number&lt;/strong&gt; to let me click on week numbers to create notes from them in the widget.&lt;/p&gt;
&lt;p&gt;I like to enable &lt;strong&gt;Confirm before creating a new note&lt;/strong&gt; so that I don&amp;rsquo;t accidentally create notes I didn&amp;rsquo;t mean to when clicking in the widget.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;calendar-plugin-settings.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal//blog/how-i-use-obsidian-to-journal/calendar-plugin-settings.png&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h4 id=&#34;dataview&#34;&gt;Dataview&lt;/h4&gt;
&lt;p&gt;After installation, there&amp;rsquo;s no additional config needed for Dataview.&lt;/p&gt;
&lt;h4 id=&#34;periodic-notes&#34;&gt;Periodic Notes&lt;/h4&gt;
&lt;p&gt;I enable all toggles with the exception of &lt;strong&gt;Quarterly Notes&lt;/strong&gt; and &lt;strong&gt;Yearly Notes&lt;/strong&gt;, since I don&amp;rsquo;t have these templated just yet.&lt;/p&gt;
&lt;p&gt;The config for each note is as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Daily Notes
&lt;ul&gt;
&lt;li&gt;Format: &lt;code&gt;YYYY-MM-DD&lt;/code&gt; (the default)
&lt;ul&gt;
&lt;li&gt;Creates files in the format &lt;code&gt;2023-11-20&lt;/code&gt;, which is the&lt;a href=&#34;https://en.wikipedia.org/wiki/ISO_8601&#34;&gt;ISO 8601 standard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A benefit of this format is that alphabetical order is the same as chronological&lt;/li&gt;
&lt;li&gt;So as daily notes are created, they are already in order&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Daily Note Template: &lt;code&gt;templates/DailySummary.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Note Folder: &lt;code&gt;planner/daily&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Weekly Notes
&lt;ul&gt;
&lt;li&gt;Format: &lt;code&gt;YYYY-[W]ww&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Creates files in the format &lt;code&gt;2023-W46&lt;/code&gt;, also ISO 8601 compliant&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Weekly Note Template: &lt;code&gt;templates/WeeklySummary.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Note Folder: &lt;code&gt;planner/weekly&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Monthly Notes
&lt;ul&gt;
&lt;li&gt;Format: &lt;code&gt;YYYY-MM-[M]&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Creates notes in the format &lt;code&gt;2023-11-M&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Monthly Note Template: &lt;code&gt;templates/MonthlySummary.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Note Folder: &lt;code&gt;planner/monthly&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Quarterly Notes
&lt;ul&gt;
&lt;li&gt;Format: &lt;code&gt;YYYY-[Q]Q&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;e.g. &lt;code&gt;2023-Q4&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Quarterly Note Template: &lt;code&gt;templates/QuarterlySummary.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Note Folder: &lt;code&gt;planner/quarterly&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;daily-notes-plugin-settings.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal//blog/how-i-use-obsidian-to-journal/daily-notes-plugin-settings.png&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h4 id=&#34;templater&#34;&gt;Templater&lt;/h4&gt;
&lt;p&gt;Other than setting the &lt;strong&gt;Template folder location&lt;/strong&gt; to &lt;code&gt;templates&lt;/code&gt;, there is no other config needed.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;templater-plugin-settings.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal//blog/how-i-use-obsidian-to-journal/templater-plugin-settings.png&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;connecting-everything-together&#34;&gt;Connecting everything together&lt;/h3&gt;
&lt;p&gt;If everything is set up correctly, you should be able to click on a day or week in the Calendar widget and it should generate a note for you. Another way to create these is via the command palette, which can be access through the hotkey &lt;code&gt;Cmd+P&lt;/code&gt; or &lt;code&gt;Ctrl+P&lt;/code&gt; - this is how I create monthly notes.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;periodic-notes-command-palette.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal//blog/how-i-use-obsidian-to-journal/periodic-notes-command-palette.png&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;deep-dive-into-note-templates&#34;&gt;Deep dive into note templates&lt;/h2&gt;
&lt;h3 id=&#34;daily&#34;&gt;Daily&lt;/h3&gt;
&lt;p&gt;The daily note is generated from &lt;a href=&#34;https://gist.github.com/jdheyburn/f0e21fa2671625a21941074f1c94ada1#file-dailysummary-txt&#34;&gt;this template&lt;/a&gt;, and all daily notes are stored in &lt;code&gt;planner/daily&lt;/code&gt;.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;daily-note-overview.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal//blog/how-i-use-obsidian-to-journal/daily-note-overview.png&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Here&amp;rsquo;s a walkthrough on what&amp;rsquo;s happening in each section.&lt;/p&gt;
&lt;h4 id=&#34;properties&#34;&gt;Properties&lt;/h4&gt;
&lt;p&gt;&lt;a href=&#34;https://help.obsidian.md/Editing+and+formatting/Properties&#34;&gt;Properties&lt;/a&gt; define the metadata of the note, which can be used to query your notes to retrieve data back using Dataview. You define it in your notes via &lt;a href=&#34;https://jekyllrb.com/docs/front-matter/&#34;&gt;YAML front matter&lt;/a&gt;, which is YAML values wrapped around lines of &lt;code&gt;---&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;--- &lt;span style=&#34;color:#6272a4&#34;&gt;# Front matter starts here&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;key&lt;/span&gt;: value
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;--- &lt;span style=&#34;color:#6272a4&#34;&gt;# Front matter ends here&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Normal markdown text from here
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Templater is used to populate the property values in the front matter, so let&amp;rsquo;s look at the &lt;a href=&#34;https://gist.github.com/jdheyburn/f0e21fa2671625a21941074f1c94ada1#file-dailysummary-txt-L1-L13&#34;&gt;local variables used&lt;/a&gt; in the daily note template.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;%*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;moment.locale(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;en-gb&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; thisDay &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;window&lt;/span&gt;.moment(&lt;span style=&#34;color:#ff79c6&#34;&gt;new&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;Date&lt;/span&gt;(tp.file.title));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; previousDay &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; thisDay.clone().subtract(&lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;days&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; previousDayFmt &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; previousDay.format(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;YYYY-MM-DD&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; lastYearDay &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; thisDay.clone().subtract(&lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;years&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; nextDay &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; thisDay.clone().add(&lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;days&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; nextDayFmt &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; nextDay.format(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;YYYY-MM-DD&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; thisWeek &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; thisDay.format(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;YYYY-[W]ww&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; thisMonth &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; thisDay.format(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;YYYY-MM-[M]&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; thisDayLong &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; thisDay.format(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;dddd Do MMMM YYYY&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; thisYear &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; thisDay.format(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;YYYY-[Y]&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;-%&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Reviewing each one of these:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;thisDay&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Creates a &lt;a href=&#34;https://silentvoid13.github.io/Templater/internal-functions/internal-modules/date-module.html?highlight=moment#momentjs&#34;&gt;moment.js&lt;/a&gt; object for the date that this daily note represents&lt;/li&gt;
&lt;li&gt;Remember that daily notes are created in the format &lt;code&gt;YYYY-MM-DD&lt;/code&gt; (&lt;code&gt;2023-11-20&lt;/code&gt;), which is already the format for the &lt;code&gt;Date&lt;/code&gt; constructor&lt;/li&gt;
&lt;li&gt;By using the file name as the source of truth, it allows me to create daily notes from the Calendar widget just by clicking on the dates&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;previousDay&lt;/code&gt; and &lt;code&gt;previousDayFmt&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Calculate the day previous to this one, and then format it in to &lt;code&gt;YYYY-MM-DD&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Used for navigation&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nextDay&lt;/code&gt; and &lt;code&gt;nextDayFmt&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Similar to above, but for the succeeding day&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lastYearDay&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Calculate the same day last year&lt;/li&gt;
&lt;li&gt;Used to query the journal entry from last year&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;thisWeek&lt;/code&gt;, &lt;code&gt;thisMonth&lt;/code&gt;, &lt;code&gt;thisYear&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;The week/month/year that this day is in&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;thisDayLong&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;A long format of this date (i.e. &lt;code&gt;Monday 20th November 2023&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You&amp;rsquo;ll notice that I have to clone &lt;code&gt;thisDay&lt;/code&gt;, because &lt;code&gt;add()&lt;/code&gt; and &lt;code&gt;subtract()&lt;/code&gt; are mutating.&lt;/p&gt;
&lt;p&gt;These variables then get placed into the YAML front matter for the note. You&amp;rsquo;ll notice that there is &lt;code&gt;date&lt;/code&gt; and &lt;code&gt;createdAt&lt;/code&gt; which is a standard across any note in my vault and represent the date and time the note was created.&lt;/p&gt;
&lt;p&gt;Additionally there is a &lt;code&gt;dateShort&lt;/code&gt; property which is the date of the note, but formatted in a nicer manner for when the note is recalled.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;date&lt;/span&gt;: &amp;lt;% tp.date.now(&amp;#34;YYYY-MM-DD&amp;#34;) %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;createdAt&lt;/span&gt;: &amp;lt;% tp.file.creation_date() %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;weekly&lt;/span&gt;: &amp;lt;% thisWeek %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;monthly&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &amp;lt;% thisMonth %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;yearly&lt;/span&gt;: &amp;lt;% thisYear %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;dateShort&lt;/span&gt;: &amp;lt;% thisDay.format(&amp;#34;dddd Do MMMM&amp;#34;) %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;aliases&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &amp;lt;% thisDayLong %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After templating it looks like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;date&lt;/span&gt;: 2023-11-20
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;createdAt&lt;/span&gt;: 2023-11-20 &lt;span style=&#34;color:#bd93f9&#34;&gt;18&lt;/span&gt;:&lt;span style=&#34;color:#bd93f9&#34;&gt;33&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;weekly&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;2023&lt;/span&gt;-W47
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;monthly&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#bd93f9&#34;&gt;2023-11&lt;/span&gt;-M
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;yearly&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;2023&lt;/span&gt;-&lt;span style=&#34;color:#ff79c6&#34;&gt;Y&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;dateShort&lt;/span&gt;: Monday 20th November
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;aliases&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - Monday 20th November 2023
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Since Obsidian has first-class support for properties, it creates a nice interface.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;daily-note-properties.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal//blog/how-i-use-obsidian-to-journal/daily-note-properties.png&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h4 id=&#34;navigation&#34;&gt;Navigation&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;# &amp;lt;% thisDayLong %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;&lt;/span&gt;&amp;lt;&amp;lt; [[planner/daily/&amp;lt;% previousDayFmt %&amp;gt;|&amp;lt;% previousDayFmt %&amp;gt;]] || [[planner/daily/&amp;lt;% nextDayFmt %&amp;gt;|&amp;lt;% nextDayFmt %&amp;gt;]] &amp;gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[[&amp;lt;% thisWeek %&amp;gt;]]  || [[&amp;lt;% thisMonth %&amp;gt;]]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is the main title for the note I&amp;rsquo;m working in. There are also links to other notes related to this day, such as the adjacent days (although I primarily use the hotkeys &lt;code&gt;Ctrl+1&lt;/code&gt; and &lt;code&gt;Ctrl+3&lt;/code&gt; to navigate previous and next days respectively), along with a link to the weekly and monthly notes for this day.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;# Monday 20th November 2023
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;&lt;/span&gt;&amp;lt;&amp;lt; [[planner/daily/2023-11-19|2023-11-19]] || [[planner/daily/2023-11-21|2023-11-21]] &amp;gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[[2023-W47]]  || [[2023-11-M]]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This visualises as:&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;daily-note-navigation-preview.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal//blog/how-i-use-obsidian-to-journal/daily-note-navigation-preview.png&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;See below for the hotkeys I have set up for navigating daily notes. Even if I don&amp;rsquo;t have hotkeys for them, I can still access the &lt;a href=&#34;https://help.obsidian.md/Plugins/Command+palette&#34;&gt;command palette&lt;/a&gt; at the hotkey &lt;code&gt;Ctrl+P&lt;/code&gt;, or &lt;code&gt;Cmd+P&lt;/code&gt; or macOS.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;periodic-notes-hotkeys.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal//blog/how-i-use-obsidian-to-journal/periodic-notes-hotkeys.png&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h4 id=&#34;summary&#34;&gt;Summary&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;## Summary 🏁
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;```dataview
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&lt;/span&gt;TABLE WITHOUT ID DailySummary as &amp;#34;This time last year: &amp;lt;% lastYearDay.format(&amp;#34;dddd Do MMMM YYYY&amp;#34;) %&amp;gt;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;FROM &amp;#34;planner/daily&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;WHERE file.name = &amp;#34;&amp;lt;% lastYearDay.format(&amp;#34;YYYY-MM-DD&amp;#34;) %&amp;gt;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;```&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;DailySummary:: 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After &lt;code&gt;DailySummary::&lt;/code&gt; is where I write the day&amp;rsquo;s journal entry. Before that though, I have a Dataview query which retrieves the journal entry for the same day, but the year before. I like retrieving the journal entry for the year before to frame time somewhat.&lt;/p&gt;
&lt;p&gt;After templating, it looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;## Summary 🏁
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;```dataview
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&lt;/span&gt;TABLE WITHOUT ID DailySummary as &amp;#34;This time last year: Sunday 20th November 2022&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;FROM &amp;#34;planner/daily&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;WHERE file.name = &amp;#34;2022-11-20&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;```&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;DailySummary:: 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And here it is when rendered.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;daily-summary-preview.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal//blog/how-i-use-obsidian-to-journal/daily-summary-preview.png&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h4 id=&#34;a-bit-about-dataview-and-querying&#34;&gt;A bit about Dataview and querying&lt;/h4&gt;
&lt;p&gt;Dataview is one of the most popular plugins for Obsidian, and there&amp;rsquo;s good reason for it. It opens up a whole world of dynamically presenting the files in your vault in a database-like manner. You can query for files themselves, or their metadata.&lt;/p&gt;
&lt;p&gt;For example, the Dataview query below will retrieve all notes that are tagged with &lt;code&gt;#kubernetes&lt;/code&gt; and &lt;code&gt;#worklog&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;```dataview
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&lt;/span&gt;LIST
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;FROM #kubernetes AND #worklog 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SORT file.name
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;```&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This then is visualised as:&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;example-dataview-query-preview.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal//blog/how-i-use-obsidian-to-journal/example-dataview-query-preview.png&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;So how do you add metadata? Dataview allows for a &lt;a href=&#34;https://blacksmithgu.github.io/obsidian-dataview/annotation/add-metadata/&#34;&gt;couple of methods&lt;/a&gt; ; including &lt;a href=&#34;https://jekyllrb.com/docs/front-matter/&#34;&gt;YAML front matter&lt;/a&gt; (aka &lt;a href=&#34;https://help.obsidian.md/Editing+and+formatting/Properties&#34;&gt;Properties&lt;/a&gt;), or in-line in the file via a key name appended by two colons - e.g. &lt;code&gt;NewProperty::&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I would definitely recommend to &lt;a href=&#34;https://blacksmithgu.github.io/obsidian-dataview/&#34;&gt;check the docs&lt;/a&gt; for Dataview, I&amp;rsquo;ll be covering it in more detail in this post for my particular use-case.&lt;/p&gt;
&lt;h3 id=&#34;weekly&#34;&gt;Weekly&lt;/h3&gt;
&lt;p&gt;The weekly note is used to summarise what happened during a week. In order for me to recall each day, I use a Dataview query to retrieve the &lt;code&gt;DailySummary&lt;/code&gt; for each day in that week.&lt;/p&gt;
&lt;p&gt;I use &lt;a href=&#34;https://gist.github.com/jdheyburn/f0e21fa2671625a21941074f1c94ada1#file-weeklysummary-txt&#34;&gt;this template&lt;/a&gt; for my weekly notes, which produces the below.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;weekly-note-overview.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal//blog/how-i-use-obsidian-to-journal/weekly-note-overview.png&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h4 id=&#34;properties-1&#34;&gt;Properties&lt;/h4&gt;
&lt;p&gt;These local variables are define in the template:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;%*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;moment.locale(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;en-gb&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; thisWeek &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;window&lt;/span&gt;.moment(tp.file.title, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;YYYY-&amp;amp;W WW&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; previousWeek &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; thisWeek.clone().subtract(&lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;weeks&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; nextWeek &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; thisWeek.clone().add(&lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;weeks&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; theseMonths &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [...&lt;span style=&#34;color:#ff79c6&#34;&gt;new&lt;/span&gt; Set([&lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#bd93f9&#34;&gt;2&lt;/span&gt;,&lt;span style=&#34;color:#bd93f9&#34;&gt;3&lt;/span&gt;,&lt;span style=&#34;color:#bd93f9&#34;&gt;4&lt;/span&gt;,&lt;span style=&#34;color:#bd93f9&#34;&gt;5&lt;/span&gt;,&lt;span style=&#34;color:#bd93f9&#34;&gt;6&lt;/span&gt;,&lt;span style=&#34;color:#bd93f9&#34;&gt;7&lt;/span&gt;].map(d=&amp;gt;moment(&lt;span style=&#34;color:#f1fa8c&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;tp.file.title&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;d&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;`&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;YYYY-W-E&amp;#34;&lt;/span&gt;).format(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;YYYY-MM-[M]&amp;#34;&lt;/span&gt;)))]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; thisYear &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; thisWeek.format(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;YYYY-[Y]&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;-%&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;thisWeek&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Creates a &lt;a href=&#34;https://silentvoid13.github.io/Templater/internal-functions/internal-modules/date-module.html?highlight=moment#momentjs&#34;&gt;moment.js&lt;/a&gt; object for the week that this note is for&lt;/li&gt;
&lt;li&gt;We&amp;rsquo;re parsing the file title (which is in the format &lt;code&gt;2023-W47&lt;/code&gt;) to create the object&lt;/li&gt;
&lt;li&gt;Similarly to the daily note, this allows me to create weekly notes from the Calendar widget by clicking on the week number&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;previousWeek&lt;/code&gt; and &lt;code&gt;nextWeek&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Calculate the weeks that are adjacent to this one&lt;/li&gt;
&lt;li&gt;Used for navigation&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;theseMonths&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Calculates the months that this week traverses&lt;/li&gt;
&lt;li&gt;It loops over days of the week (i.e. first day in the week), creates a new moment object, then formats it in the month format&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;thisYear&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;The year that this week is in&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These variables are then used in the YAML front matter below. There is some extra logic to print out &lt;code&gt;monthly&lt;/code&gt; into an array.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;date&lt;/span&gt;: &amp;lt;% tp.date.now(&amp;#34;YYYY-MM-DD&amp;#34;) %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;createdAt&lt;/span&gt;: &amp;lt;% tp.file.creation_date() %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;monthly&lt;/span&gt;: &amp;lt;%* theseMonths.forEach(month =&amp;gt; tR += `\n - ${month}`) %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;yearly&lt;/span&gt;: &amp;lt;% thisYear %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After templating, it looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;date&lt;/span&gt;: 2023-11-20
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;createdAt&lt;/span&gt;: 2023-11-20 &lt;span style=&#34;color:#bd93f9&#34;&gt;19&lt;/span&gt;:&lt;span style=&#34;color:#bd93f9&#34;&gt;35&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;monthly&lt;/span&gt;: 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; - &lt;span style=&#34;color:#bd93f9&#34;&gt;2023-11&lt;/span&gt;-M
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;yearly&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;2023&lt;/span&gt;-&lt;span style=&#34;color:#ff79c6&#34;&gt;Y&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Visualised as:&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;weekly-note-properties-preview.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal//blog/how-i-use-obsidian-to-journal/weekly-note-properties-preview.png&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h4 id=&#34;navigation-1&#34;&gt;Navigation&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&amp;lt; [[&amp;lt;% previousWeek.format(&amp;#34;YYYY-[W]ww&amp;#34;) %&amp;gt;]] || [[&amp;lt;% nextWeek.format(&amp;#34;YYYY-[W]ww&amp;#34;) %&amp;gt;]] &amp;gt;&amp;gt;&amp;lt;%* theseMonths.forEach(month =&amp;gt; tR += &lt;span style=&#34;color:#f1fa8c&#34;&gt;`\n[[${month}]]`&lt;/span&gt;) %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I can navigate to adjacent weekly notes, and the monthly notes for this week from this panel. After templating it looks like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&amp;lt; [[2023-W46]] || [[2023-W48]] &amp;gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[[&lt;span style=&#34;color:#bd93f9&#34;&gt;2023-11&lt;/span&gt;-M]]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then is visualised as:&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;weekly-note-navigation-preview.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal//blog/how-i-use-obsidian-to-journal/weekly-note-navigation-preview.png&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h4 id=&#34;summary-1&#34;&gt;Summary&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;```dataview
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&lt;/span&gt;TABLE WITHOUT ID (&amp;#34;[[&amp;#34;+file.name+&amp;#34;|&amp;#34;+dateShort+&amp;#34;]]&amp;#34;) as &amp;#34;Daily Summaries&amp;#34;, DailySummary as &amp;#34; &amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;FROM &amp;#34;planner/daily&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;WHERE weekly = this.file.name
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SORT file.name ASC
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;```&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;## Summary
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;WeeklySummary:: 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;WeeklySummary::&lt;/code&gt; is where the summary of that week is entered. Above that is a Dataview query for retrieving all the daily entries for the days within that week. This renders the view below:&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;weekly-note-dataview-query-preview.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal//blog/how-i-use-obsidian-to-journal/weekly-note-dataview-query-preview.png&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Let&amp;rsquo;s now break down what the query is doing, skipping over the first line for now.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FROM &amp;quot;planner/daily&amp;quot;&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;limits searching for files in this directory, which is where my daily notes get placed&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WHERE weekly = this.file.name&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;filters the results to only match files where the &lt;code&gt;weekly&lt;/code&gt; property is equal to the name of the file &lt;em&gt;where the query is executed from&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;remember that this is executing from my weekly note, which is named &lt;code&gt;2023-W47&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;this will then only return daily notes for the given week&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SORT file.name ASC&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;arrange the files in alphabetical order&lt;/li&gt;
&lt;li&gt;given we named the files in the ISO8601 date format, these are inherently ordered chronologically&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If we were to do a basic &lt;a href=&#34;https://blacksmithgu.github.io/obsidian-dataview/queries/structure/#choose-a-output-format&#34;&gt;LIST&lt;/a&gt; output, with the query below&amp;hellip;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;```dataview
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&lt;/span&gt;LIST
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;FROM &amp;#34;planner/daily&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;WHERE weekly = this.file.name
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SORT file.name ASC
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;```&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then it would print out a list of all the files with the given property.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;example-list-output.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal//blog/how-i-use-obsidian-to-journal/example-list-output.png&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;While this tells us what days were in the week, we can use the &lt;a href=&#34;https://blacksmithgu.github.io/obsidian-dataview/queries/query-types/#table&#34;&gt;TABLE&lt;/a&gt; query to display metadata of the queried files in an easy to view format. So reviewing the first line of our original query again:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;TABLE WITHOUT ID (&amp;#34;[[&amp;#34;+file.name+&amp;#34;|&amp;#34;+dateShort+&amp;#34;]]&amp;#34;) as &amp;#34;Daily Summaries&amp;#34;, DailySummary as &amp;#34; &amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TABLE WITHOUT ID&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;defines this as a TABLE query&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WITHOUT ID&lt;/code&gt; will hide the name of the file, i.e. &lt;code&gt;2023-11-13&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;We want to hide it because we want to use the human friendly format instead&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now we define the name of the columns for the table.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;(&amp;quot;[[&amp;quot;+file.name+&amp;quot;|&amp;quot;+dateShort+&amp;quot;]]&amp;quot;) as &amp;quot;Daily Summaries&amp;quot;&lt;/code&gt; will create a column named Daily Summaries, with the text being a hyperlink to the file of that daily note, with the display text being the human friendly text.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DailySummary as &amp;quot; &amp;quot;&lt;/code&gt; creates a column with an empty name, using the file property &lt;code&gt;DailySummary&lt;/code&gt; as the values. Given that we&amp;rsquo;re setting an in-line property in the daily notes at &lt;code&gt;DailySummary::&lt;/code&gt;, the Dataview query is able to display back the values.&lt;/p&gt;
&lt;p&gt;Once we&amp;rsquo;ve written the weekly summary, that bubbles up to the monthly note.&lt;/p&gt;
&lt;h3 id=&#34;monthly&#34;&gt;Monthly&lt;/h3&gt;
&lt;p&gt;The monthly note consolidates all the weekly notes into here so that I can write a summary, and is created from &lt;a href=&#34;https://gist.github.com/jdheyburn/f0e21fa2671625a21941074f1c94ada1#file-monthlysummary-txt&#34;&gt;this template&lt;/a&gt;.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;monthly-note-overview.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal//blog/how-i-use-obsidian-to-journal/monthly-note-overview.png&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h4 id=&#34;properties-and-navigation&#34;&gt;Properties and navigation&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;%*&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;moment.locale(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;en-gb&amp;#34;&lt;/span&gt;)  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; thisMonth &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;window&lt;/span&gt;.moment(tp.file.title, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;YYYY-MM-&amp;amp;M &amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; thisQuarter &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; thisMonth.format(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;YYYY-[Q]Q&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; previousMonth &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; thisMonth.clone().subtract(&lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;months&amp;#39;&lt;/span&gt;)  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; nextMonth &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; thisMonth.clone().add(&lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;months&amp;#39;&lt;/span&gt;)  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; thisYear &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; thisMonth.format(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;YYYY-[Y]&amp;#34;&lt;/span&gt;)  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;-%&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;thisMonth&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Creates a moment object for the month that this note is for&lt;/li&gt;
&lt;li&gt;Similar to daily and weekly, we are parsing the title of the note to create the object&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;thisQuarter&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;The quarter that this month is in&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;previousMonth&lt;/code&gt; and &lt;code&gt;nextMonth&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Adjacent months to this one&lt;/li&gt;
&lt;li&gt;Used for navigation&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;thisYear&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;The year that this month is in&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These variables get used in the template for the YAML front matter, and navigation.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;date&lt;/span&gt;: &amp;lt;% tp.date.now(&amp;#34;YYYY-MM-DD&amp;#34;) %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;createdAt&lt;/span&gt;: &amp;lt;% tp.file.creation_date() %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;quarterly&lt;/span&gt;: &amp;lt;% thisQuarter %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;yearly&lt;/span&gt;: &amp;lt;% thisYear %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&amp;lt; [[&amp;lt;% previousMonth.format(&amp;#34;YYYY-MM-[M]&amp;#34;) %&amp;gt;]] || [[&amp;lt;% nextMonth.format(&amp;#34;YYYY-MM-[M]&amp;#34;) %&amp;gt;]] &amp;gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[[&amp;lt;% thisQuarter %&amp;gt;]]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[[&amp;lt;% thisYear %&amp;gt;]]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After templating, it looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;date&lt;/span&gt;: 2023-11-20
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;createdAt&lt;/span&gt;: 2023-11-20 &lt;span style=&#34;color:#bd93f9&#34;&gt;19&lt;/span&gt;:&lt;span style=&#34;color:#bd93f9&#34;&gt;36&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;quarterly&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;2023&lt;/span&gt;-Q4
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;yearly&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;2023&lt;/span&gt;-&lt;span style=&#34;color:#ff79c6&#34;&gt;Y&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&amp;lt; [[2023-10-M]] || [[2023-12-M]] &amp;gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[[&lt;span style=&#34;color:#bd93f9&#34;&gt;2023&lt;/span&gt;-Q4]]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[[&lt;span style=&#34;color:#bd93f9&#34;&gt;2023&lt;/span&gt;-&lt;span style=&#34;color:#ff79c6&#34;&gt;Y&lt;/span&gt;]]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And this is how both of them are rendered:&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;monthly-properties-and-navigation.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal//blog/how-i-use-obsidian-to-journal/monthly-properties-and-navigation.png&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h4 id=&#34;summary-2&#34;&gt;Summary&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;## Summaries 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;```dataview
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&lt;/span&gt;Table without id (&amp;#34;[[&amp;#34;+file.name+&amp;#34;]]&amp;#34;) as &amp;#34;Weekly summaries&amp;#34;, WeeklySummary as &amp;#34; &amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;FROM &amp;#34;planner/weekly&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;WHERE contains(monthly, this.file.name)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SORT file.name ASC
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;```&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This Dataview query retrieves all the weekly summaries for the weeks in this month. It&amp;rsquo;s similar to the query used in weekly notes to retrieve daily summaries, with the exception that the &lt;code&gt;WHERE&lt;/code&gt; clause is modified to handle &lt;code&gt;monthly&lt;/code&gt;, given it&amp;rsquo;s an array.&lt;/p&gt;
&lt;p&gt;Below that I have a heading for where the monthly summary would go.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&#34;text-decoration:underline&#34;&gt;[!hint] 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;text-decoration:underline&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&#34;text-decoration:underline&#34;&gt;Anything written under the **Month summary** section is added to the quarterly review.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;text-decoration:underline&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;### Monthly summary
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I tend to write a longer prose for the month summary, and so that wouldn&amp;rsquo;t fit in nicely in a Dataview query. Instead I embed the contents of the heading in the quarterly note, which I&amp;rsquo;ll talk about in that section. I include a &lt;a href=&#34;https://help.obsidian.md/Editing+and+formatting/Callouts&#34;&gt;callout&lt;/a&gt; which helps remind me of that set up.&lt;/p&gt;
&lt;p&gt;When rendered, it looks like this:&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;monthly-note-dataview-query-preview.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal//blog/how-i-use-obsidian-to-journal/monthly-note-dataview-query-preview.png&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;quarterly&#34;&gt;Quarterly&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ve not really built up a habit of writing a summary for quarterly notes, but I do collate the summaries of all months in a quarter into its note. These are located in &lt;code&gt;planner/quarterly&lt;/code&gt; in the format &lt;code&gt;yyyy-[Q]n.md&lt;/code&gt;, i.e. &lt;code&gt;2024-Q1.md&lt;/code&gt;.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;quarterly-note-overview.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/how-i-use-obsidian-to-journal//blog/how-i-use-obsidian-to-journal/quarterly-note-overview.png&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h4 id=&#34;properties-2&#34;&gt;Properties&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;%*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;moment.locale(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;en-gb&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; thisQuarter &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;window&lt;/span&gt;.moment(tp.file.title, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;YYYY-&amp;amp;Q Q&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; theseMonths &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [...&lt;span style=&#34;color:#ff79c6&#34;&gt;new&lt;/span&gt; Set([&lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt;,&lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;,&lt;span style=&#34;color:#bd93f9&#34;&gt;2&lt;/span&gt;].map(m=&amp;gt;thisQuarter.startOf(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;quarter&amp;#34;&lt;/span&gt;).clone().add(m, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;months&amp;#34;&lt;/span&gt;)))]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;const&lt;/span&gt; thisYear &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; thisQuarter.format(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;YYYY-[Y]&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;-%&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;thisQuarter&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Creates a moment object for the quarter that this note is for&lt;/li&gt;
&lt;li&gt;Similar to the other notes, it parses the title for this&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;theseMonths&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Creates an array of moment objects for all the months in the quarter&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;thisYear&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;The year that this quarter is in&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Out of these, only &lt;code&gt;thisYear&lt;/code&gt; is used in the properties&amp;hellip;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;date&lt;/span&gt;: &amp;lt;% tp.date.now(&amp;#34;YYYY-MM-DD&amp;#34;) %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;createdAt&lt;/span&gt;: &amp;lt;% tp.file.creation_date() %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;yearly&lt;/span&gt;: &amp;lt;% thisYear %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&amp;hellip;and here it is after templating:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;date&lt;/span&gt;: 2024-01-04
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;createdAt&lt;/span&gt;: 2024-01-04 &lt;span style=&#34;color:#bd93f9&#34;&gt;20&lt;/span&gt;:&lt;span style=&#34;color:#bd93f9&#34;&gt;31&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;yearly&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;2024&lt;/span&gt;-&lt;span style=&#34;color:#ff79c6&#34;&gt;Y&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;summary-3&#34;&gt;Summary&lt;/h4&gt;
&lt;p&gt;For bubbling up the monthly summaries into the quarter note, I embed the contents of each month&amp;rsquo;s &lt;code&gt;### Monthly summary&lt;/code&gt; heading into the note - which is done by &lt;a href=&#34;https://help.obsidian.md/Linking+notes+and+files/Internal+links#Link+to+a+heading+in+a+note&#34;&gt;linking to a header in a note&lt;/a&gt;, and then &lt;a href=&#34;https://help.obsidian.md/Linking+notes+and+files/Embedding+files#Embed+a+note+in+another+note&#34;&gt;embedding it&lt;/a&gt;: &lt;code&gt;![[2024-01-M#Monthly summary]]&lt;/code&gt;. This is a core feature of Obsidian and doesn&amp;rsquo;t require the Dataview plugin.&lt;/p&gt;
&lt;p&gt;The reason I chose embedded a note heading is because the monthly summary tends to be a longer prose, whereas the smaller units that make up a month typically are a few sentences and so can be easily captured by a Dataview property.&lt;/p&gt;
&lt;p&gt;Taking a look at how the template works, it loops over each element in &lt;code&gt;theseMonths&lt;/code&gt; and formats the markdown out for each month:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;## Summary
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&#34;text-decoration:underline&#34;&gt;Insert quarterly summary here
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;text-decoration:underline&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;## Months
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;%* theseMonths.forEach(month =&amp;gt; { %&amp;gt;### &amp;lt;% month.format(&amp;#34;MMMM&amp;#34;) %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;![[&amp;lt;% month.format(&amp;#34;YYYY-MM-[M]&amp;#34;) %&amp;gt;#Monthly summary]]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;%* }); %&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And this is how it looks after templating:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;## Summary
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&#34;text-decoration:underline&#34;&gt;Insert quarterly summary here
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;text-decoration:underline&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;## Months
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;### January
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;&lt;/span&gt;![[2024-01-M#Monthly summary]]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;### February
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;&lt;/span&gt;![[2024-02-M#Monthly summary]]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;### March
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;&lt;/span&gt;![[2024-03-M#Monthly summary]]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;All of these monthly summaries can be rather long, so the quarterly summary is written at the top.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This post ended up way more in-depth then I anticipated, and so it was written over several weeks, which might&amp;rsquo;ve caused some inconsistencies in my screenshots, so apologies for that!&lt;/p&gt;
&lt;p&gt;You might be asking where the yearly note gets created (i.e. &lt;code&gt;2024-Y&lt;/code&gt;) - and the truth is that these are not templated yet. I create these ad-hoc whenever I start to write my year review. Even for these, I tend to read through the monthly summaries (given their longer prose) to build together how I think the year went.&lt;/p&gt;
&lt;p&gt;However the main purpose of this post is to share my flow for journalling, and what tools I use to help achieve that. I do have some improvements I wish to make such as moving notes away from their respective flat directories. i.e.:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Daily notes move from &lt;code&gt;planner/daily&lt;/code&gt; to be grouped by year and month: &lt;code&gt;planner/daily/YYYY/MM&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Weekly notes move from &lt;code&gt;planner/weekly&lt;/code&gt; to &lt;code&gt;planner/weekly/YYYY&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Monthly notes move from &lt;code&gt;planner/monthly&lt;/code&gt; to &lt;code&gt;planner/monthly/YYYY&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;UPDATE 2025-03-26&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I wrote a follow-up blog post to group periodic notes into their respective year:
&lt;a href=&#34;https://jdheyburn.co.uk/blog/reorganising-obsidian-journal-notes/&#34;&gt;Reorganising Obsidian Journal Notes&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hopefully I&amp;rsquo;ve given enough of a walkthrough to allow you to come up with a flow of your own! In the future I&amp;rsquo;ll look to document additional functionalities I have in my daily notes such as task management.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Automating service configurations with NixOS</title><enclosure url="https://jdheyburn.co.uk/blog/automating-service-configurations-with-nixos/cover.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/blog/automating-service-configurations-with-nixos/</link>
      <pubDate>Thu, 02 Feb 2023 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/automating-service-configurations-with-nixos/</guid>-->
      <description>&lt;p&gt;In my &lt;a href=&#34;https://jdheyburn.co.uk/blog/converting-to-the-church-of-nix/&#34;&gt;last post&lt;/a&gt; I mentioned that I use a &amp;ldquo;service catalog&amp;rdquo; as a source of truth for what is deployed where and how it should be configured. This catalog is read by a number of services to determine the locations of those services so that can be referred back to.&lt;/p&gt;
&lt;p&gt;NixOS makes it easy to get services up and running with sensible defaults. But once those services are online we need a means of routing traffic to them from a nice domain (e.g. &lt;code&gt;service_name.svc.joannet.casa&lt;/code&gt;), and monitoring against the service to alert when it&amp;rsquo;s down.&lt;/p&gt;
&lt;p&gt;Since NixOS manages the services and their configuration, we can have it create configurations for services that enable connectivity.&lt;/p&gt;
&lt;p&gt;For my use case the tasks it performs are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DNS rewrites to forward DNS names to the node hosting the service
&lt;ul&gt;
&lt;li&gt;I use &lt;a href=&#34;https://github.com/AdguardTeam/AdGuardHome/&#34;&gt;AdGuardHome&lt;/a&gt; as my DNS server, so it generates the config for&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Reverse proxy the service to the port the service runs on the host
&lt;ul&gt;
&lt;li&gt;Each host has a &lt;a href=&#34;https://caddyserver.com/&#34;&gt;Caddy&lt;/a&gt; instance deployed to it, which reverse proxies all the services that run on that host&lt;/li&gt;
&lt;li&gt;Caddy needs to be aware of the domain to forward traffic to, at the particular port&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dashy.to/&#34;&gt;Dashy&lt;/a&gt; for the home dashboard
&lt;ul&gt;
&lt;li&gt;A dashboard which let&amp;rsquo;s you create bookmarks, and more&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Monitoring service endpoints
&lt;ul&gt;
&lt;li&gt;We want to be informed when services go down, so we can automate writing the &lt;a href=&#34;https://github.com/prometheus/blackbox_exporter&#34;&gt;Prometheus blackbox exporter&lt;/a&gt; config - scraped by &lt;a href=&#34;https://victoriametrics.com/&#34;&gt;VictoriaMetrics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Then there is a &lt;a href=&#34;https://grafana.com/&#34;&gt;Grafana&lt;/a&gt; alert which is configured to alert when the probe fails&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When I add a new service to the catalog and deploy the NixOS configs to the nodes, all the above is taken care of for me.&lt;/p&gt;
&lt;h2 id=&#34;catalog-definitions&#34;&gt;Catalog definitions&lt;/h2&gt;
&lt;p&gt;In &lt;a href=&#34;https://github.com/jdheyburn/nixos-configs/blob/5175593745a27de7afc5249bc130a2f1c5edb64c/catalog.nix&#34;&gt;catalog.nix,&lt;/a&gt; there is an attribute called &lt;code&gt;services&lt;/code&gt; which is an attribute set (attrset) of service names to its service definition. The service name is what Caddy and AdGuardHome will assume to be the domain name to forward requests to, although with an option to make this configurable.&lt;/p&gt;
&lt;p&gt;Also in catalog.nix is another attribute &lt;code&gt;nodes&lt;/code&gt; which maps hostnames to node definitions. Service definitions in their current form are dependent on node definitions, so let&amp;rsquo;s dive into the latter first.&lt;/p&gt;
&lt;h3 id=&#34;node-definitions&#34;&gt;Node definitions&lt;/h3&gt;
&lt;p&gt;A node definition has these attributes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ip.private&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;private IP address&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ip.tailscale&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Tailscale IP address&lt;/li&gt;
&lt;li&gt;Not currently used yet, but will look to add functionality when I want to set up connectivity to the services over Tailscale&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;system&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;What architecture the host is on&lt;/li&gt;
&lt;li&gt;Used to determine what flavour of nixpkgs should be used&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isNixOS&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;boolean representing if this is running NixOS, as I have some legacy hosts which are not yet migrated over&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nixosHardware&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;optional; any &lt;a href=&#34;https://github.com/NixOS/nixos-hardware&#34;&gt;nixos-hardware&lt;/a&gt; modules to include into the configuration too&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&amp;rsquo;s see an example node definition for the &lt;code&gt;dee&lt;/code&gt; host.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;nodesBase = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    dee &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      ip&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;private &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;192.168.1.10&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      ip&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;tailscale &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;100.127.189.33&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      system &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aarch64-linux&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      isNixOS &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      nixosHardware &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; nixos-hardware&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;nixosModules&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;raspberry-pi-4;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You&amp;rsquo;ll notice node definitions are defined under &lt;code&gt;nodesBase&lt;/code&gt; , this is because I want &lt;code&gt;nodes&lt;/code&gt; to have the hostname enriched in each node definition at &lt;code&gt;hostName&lt;/code&gt;. We&amp;rsquo;re already defining the hostname as the attribute name, we can follow the &lt;a href=&#34;https://en.wikipedia.org/wiki/Don%27t_repeat_yourself&#34;&gt;Don&amp;rsquo;t Repeat Yourself&lt;/a&gt; (DRY) principle by doing some Nixlang work:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  nodes = &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;builtins&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;listToAttrs (&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;map&lt;/span&gt; (hostName: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; hostName;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    value &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; (nodesBase&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;hostName&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;//&lt;/span&gt; { hostName &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; hostName; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }) (&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;builtins&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;attrNames nodesBase));
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;From my Nixlang cheatsheet, we can [[#Add an attribute to an attrset]] to enrich each base node definition with the &lt;code&gt;hostName&lt;/code&gt; (the attribute name of &lt;code&gt;nodesBase&lt;/code&gt;), and then [[#Converting a list to attributes|convert the returned list to an attrset]]. After this a subset of &lt;code&gt;nodes&lt;/code&gt; would be equal to:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;nodes = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    dee &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      hostName &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;dee&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      ip&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;private &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;192.168.1.10&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      ip&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;tailscale &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;100.127.189.33&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      system &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aarch64-linux&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      isNixOS &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      nixosHardware &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; nixos-hardware&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;nixosModules&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;raspberry-pi-4;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#6272a4&#34;&gt;# ... remaining nodes here&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;service-definitions&#34;&gt;Service definitions&lt;/h3&gt;
&lt;p&gt;The service definition accepts these attributes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;host&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;The node that runs this service&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;port&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;The port this service runs at&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;blackbox.name&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;The display name to use for this service for blackbox&lt;/li&gt;
&lt;li&gt;For when the domain name doesn&amp;rsquo;t map to the service name
&lt;ul&gt;
&lt;li&gt;i.e. the Dashy service is reachable at &lt;code&gt;home.svc.joannet.casa&lt;/code&gt;, but we want blackbox to announce the service as &lt;code&gt;dashy&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;blackbox.path&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Blackbox by default will take the root domain name as the health check endpoint. If that should not the case then you can add a suffix path here&lt;/li&gt;
&lt;li&gt;e.g. &lt;code&gt;/health&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;caddify.enable&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Whether Caddy which runs on the same host should reverse proxy to this service at the port defined&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;caddify.skip_tls_verify&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Whether Caddy should ignore TLS verification when forwarding traffic to this service&lt;/li&gt;
&lt;li&gt;Usually for when the backend service defaults to HTTPS, and I cba to set up a certificate trust&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;caddify.forwardTo&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Define a node here different to the host to have that node set up reverse proxy instead&lt;/li&gt;
&lt;li&gt;Currently I&amp;rsquo;m using this to reverse proxy for services where nodes do not have Caddy on them (i.e. non-NixOS nodes)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;caddify.paths&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;A list of paths, additional path forwarding to ports that&lt;/li&gt;
&lt;li&gt;Used this for testing path forwarding for minio console, but reverted as it didn&amp;rsquo;t play nice - I&amp;rsquo;m not using this for anything right now&lt;/li&gt;
&lt;li&gt;&lt;code&gt;path&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;The URL path to forward (e.g. &lt;code&gt;/ui/*&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;port&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;The port to forward to&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dashy.section&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;What section in Dashy it should fall under&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dashy.description&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;The description to use in Dashy&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dashy.icon&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;The icon to display in Dashy&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An example service definition would look like this.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;servicesBase = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    adguard &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      host &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; nodes&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;dee;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      port &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;3000&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      caddify&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;enable &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      dashy&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;section &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;networks&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      dashy&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;description &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;DNS resolver&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      dashy&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;icon &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;hl-adguardhome&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So this will set for us the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DNS entry in AdGuardHome for &lt;code&gt;adguard.svc.joannet.casa&lt;/code&gt;, which creates an A record to the private IP address of host &lt;code&gt;dee&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Configures Caddy on &lt;code&gt;dee&lt;/code&gt; to reverse proxy traffic received on &lt;code&gt;adguard.svc.joannet.casa&lt;/code&gt; and forward it to &lt;code&gt;127.0.0.1:3000&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Because I&amp;rsquo;m using Caddy, I get &lt;a href=&#34;https://caddyserver.com/docs/automatic-https&#34;&gt;HTTPS for free&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Creates an entry in Dashy, in the section &lt;code&gt;networks&lt;/code&gt; with the provided description and gives it a pretty icon&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additionally, the AdGuardHome module will use the port configured at the port to use when starting the service, meaning that I don&amp;rsquo;t duplicate the port (DRY principle again).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;adguardhome = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  enable &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  settings &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    bind_port &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; catalog&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;services&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;adguard&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;port;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#6272a4&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Similarly to &lt;code&gt;nodes&lt;/code&gt;, I add the service name into the definition by duplicating &lt;code&gt;servicesBase&lt;/code&gt; to &lt;code&gt;services&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services = &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;builtins&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;listToAttrs (&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;map&lt;/span&gt; (serviceName: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; serviceName;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  value &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; (servicesBase&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;serviceName&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;//&lt;/span&gt; { name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; serviceName; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}) (&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;builtins&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;attrNames servicesBase));
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;reading-catalog-definitions&#34;&gt;Reading catalog definitions&lt;/h2&gt;
&lt;p&gt;To generate the configs for each service, we have to parse the contents of catalog.nix using Nixlang (have you seen &lt;a href=&#34;https://jdheyburn.co.uk/blog/nix-cheat-sheet/&#34;&gt;the cheat sheet&lt;/a&gt;?).&lt;/p&gt;
&lt;h3 id=&#34;dns-rewrites&#34;&gt;DNS rewrites&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;tl;dr:&lt;/strong&gt; &lt;a href=&#34;https://github.com/jdheyburn/nixos-configs/blob/5175593745a27de7afc5249bc130a2f1c5edb64c/modules/dns/default.nix&#34;&gt;See here&lt;/a&gt; for the configuration in GitHub.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;AdGuard can respond to DNS requests by answering an IP address for it. The configuration for this is structured like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;dns&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;rewrites&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    - &lt;span style=&#34;color:#ff79c6&#34;&gt;domain&lt;/span&gt;: service.DOMAIN
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;answer&lt;/span&gt;: IP_ADDRESS
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can use Nix to build this configuration for us.&lt;/p&gt;
&lt;p&gt;In my &lt;a href=&#34;https://github.com/jdheyburn/nixos-configs/blob/5175593745a27de7afc5249bc130a2f1c5edb64c/modules/dns/default.nix&#34;&gt;module for DNS&lt;/a&gt;, we start by retrieving all services that should be behind Caddy using filterAttrs, and then converting to a list by passing that output to attrValues.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#6272a4&#34;&gt;# Get services which are being served by caddy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  caddy_services = attrValues (filterAttrs (svc_name: svc_def:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    svc_def &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;caddify&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; svc_def&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;caddify &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;enable&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; svc_def&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;caddify&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;enable)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    catalog&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;services);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;filterAttrs takes two arguments, a lambda to filter the attributes by, and the attrset to filter.&lt;/p&gt;
&lt;p&gt;Taking what we &lt;a href=&#34;https://jdheyburn.co.uk/blog/nix-cheat-sheet/#functions-and-lambdas&#34;&gt;learnt about lambdas&lt;/a&gt;, we know that the filtering lambda takes two parameters (&lt;code&gt;svc_name&lt;/code&gt; and &lt;code&gt;svc_def&lt;/code&gt;), and evaluates the body of &lt;code&gt;svc_def ? &amp;quot;caddify&amp;quot; &amp;amp;&amp;amp; svc_def.caddify ? &amp;quot;enable&amp;quot; &amp;amp;&amp;amp; svc_def.caddify.enable&lt;/code&gt;. Since the &lt;code&gt;caddify&lt;/code&gt; field in the service definition is optional, we can use &lt;a href=&#34;https://jdheyburn.co.uk/blog/nix-cheat-sheet/#check-if-attribute-is-in-attrset-shorthand&#34;&gt;shorthand&lt;/a&gt; to determine if the field is present in the attribute (&lt;code&gt;svc_def ? &amp;quot;caddify&amp;quot;&lt;/code&gt; and &lt;code&gt;svc_def.caddify ? &amp;quot;enable&amp;quot;&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;When learning new languages I like to make comparisons to other languages so that we can see what&amp;rsquo;s happening - the equivalent for above would look like this in Python.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;caddy_services &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; svc_name, svc_def &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; catalog_services&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;items():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;caddify&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; svc_def \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;and&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;enable&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; svc_def[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;caddify&amp;#34;&lt;/span&gt;] \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;and&lt;/span&gt; svc_def[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;caddify&amp;#34;&lt;/span&gt;][&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;enable&amp;#34;&lt;/span&gt;]:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    caddy_services[svc_name] &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; svc_def
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# For any Python comprehension fans&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;caddy_services &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  svc_name: svc_def
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; svc_name, svc_def &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; catalog_services&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;items()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;caddify&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; svc_def
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;and&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;enable&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; svc_def[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;caddify&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;and&lt;/span&gt; svc_def[&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;caddify&amp;#34;&lt;/span&gt;][&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;enable&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;filterAttrs&lt;/code&gt; returns us an attrset, whereas we&amp;rsquo;d like a list so that we can map over each element. We can call &lt;code&gt;attrValues&lt;/code&gt; to retrieve the list of values that make up this attrset. It the same as if we were to call &lt;code&gt;caddy_services.values()&lt;/code&gt; in Python.&lt;/p&gt;
&lt;p&gt;We now need to define a function which will help us to determine the IP address that the domain should be forwarded to. The service definition contains a &lt;code&gt;host&lt;/code&gt; attribute which is set to a node definition, however there are some services at home which are not running NixOS and so won&amp;rsquo;t have Caddy.&lt;/p&gt;
&lt;p&gt;For these services they should be routed to a host which does have Caddy, and this is what the &lt;code&gt;caddify.forwardTo&lt;/code&gt; is for. We can write a lambda that will return the correct node definition if this attribute exists in the service definition:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;getCaddyDestination = service:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; service&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;caddify &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;forwardTo&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    service&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;caddify&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;forwardTo
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    service&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;host;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This function is used when we map over &lt;code&gt;caddy_services&lt;/code&gt; to generate the attrset containing &lt;code&gt;domain&lt;/code&gt; and &lt;code&gt;answer&lt;/code&gt; - the format for AdGuard to perform DNS rewrites. As &lt;code&gt;getCaddyDestination&lt;/code&gt; returns us a node definition, we can traverse it to retrieve the private IP address.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;rewrites = &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;map&lt;/span&gt; (service: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  domain &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;service&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;.svc.joannet.casa&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  answer &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; (getCaddyDestination service)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;ip&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;private;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}) caddy_services;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The last thing we need to do is refer to &lt;code&gt;rewrites&lt;/code&gt; when we &lt;a href=&#34;https://github.com/jdheyburn/nixos-configs/blob/5175593745a27de7afc5249bc130a2f1c5edb64c/modules/dns/default.nix#L65&#34;&gt;write the config&lt;/a&gt; for AdGuard.&lt;/p&gt;
&lt;h3 id=&#34;caddy-config&#34;&gt;Caddy config&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;tl;dr:&lt;/strong&gt; &lt;a href=&#34;https://github.com/jdheyburn/nixos-configs/blob/5175593745a27de7afc5249bc130a2f1c5edb64c/modules/caddy/default.nix&#34;&gt;See here&lt;/a&gt; for the configuration in GitHub.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Once DNS is configured, any DNS requests for &lt;code&gt;service.svc.joannet.casa&lt;/code&gt; will be answered with the IP address of the reverse proxy (Caddy) that can serve that request, so let&amp;rsquo;s configure Caddy on how to handle that request.&lt;/p&gt;
&lt;p&gt;My setup is probably more complex than what&amp;rsquo;s needed, since Caddy requires config for services &lt;em&gt;running on the same host&lt;/em&gt;, as well as any additional &lt;em&gt;services it should forward to other hosts&lt;/em&gt;. For the sake of brevity I&amp;rsquo;ll focus on the former, since the latter is done in a similar manner (look for &lt;code&gt;forward_services&lt;/code&gt; and &lt;code&gt;forward_routes&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;I use the &lt;a href=&#34;https://caddyserver.com/docs/json/&#34;&gt;JSON structure&lt;/a&gt; to configure Caddy, since I&amp;rsquo;m generating these programatically. The config that we want generated for the services is in this format:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;apps&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;http&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;servers&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;srv0&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;listen&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;:443&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;routes&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;match&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;host&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;service.svc.joannet.casa&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              ],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;terminal&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;handle&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;handler&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;subroute&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;routes&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;handle&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                          &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;handler&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;reverse_proxy&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                          &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;upstreams&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                              &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;dial&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;localhost:PORT&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                          ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                      ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                  ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#6272a4&#34;&gt;// ... other services here
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;          ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;tls&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;automation&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;policies&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;subjects&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;service.svc.joannet.casa&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#6272a4&#34;&gt;// ... other services here
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;          ],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;issuers&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#6272a4&#34;&gt;// ... removed for brevity
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;          ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;That&amp;rsquo;s a lot of nesting - no wonder why they encourage using a &lt;a href=&#34;https://caddyserver.com/docs/caddyfile&#34;&gt;Caddyfile&lt;/a&gt; instead - infact &lt;a href=&#34;https://github.com/jdheyburn/nixos-configs/blob/2962a4f61c124289cc05ea4398d5cd15adc4b191/modules/caddy/Caddyfile&#34;&gt;here is the Caddyfile&lt;/a&gt; I used before I migrated to this.&lt;/p&gt;
&lt;p&gt;I could template the config for that instead, however I wanted to play around with learning how to manipulate attrsets, since this is how the majority of options are configured in Nix.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For each service, we need to create a route:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;match&amp;#34;&lt;/span&gt;: [{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;host&amp;#34;&lt;/span&gt;: [ &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;service.svc.joannet.casa&amp;#34;&lt;/span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;terminal&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;handle&amp;#34;&lt;/span&gt;: [{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;handler&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;subroute&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;routes&amp;#34;&lt;/span&gt;: [{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;handle&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#6272a4&#34;&gt;// ... insert route handler
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;      ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;route-functions&#34;&gt;Route functions&lt;/h4&gt;
&lt;p&gt;Translating this to a function yields this result.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;route = { name&lt;span style=&#34;color:#ff79c6&#34;&gt;,&lt;/span&gt; port&lt;span style=&#34;color:#ff79c6&#34;&gt;,&lt;/span&gt; upstream &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;localhost&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;,&lt;/span&gt; skip_tls_verify &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; false
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;,&lt;/span&gt; paths &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; [ ] }:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;let&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    subroutes &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;map&lt;/span&gt; (service: routeHandler service) (paths &lt;span style=&#34;color:#ff79c6&#34;&gt;++&lt;/span&gt; [{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      port &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; port;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      upstream &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; upstream;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      skip_tls_verify &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; skip_tls_verify;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    match &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [{ host &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [ &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;name&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;.svc.joannet.casa&amp;#34;&lt;/span&gt; ]; }];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    terminal &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    handle &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      handler &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;subroute&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      routes &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; subroutes;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;route&lt;/code&gt; becomes a function which takes in an attrset with these parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;port&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;the service port to reverse proxy the request to&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;upstream&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;IP address to forward the request to, defaults to &lt;code&gt;localhost&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;skip_tls_verify&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;whether to ignore TLS certificate verification, defaults to &lt;code&gt;false&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;path&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;whether any query paths should be used instead of the domain
&lt;ul&gt;
&lt;li&gt;i.e. forward to &lt;code&gt;/path&lt;/code&gt; instead of &lt;code&gt;/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After then within the &lt;code&gt;let&lt;/code&gt; block we can create some local variables. In &lt;code&gt;subroutes&lt;/code&gt; we&amp;rsquo;re mapping over the parameters and creating a &lt;code&gt;routeHandler&lt;/code&gt; from them (shown below). Since any defined path forwarding must take precedence, it is prepended to the base path handler.&lt;/p&gt;
&lt;p&gt;Then in the return block we return a route entry for this service. Within &lt;code&gt;handle[0].routes&lt;/code&gt; we need a &amp;ldquo;route handler&amp;rdquo;, this can take different formats depending on whether you want to disable verification of TLS connections to the upstream service:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;handler&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;reverse_proxy&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;upstreams&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;dial&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;localhost:PORT&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#6272a4&#34;&gt;// optional below, to disable TLS verification
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;transport&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;protocol&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;http&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;tls&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;insecure_skip_verify&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can write a function that can output this for us:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;routeHandler =
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  { port&lt;span style=&#34;color:#ff79c6&#34;&gt;,&lt;/span&gt; upstream &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;localhost&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;,&lt;/span&gt; skip_tls_verify &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; false&lt;span style=&#34;color:#ff79c6&#34;&gt;,&lt;/span&gt; path &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; [ ] }:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;let&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    base_handle &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      handler &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;reverse_proxy&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      upstreams &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [{ dial &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;upstream&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;toString&lt;/span&gt; port&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;; }];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    handle &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; base_handle &lt;span style=&#34;color:#ff79c6&#34;&gt;//&lt;/span&gt; optionalAttrs (skip_tls_verify) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      transport &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        protocol &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;http&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        tls&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;insecure_skip_verify &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    handle &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [ handle ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  } &lt;span style=&#34;color:#ff79c6&#34;&gt;//&lt;/span&gt; optionalAttrs (length path &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt;) { match &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [{ path &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; path; }]; };
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;routeHandler&lt;/code&gt; function this takes the same parameters as the &lt;code&gt;route&lt;/code&gt; function. We know that each route handler needs at least a &lt;code&gt;handler&lt;/code&gt; and &lt;code&gt;upstreams&lt;/code&gt;, so let&amp;rsquo;s define them in the local variable &lt;code&gt;base_handle&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then if &lt;code&gt;skip_tls_verify&lt;/code&gt; is true, we want to append on the &lt;code&gt;transport&lt;/code&gt; block - this is making use of the &lt;a href=&#34;https://jdheyburn.co.uk/blog/nix-cheat-sheet/#merge-two-attrsets-shorthand&#34;&gt;merge attrset shorthand&lt;/a&gt; &lt;code&gt;//&lt;/code&gt; to do this.&lt;/p&gt;
&lt;p&gt;In the return block, we&amp;rsquo;re returning a attrset with the newly created route handler. Optionally if any additional paths were defined then add a match block to them too.&lt;/p&gt;
&lt;h4 id=&#34;constructing-caddy-config&#34;&gt;Constructing Caddy config&lt;/h4&gt;
&lt;p&gt;To start, we need to pull the services from catalog that are being hosted on this node, then convert it to a list.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;host_services = attrValues (filterAttrs (svc_name: svc_def:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  svc_def &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;host&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; svc_def&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;host&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;hostName &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; config&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;networking&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;hostName
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; svc_def&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;caddify&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;enable) catalog&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;services);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For each service we want to generate a Caddy route for it, using our &lt;code&gt;route&lt;/code&gt; function from earlier.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;catalog_routes = &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;map&lt;/span&gt; (service:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  route {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; service&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    port &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; service&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;port;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    skip_tls_verify &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; service&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;caddify &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;skip_tls_verify&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; service&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;caddify&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;skip_tls_verify;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    paths &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; optionals (service&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;caddify &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;paths&amp;#34;&lt;/span&gt;) service&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;caddify&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;paths;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }) host_services;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then the last thing we need is the list of the domains that can be used for &lt;code&gt;tls.automation.policies[0].subjects&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;subject_names = &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;map&lt;/span&gt; (service: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;service&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;.svc.joannet.casa&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (host_services);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we can declare these variables in the Caddy service options:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;caddy = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#6272a4&#34;&gt;# ... removed for brevity&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  configFile &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; pkgs&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;writeText &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Caddyfile&amp;#34;&lt;/span&gt; (&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;builtins&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;toJSON {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    logging&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;logs&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;default&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;level &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ERROR&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    apps &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      http&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;servers&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;srv0 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        listen &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [ &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;:443&amp;#34;&lt;/span&gt; ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        routes &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; catalog_routes;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      tls&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;automation&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;policies &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        subjects &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; subject_names;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        issuers &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            module &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;acme&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ca &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;https://acme-v02.api.letsencrypt.org/directory&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            challenges&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;dns&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;provider &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;cloudflare&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              api_token &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{env.CLOUDFLARE_API_TOKEN}&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            module &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;zerossl&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ca &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;https://acme-v02.api.letsencrypt.org/directory&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            challenges&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;dns&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;provider &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;cloudflare&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              api_token &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{env.CLOUDFLARE_API_TOKEN}&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;I&amp;rsquo;m using &lt;a href=&#34;https://github.com/ryantm/agenix&#34;&gt;agenix&lt;/a&gt; to expose my Cloudflare token in Caddys environment variables.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;After this is deployed, we will now be able to access services at easy to remember domain names, forwarded to the host on the network.&lt;/p&gt;
&lt;h3 id=&#34;monitoring&#34;&gt;Monitoring&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;tl;dr:&lt;/strong&gt; &lt;a href=&#34;https://github.com/jdheyburn/nixos-configs/blob/5175593745a27de7afc5249bc130a2f1c5edb64c/modules/prometheus-stack/scrape-configs.nix&#34;&gt;See here&lt;/a&gt; for the configuration in GitHub.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To monitor the services I&amp;rsquo;m using the &lt;a href=&#34;https://github.com/prometheus/blackbox_exporter&#34;&gt;Prometheus blackbox exporter&lt;/a&gt; - a big thanks to maxanderson&amp;rsquo;s &lt;a href=&#34;https://github.com/maxandersen/internet-monitoring/blob/master/prometheus/prometheus.yml&#34;&gt;internetmonitoring&lt;/a&gt; for the inspiration.&lt;/p&gt;
&lt;p&gt;Blackbox has a &lt;a href=&#34;https://github.com/prometheus/blackbox_exporter/tree/master/prober&#34;&gt;multitude of probes&lt;/a&gt; you can use - for our use case we want to use the http probe, which returns the HTTP response code, probe duration, and also returns info on TLS certificates.&lt;/p&gt;
&lt;h4 id=&#34;blackbox-configuration&#34;&gt;Blackbox configuration&lt;/h4&gt;
&lt;p&gt;Given we can build a list of services, we want to write configuration that blackbox can parse to probe these. We can pass a target list of the format:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# format&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;url;human_name;routing
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# examples&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;https://service_name.svc.joannet.casa;service_name;internal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;https://grafana.svc.joannet.casa;grafana;internal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;https://loki.svc.joannet.casa/ready;loki;internal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;https://bbc.co.uk;bbc.co.uk;external
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;https://jdheyburn.co.uk;jdheyburn.co.uk;external
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;url&lt;/code&gt; is the endpoint that the probe should hit, followed by a &lt;code&gt;human_name&lt;/code&gt; which allows us to make it easily identifiable when querying/alerting, and lastly a &lt;code&gt;routing&lt;/code&gt; which can be one of internal or external - which we can use to filter metrics on later.&lt;/p&gt;
&lt;p&gt;We can then use Prometheus&amp;rsquo;s relabel configs to parse these and map them to labels in the probe. So let&amp;rsquo;s get building by first building the list of services as we&amp;rsquo;ve done so previously.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;caddified_services = attrValues (filterAttrs
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  (svc_name: svc_def: svc_def &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;caddify&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; svc_def&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;caddify&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;enable)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  catalog&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;services);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And now map these services into the desired format of &lt;code&gt;url;human_name;routing&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;internal_https_targets = &lt;span style=&#34;color:#ff79c6&#34;&gt;let&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  getPath &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; service:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    optionalString (service &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;blackbox&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; service&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;blackbox &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;path&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    service&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;blackbox&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;path;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  getHumanName &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; service:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; service &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;blackbox&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; service&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;blackbox &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;name&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      service&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;blackbox&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      service&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;map&lt;/span&gt; (service:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;https://&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;service&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;.svc.joannet.casa&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;getPath service&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    getHumanName service
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;;internal&amp;#34;&lt;/span&gt;) caddified_services;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;let&lt;/code&gt; blocks can be used in Nix to define local variables when assigning statements (I&amp;rsquo;ve used them previously in functions). I&amp;rsquo;m defining two functions in this block, &lt;code&gt;getPath&lt;/code&gt; and &lt;code&gt;getHumanName&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;getPath&lt;/code&gt; checks to see if there is a health check path to append to the service URL, because the health check endpoint may not necessarily be at the root path (&lt;code&gt;/&lt;/code&gt;) as we see for the case of &lt;a href=&#34;https://github.com/jdheyburn/nixos-configs/blob/5175593745a27de7afc5249bc130a2f1c5edb64c/catalog.nix#L94&#34;&gt;Loki&lt;/a&gt; (&lt;code&gt;/ready&lt;/code&gt;). So we perform a look up to see if a path is defined in the service definition, else we don&amp;rsquo;t append one (&lt;code&gt;optionalString&lt;/code&gt; will return an empty string (&lt;code&gt;&amp;quot;&amp;quot;&lt;/code&gt;) if the condition is false).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;getHumanName&lt;/code&gt; checks to see if there&amp;rsquo;s a human name we should override with, else the default service name is used. This is useful where the domain name doesn&amp;rsquo;t necessarily map to the service that underlines it, such as &lt;code&gt;home.svc.joannet.casa&lt;/code&gt; is the service &lt;code&gt;dashy&lt;/code&gt; - &lt;a href=&#34;https://github.com/jdheyburn/nixos-configs/blob/5175593745a27de7afc5249bc130a2f1c5edb64c/catalog.nix#L70&#34;&gt;this override&lt;/a&gt; prevents the human name being set as &lt;code&gt;home&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Blackbox can be used to monitor any endpoint. It can be useful to have it monitor endpoints external to my local services so that I can ensure my Internet is connected. Let&amp;rsquo;s create that list and merge it with our internal targets.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;external_targets = &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;map&lt;/span&gt; (url: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;https://&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;url&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;url&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;;external&amp;#34;&lt;/span&gt;) [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;bbc.co.uk&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;github.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;google.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;jdheyburn.co.uk&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# concatenate the two together&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;blackbox&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;https_targets = external_targets &lt;span style=&#34;color:#ff79c6&#34;&gt;++&lt;/span&gt; internal_https_targets;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we need to define some relabelling so that blackbox knows how to parse it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;blackbox&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;relabel_configs = [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    source_labels &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [ &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;__address__&amp;#34;&lt;/span&gt; ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    regex &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;(.*);(.*);(.*)&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#6272a4&#34;&gt;# first is the url, thus unique for instance&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    target_label &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;instance&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    replacement &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;$1&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    source_labels &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [ &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;__address__&amp;#34;&lt;/span&gt; ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    regex &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;(.*);(.*);(.*)&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#6272a4&#34;&gt;# second is humanname to use in charts&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    target_label &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;humanname&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    replacement &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;$2&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    source_labels &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [ &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;__address__&amp;#34;&lt;/span&gt; ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    regex &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;(.*);(.*);(.*)&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#6272a4&#34;&gt;# third state whether this is testing external or internal network&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    target_label &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;routing&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    replacement &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;$3&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    source_labels &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [ &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;instance&amp;#34;&lt;/span&gt; ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    target_label &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;__param_target&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    target_label &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;__address__&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    replacement &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;127.0.0.1:&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;toString&lt;/span&gt; catalog&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;services&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;blackboxExporter&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;port&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The first three configs are using regex to parse the format of &lt;code&gt;url;human_name;routing&lt;/code&gt; to map them to labels. We then take the newly created &lt;code&gt;instance&lt;/code&gt; label and map it to &lt;code&gt;__param_target&lt;/code&gt;, which is the endpoint that blackbox will probe against. Lastly we define the exporter address that Prometheus should scrape at, which is the local blackbox instance running at the defined port.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll now need to add this config to the Prometheus scrape configs:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    job_name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;blackbox-https&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    metrics_path &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;/probe&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    params&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;module &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [ &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;http_2xx&amp;#34;&lt;/span&gt; ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    static_configs &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [{ targets &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; blackbox&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;https_targets; }];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    relabel_configs &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; blackbox&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;relabel_configs;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So now that we have the scrape config that our scraper can use (i.e. VictoriaMetrics), we&amp;rsquo;ll need to boot up the blackbox exporter so that there&amp;rsquo;s something to scrape against.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;services&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;prometheus&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;exporters&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;blackbox = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  enable &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  port &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; catalog&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;services&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;blackboxExporter&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;port;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  configFile &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; pkgs&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;writeText &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;blackbox.json&amp;#34;&lt;/span&gt; (&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;builtins&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;toJSON {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    modules&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;http_2xx &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      prober &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;http&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      timeout &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;5s&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      http&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;fail_if_not_ssl &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      http&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;preferred_ip_protocol &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ip4&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      http&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;valid_status_codes &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [ &lt;span style=&#34;color:#bd93f9&#34;&gt;200&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;401&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;403&lt;/span&gt; ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;visualising-and-alerting&#34;&gt;Visualising and alerting&lt;/h4&gt;
&lt;p&gt;It&amp;rsquo;s all well and good having VictoriaMetrics scrape and poll, but let&amp;rsquo;s use Grafana to visualise all this. I think I sourced it from &lt;a href=&#34;https://grafana.com/grafana/dashboards/14928-prometheus-blackbox-exporter/&#34;&gt;this dashboard&lt;/a&gt;, in either case I&amp;rsquo;d recommend to use it!&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;blackbox-dashboard.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automating-service-configurations-with-nixos//blog/automating-service-configurations-with-nixos/blackbox-dashboard.png&#34;
    alt=&#34;A Grafana dashboard showing the data points of the healthchecks against various internal and external websites&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;You&amp;rsquo;ll notice that some services are responding with 4XX codes, this is because the probes are not being authenticated - but I&amp;rsquo;m getting &lt;em&gt;some&lt;/em&gt; response from the service which shows that something is working. These 4XX codes don&amp;rsquo;t cause a probe to fail, which is down to how I configured the blackbox exporter in the previous section: &lt;code&gt;http.valid_status_codes = [ 200 401 403 ];&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I also have an alert set up in Grafana to alert on the metric &lt;code&gt;probe_success&lt;/code&gt;. This metric will report 1 when it was successful, else 0. Given that I want to be alerted when a service has gone down for 5 minutes, I can give the alert a metric query of &lt;code&gt;max by(humanname) (probe_success{routing=&amp;quot;internal&amp;quot;})&lt;/code&gt;, which will produce a unique metric for each &lt;code&gt;humanname&lt;/code&gt; (i.e. service). This is assigned to the variable A.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I&amp;rsquo;m only interested in alerting on internally routed services, external is out of my control.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;create-alert-1.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automating-service-configurations-with-nixos//blog/automating-service-configurations-with-nixos/create-alert-1.png&#34;
    alt=&#34;Grafana create alert page, entering the metric that we want to be alerting on, followed by the expressions to be made against the metric result&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Expression B is then reducing the metric output to a single value, which will be the maximum value for that period.&lt;/p&gt;
&lt;p&gt;Lastly expression C checks if B is less than 1, which is what will be produced if the probe failed.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;create-alert-2.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automating-service-configurations-with-nixos//blog/automating-service-configurations-with-nixos/create-alert-2.png&#34;
    alt=&#34;Grafana create alert page, defining how the alert should be evaluated and what details to accompany with it&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Next up I&amp;rsquo;m setting how often the alert should poll, and some additional details on the alert, which can be used in the body to link to the blackbox dashboard for diagnosing.&lt;/p&gt;
&lt;p&gt;By default all alerts go to my root notification policy, which is to send me an email. I&amp;rsquo;ve got SMTP set up on my Grafana instance, but I&amp;rsquo;ll dive into that another time. In the meantime, here&amp;rsquo;s a screenshot of an email alert!&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;example-grafana-email-alert.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automating-service-configurations-with-nixos//blog/automating-service-configurations-with-nixos/example-grafana-email-alert.png&#34;
    alt=&#34;An example Grafana email alert, it reports that two services are down&#34; width=&#34;400x&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;dashy&#34;&gt;Dashy&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;tl;dr:&lt;/strong&gt; &lt;a href=&#34;https://github.com/jdheyburn/nixos-configs/blob/5175593745a27de7afc5249bc130a2f1c5edb64c/modules/dashy/default.nix&#34;&gt;See here&lt;/a&gt; for the configuration in GitHub.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://dashy.to/&#34;&gt;Dashy&lt;/a&gt; is a customisable dashboard that can act as a homepage for your web browser to help you navigate to services, bookmarks, and more. I&amp;rsquo;m using it to keep a visual track on the services that I&amp;rsquo;m running at home.&lt;/p&gt;
&lt;p&gt;The desired end result looks like this:&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;dashy-homepage.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automating-service-configurations-with-nixos//blog/automating-service-configurations-with-nixos/dashy-homepage.png&#34;
    alt=&#34;Dashy homepage with columns for each type of service that is being hosted, followed by those such services&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;The config for Dashy looks like the below, so let&amp;rsquo;s get Nix to build it for us!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;appConfig&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#6272a4&#34;&gt;# ... removed for brevity&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;pageInfo&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;title&lt;/span&gt;: Joannet
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;navLinks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    - &lt;span style=&#34;color:#ff79c6&#34;&gt;path&lt;/span&gt;: https://dashy.to/docs
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;title&lt;/span&gt;: Dashy Documentation
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;sections&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Media
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;icon&lt;/span&gt;: fas fa-play-circle
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;items&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;title&lt;/span&gt;: plex
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: Watch TV and movies
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;icon&lt;/span&gt;: hl-plex
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;url&lt;/span&gt;: https://plex.svc.joannet.casa
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -  &lt;span style=&#34;color:#6272a4&#34;&gt;# additional sections here&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;building-dashy-config&#34;&gt;Building Dashy config&lt;/h4&gt;
&lt;p&gt;We start off with a baseline of sections we want Dashy to look for, since a section requires an icon (the image to the left of the section name in the above screenshot).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sections = [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Media&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    icon &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;fas fa-play-circle&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Monitoring&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    icon &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;fas fa-heartbeat&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Networks&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    icon &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;fas fa-network-wired&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Storage&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    icon &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;fas fa-database&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Virtualisation&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    icon &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;fas fa-cloud&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And in what might come across as &lt;a href=&#34;https://knowyourmeme.com/memes/how-to-draw-an-owl&#34;&gt;draw the rest of the fucking owl&lt;/a&gt;, we build the sections list&amp;hellip;!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sectionServices = &lt;span style=&#34;color:#ff79c6&#34;&gt;let&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  isDashyService &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; section_name: svc_def:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    svc_def &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;dashy&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; svc_def&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;dashy &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;section&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; svc_def&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;dashy&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;section
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; section_name;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  createSectionItems &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; services:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;map&lt;/span&gt; (service: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      title &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; service&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      description &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; service&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;dashy&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;description;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      url &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;https://&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;service&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;.svc.joannet.casa&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      icon &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; service&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;dashy&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;icon;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }) services;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  sectionItems &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; sectionName:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    createSectionItems (attrValues (filterAttrs
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      (svc_name: svc_def: isDashyService (toLower sectionName) svc_def)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      catalog&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;services));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;map&lt;/span&gt; (section: section &lt;span style=&#34;color:#ff79c6&#34;&gt;//&lt;/span&gt; { items &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; sectionItems section&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;name; }) sections;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s expand on this. Three functions are being defined in this let block:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;isDashyService&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Returns true we should include this service in the current iterated section&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jdheyburn.co.uk/blog/automating-service-configurations-with-nixos/#service-definitions&#34;&gt;Service definitions&lt;/a&gt; opt-in to what Dashy section they belong to&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;createSectionItems&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;For a given list of services, create the item definition for that service as required by Dashy&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sectionItems&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;For the current iterated section:
&lt;ul&gt;
&lt;li&gt;filter on services to be added to the section&lt;/li&gt;
&lt;li&gt;convert that to a list&lt;/li&gt;
&lt;li&gt;create section items from it&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then for each element in &lt;code&gt;sections&lt;/code&gt;, enrich it with an &lt;code&gt;items&lt;/code&gt; attribute with the output of &lt;code&gt;sectionItems&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;This &lt;code&gt;sectionServices&lt;/code&gt; variable is then added to &lt;code&gt;dashyConfig&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dashyConfig = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  pageInfo &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    title &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Joannet&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    navLinks &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      title &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Dashy Documentation&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      path &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;https://dashy.to/docs&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  appConfig &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    theme &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;nord-frost&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    iconSize &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;large&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    layout &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;vertical&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    preventWriteToDisk &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    preventLocalSave &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    disableConfiguration &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; false;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    hideComponents &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      hideSettings &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      hideFooter &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  sections &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; sectionServices;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We&amp;rsquo;ll then need to convert it to a YAML file. This was a bit tricky to set up, but I sought inspiration from this &lt;a href=&#34;https://github.com/NixOS/nixpkgs/blob/17b0cf40e3ce85207d180d792cddc4a37125db36/nixos/modules/services/home-automation/home-assistant.nix#L15-L18&#34;&gt;code block&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;format = pkgs&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;formats&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;yaml { };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;configFile =
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  pkgs&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;runCommand &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;dashy-configuration.yaml&amp;#34;&lt;/span&gt; { preferLocalBuild &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; true; } &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;    cp &lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;format&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;generate &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;dashy-configuration.yaml&amp;#34;&lt;/span&gt; dashyConfig&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt; $out
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;    sed -i -e &amp;#34;s/&amp;#39;\!\([a-z_]\+\) \(.*\)&amp;#39;/\!\1 \2/;s/^\!\!/\!/;&amp;#34; $out
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;  &amp;#39;&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then &lt;code&gt;configFile&lt;/code&gt; is exposed to the container where the app is running. You can also see below that the port from catalog is used here to expose the service.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;virtualisation&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;oci-containers&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;containers&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;dashy = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  image &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;lissy93/dashy:&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;version&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  volumes &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [ &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;configFile&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;:/app/public/conf.yml&amp;#34;&lt;/span&gt; ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ports &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [ &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;toString&lt;/span&gt; catalog&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;services&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;home&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;port&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;:80&amp;#34;&lt;/span&gt; ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;deployment-configurations&#34;&gt;Deployment configurations&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;This next section talks about some more advanced features of Nix, of which introducing them is out of scope for this blog post given its length. I&amp;rsquo;ll discuss how the catalog is used here and link back a more in-depth blog when it is published.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It&amp;rsquo;s not just service configurations that use the catalog too - I use &lt;a href=&#34;https://github.com/serokell/deploy-rs&#34;&gt;deploy-rs&lt;/a&gt; to deploy these configurations to NixOS nodes, reading from the node definitions in catalog. Given that various services are interdependent on each other across varying nodes, deploy-rs allows me to deploy all configurations at the same time from one command.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# all hosts&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;nix run github:serokell/deploy-rs -- -s &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# per host&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;nix run github:serokell/deploy-rs -- -s &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;.#dennis&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;deploy-rs requires you to enable &lt;a href=&#34;https://www.tweag.io/blog/2020-05-25-flakes/&#34;&gt;Nix Flakes&lt;/a&gt; in your config, allowing you to fix all your dependencies to a particular version, with a hash. This ensures that you are &lt;em&gt;always&lt;/em&gt; able to reproduce the config no matter what rebuilds you do. It primarily is used for locking dependencies of a particular package, but it can also be used for locking dependencies of NixOS configs.&lt;/p&gt;
&lt;p&gt;deploy-rs piggybacks on flakes to define what hosts it should deploy too, requiring a &lt;code&gt;deploy.nodes&lt;/code&gt; attrset of hostnames to a &lt;a href=&#34;https://github.com/serokell/deploy-rs#node&#34;&gt;definition&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;deploy = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  nodes &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    dennis &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      hostname &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;192.168.1.12&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      profiles &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        system &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          user &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;root&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          path &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; deploy-rs&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;lib&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;x86_64-linux&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;activate&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;nixos self&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;nixosConfigurations&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;dennis;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          sshOpts &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [ &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;-o&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;IdentitiesOnly=yes&amp;#34;&lt;/span&gt; ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Given that I have node NixOS configurations defined in the &lt;a href=&#34;https://github.com/jdheyburn/nixos-configs/tree/debed8c96d722fb988fb61ca106b6bf3e11414e4/hosts&#34;&gt;hosts directory&lt;/a&gt;, I can retrieved the hostnames and use these to poll the hosts defined in &lt;code&gt;catalog.nodes&lt;/code&gt; to construct a new attrset that deploy-rs requires.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# Defined earlier in the flake&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;hosts = &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;builtins&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;attrNames (&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;builtins&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;readDir &lt;span style=&#34;color:#f1fa8c&#34;&gt;./hosts&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;deploy&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;nodes = &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;builtins&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;listToAttrs (&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;map&lt;/span&gt; (host:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;let&lt;/span&gt; node &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; catalog&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;nodes&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;host&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; host;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    value &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      hostname &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; node&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;ip&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;private;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      profiles&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;system &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        user &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;root&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        path &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; deploy-rs&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;lib&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;node&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;system&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;activate&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;nixos
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          self&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;nixosConfigurations&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;host&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        sshOpts &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [ &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;-o&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;IdentitiesOnly=yes&amp;#34;&lt;/span&gt; ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }) hosts);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;What&amp;rsquo;s happening here is I&amp;rsquo;m pulling from the node definitions the IP address used to reach the host, as well as the system architecture for that node, so that we can call the correct deploy-rs library. Lastly I feed it its nixosConfiguration that should be deployed to the node - this is a &lt;a href=&#34;https://www.tweag.io/blog/2020-07-31-nixos-flakes/&#34;&gt;requirement of using a flake&lt;/a&gt; to deploy configs.&lt;/p&gt;
&lt;p&gt;While brief, I didn&amp;rsquo;t want to overload this section with nuances of how flakes are set up. You can see &lt;a href=&#34;https://github.com/jdheyburn/nixos-configs/blob/5175593745a27de7afc5249bc130a2f1c5edb64c/flake.nix&#34;&gt;my flake.nix&lt;/a&gt; if you are keen to see how it all pieces together.&lt;/p&gt;
&lt;h2 id=&#34;conclusion-and-improvements&#34;&gt;Conclusion and improvements&lt;/h2&gt;
&lt;p&gt;I don&amp;rsquo;t like how I have to define which host the service is running on, I think it would be better to have it so that wherever the modules are enabled, then the catalog discovers that. It&amp;rsquo;s only a tiny bit of duplication so its not been at the top of my list to improve on.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d like to also define as much Grafana configuration as possible using this. I provided a GUI walkthrough of how I set up the alerting, but it would be great to have Nix build this for us instead.&lt;/p&gt;
&lt;p&gt;While not largely catalog related, we can extend on &lt;a href=&#34;https://github.com/nix-community/home-manager&#34;&gt;home-manager&lt;/a&gt; to allow us to be able to use the catalog to deploy Nix configs to non-NixOS nodes. When we do this, we will use the package manager component of Nix (nixpkgs) to manage the packages on a host.&lt;/p&gt;
&lt;p&gt;The catalog has been largely beneficial as my source of truth; adding a service in here means that I automatically get an endpoint for it with forwarding, and have it monitored too - plus with easy extensibility to other use cases as they come. It&amp;rsquo;s also enabled configurations to be read across different hosts, without the use of a service discovery component. It&amp;rsquo;s one of the benefits of using/experimenting with NixOS of which I&amp;rsquo;m glad I&amp;rsquo;ve invested the time in and look forward to playing around with more in future.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Nix Cheat Sheet</title><enclosure url="https://jdheyburn.co.uk/blog/nix-cheat-sheet/cover.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/blog/nix-cheat-sheet/</link>
      <pubDate>Fri, 13 Jan 2023 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/nix-cheat-sheet/</guid>-->
      <description>&lt;p&gt;The world of Nix has a bit of a steep learning curve. As I write blog posts on the subject, I&amp;rsquo;ll refer to some of the tips and tricks from the cheat sheet here.&lt;/p&gt;
&lt;h2 id=&#34;documentation&#34;&gt;Documentation&lt;/h2&gt;
&lt;p&gt;A list of Nix documentation I refer to, along with some getting started pages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://nixos.org/manual/nixpkgs/stable/&#34;&gt;Nixpkgs manual&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://nixos.org/manual/nixos/stable/&#34;&gt;NixOS manual&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://nix.dev/&#34;&gt;nix.dev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://nixos.org/guides/nix-pills/&#34;&gt;Nix Pills&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://zero-to-nix.com/&#34;&gt;Zero to Nix&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;resources&#34;&gt;Resources&lt;/h2&gt;
&lt;p&gt;Here are a list of resources I use to search for Nix options, services, and any Nix built-in functions.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://search.nixos.org/packages?channel=unstable&amp;from=0&amp;size=50&amp;sort=relevance&#34;&gt;NixOS Search&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://mynixos.com/&#34;&gt;MyNixOS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://noogle.dev/&#34;&gt;noogle&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;functions-and-lambdas&#34;&gt;Functions (and Lambdas)&lt;/h2&gt;
&lt;p&gt;In some languages you typically have a formal declaration of a function (or lambda), such as for Python:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;double_num&lt;/span&gt;(num_to_double)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; num_to_double &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Nixlang is a functional language, and a typical behaviour of these is to define lambdas and assign them to variables.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;double_num = num_to_double: num_to_double &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The lambda is defined when we create parameters for that lambda, which are defined by having a variable name prefixed by a colon. From the example above, the parameter for the &lt;code&gt;double_num&lt;/code&gt; lambda is &lt;code&gt;num_to_double&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Once all the parameters are defined we move onto the body, in which the final statement is returned, similarly to the &lt;a href=&#34;https://franzejr.github.io/best-ruby/idiomatic_ruby/implicit_return.html&#34;&gt;implicit return in Ruby&lt;/a&gt;. Here we&amp;rsquo;re just using the maths expression &lt;code&gt;num_to_double * 2&lt;/code&gt; to have the intended outcome.&lt;/p&gt;
&lt;h2 id=&#34;check-if-attribute-is-in-attrset-shorthand&#34;&gt;Check if attribute is in attrset shorthand&lt;/h2&gt;
&lt;p&gt;You can use the built in function &lt;a href=&#34;https://nixos.org/manual/nix/stable/language/builtins.html#builtins-hasAttr&#34;&gt;hasAttr&lt;/a&gt; to determine if an attribute exists within an attrset.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ attrSet = { key1 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;key1&amp;#34;&lt;/span&gt;; };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;builtins&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;hasAttr&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;key1&amp;#34;&lt;/span&gt; attrSet
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;true
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;builtins&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;hasAttr&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;key2&amp;#34;&lt;/span&gt; attrSet
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;false
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can use the &lt;code&gt;?&lt;/code&gt; operator as an alternate way of doing the same.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ attrSet &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;key1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;true
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ attrSet &lt;span style=&#34;color:#ff79c6&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;key2&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;false
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;merge-two-attrsets-shorthand&#34;&gt;Merge two attrsets shorthand&lt;/h2&gt;
&lt;p&gt;You can use the &lt;code&gt;//&lt;/code&gt; operator to merge the contents of the attrset on the right into the attrset on the left.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ newAttrset = { key1 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;key1&amp;#34;&lt;/span&gt;; } &lt;span style=&#34;color:#ff79c6&#34;&gt;//&lt;/span&gt; { key2 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;key2&amp;#34;&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ newAttrset
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{ key1 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;key1&amp;#34;&lt;/span&gt;; key2 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;key2&amp;#34;&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If the attribute already exists in the left attrset, then it is overwritten.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ attrSet = { key1 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;key1&amp;#34;&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ newAttrSet = attrSet &lt;span style=&#34;color:#ff79c6&#34;&gt;//&lt;/span&gt; { key1 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;newKey1&amp;#34;&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ newAttrSet
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{ key1 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;newKey1&amp;#34;&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;add-an-attribute-to-an-attrset&#34;&gt;Add an attribute to an attrset&lt;/h2&gt;
&lt;p&gt;Given an attrset with this structure:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ items = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  keyName &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    value1 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;value&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    value2 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;value&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can use the &lt;code&gt;map&lt;/code&gt; function to loop over each attrName (attribute name (aka key)) and its value in &lt;code&gt;items&lt;/code&gt; to create a new attrset with its attrName added to it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ newItems = &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;map&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (key: items&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;key&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;//&lt;/span&gt; { name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; key; })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;builtins&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;attrNames items);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;map&lt;/code&gt; function takes two parameters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a lambda that should be performed against each element
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;(key: items.&amp;quot;${key}&amp;quot; // { name = key; })&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;This lambda takes a parameter &lt;code&gt;key&lt;/code&gt;, and returns the attribute value at &lt;code&gt;items.key&lt;/code&gt;, and merges it with the attrset &lt;code&gt;{ name = key; }&lt;/code&gt; using the [[#Merge two attrsets shorthand]].&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;a list of elements
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;(builtins.attrNames items)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;builtins.attrNames&lt;/code&gt; is a function that takes a attrset and returns all the attribute names (keys) for it&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So &lt;code&gt;newItems&lt;/code&gt; will equal the below:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ newItems
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;keyName&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    value1 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;value&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    value2 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;value&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;map&lt;/code&gt; function is also useful for converting an attrset to a list, since a list is what is returns by default.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ items_list = &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;map&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (key: items&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;key&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    (&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;builtins&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;attrNames items);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ items_list
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    value1 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;value&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    value2 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;value&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;converting-a-list-to-an-attribute-set&#34;&gt;Converting a list to an attribute set&lt;/h2&gt;
&lt;p&gt;When we [[#Add an attribute to an attrset|add an attribute to an attset]], the &lt;code&gt;map&lt;/code&gt; function returns a list, when we may want to have an attrset back instead.&lt;/p&gt;
&lt;p&gt;We can use the &lt;code&gt;builtins.listToAttrs&lt;/code&gt; function to convert it to one. It takes one argument, a list of attrsets that contain two attributes; &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;value&lt;/code&gt; - the former becomes the attrName and the latter its value.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ list = [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;keyName&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        value &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            value1 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;value&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            value2 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;value&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;keyName2&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        value &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            value3 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;value&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ attrSet = &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;builtins&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;listToAttrs list
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;attrSet&lt;/code&gt; will then equal the below:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;attrSet = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    keyName &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        value1 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;value&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        value2 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;value&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    keyName2 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        value3 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;value&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
    </item>
    
    <item>
      <title>Converting to the Church of Nix</title><enclosure url="https://jdheyburn.co.uk/blog/converting-to-the-church-of-nix/cover.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/blog/converting-to-the-church-of-nix/</link>
      <pubDate>Mon, 17 Oct 2022 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/converting-to-the-church-of-nix/</guid>-->
      <description>&lt;p&gt;I&amp;rsquo;ve &lt;a href=&#34;https://jdheyburn.co.uk/blog/reverse-proxy-multiple-domains-using-caddy-2/&#34;&gt;written in the past&lt;/a&gt; about some services I host at home which were running on a Raspberry Pi 3 with Raspbian. Making changes to the host was done ad-hoc with no configuration management tool like Ansible, so should anything happen to the host and it dies, I won&amp;rsquo;t have a way to reproduce what I had working before.&lt;/p&gt;
&lt;p&gt;Naturally last December, the Pi SD card got corrupted and lost everything that was on there.&lt;/p&gt;
&lt;p&gt;Rather than rebuilding things how they were in the same way as before, I wanted to look into a better way to ensure I could reproduce services installed and manage configurations on a host to mitigate this in the future.&lt;/p&gt;
&lt;p&gt;NixOS came to mind as it solves these problems and more, I had tried to get it working on the Pi and on my Dell XPS 9360 laptop in the past with unsuccessful results. This time round there is much greater support for NixOS on Pi, and coupled with some more motivation, I decided to make the switch.&lt;/p&gt;
&lt;p&gt;In this series I&amp;rsquo;ll be writing about my setup, how I deploy modules, onboarding flakes, and problems I came across along the way. The series will be more NixOS config management orientated, while being referred back to in other posts where I&amp;rsquo;ll talk about some of the services I&amp;rsquo;m running via NixOS. This post kicks off with a quick introduction with some more in-depth posts to come.&lt;/p&gt;
&lt;h2 id=&#34;why-nixos&#34;&gt;Why NixOS&lt;/h2&gt;
&lt;p&gt;For a quick 411, there are 3 components that fall under the &amp;ldquo;Nix&amp;rdquo; umbrella:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Nix / Nixlang
&lt;ul&gt;
&lt;li&gt;The functional, declarative language that is used to build, manipulate, packages, files, configurations, within the ecosystem&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Nixpkgs
&lt;ul&gt;
&lt;li&gt;A package manager that can be installed on a Linux machine, or on macOS via &lt;a href=&#34;https://github.com/LnL7/nix-darwin&#34;&gt;nix-darwin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Think an alternative to &lt;code&gt;apt&lt;/code&gt;, &lt;code&gt;brew&lt;/code&gt;, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;NixOS
&lt;ul&gt;
&lt;li&gt;An operating system that uses Nixpkgs as its package manager, but also can configure OS-level configurations, hardware settings, services, via Nixlang&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nixpkgs are designed to be easily reproducible by defining explicit versions and hashes of software and its dependencies - and the same goes for NixOS which takes this same philisophy and applies it beyond software. Nix builds this software and stores it in the Nix store (appropriately named &lt;code&gt;/nix/store&lt;/code&gt;), where a previous deployment of a configuration can be rolled back easily with a single command. This makes it useful for experiments or to rollback when shit hits the fan.&lt;/p&gt;
&lt;p&gt;There are plenty of talented people who have written about it before, so I&amp;rsquo;ll link them here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://nixos.org/manual/nix/stable/introduction.html&#34;&gt;NixOS manual introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ianthehenry.com/posts/how-to-learn-nix/introduction/&#34;&gt;How to Learn Nix, Part 1: What&amp;rsquo;s all this about?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So the 3 components together allow for some pretty powerful stuff. Your NixOS configurations are written to a &lt;code&gt;.nix&lt;/code&gt; suffixed file by default located at  &lt;code&gt;/etc/nixos/configuration.nix&lt;/code&gt;, which can contain minimal config:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{ config&lt;span style=&#34;color:#ff79c6&#34;&gt;,&lt;/span&gt; pkgs&lt;span style=&#34;color:#ff79c6&#34;&gt;,&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;...&lt;/span&gt; }:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  networking&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;hostName &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;my-hostname&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#6272a4&#34;&gt;# Define your hostname.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#6272a4&#34;&gt;# Set your time zone.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  time&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;timeZone &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Europe/London&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#6272a4&#34;&gt;# Define a user account. Don&amp;#39;t forget to set a password with ‘passwd’.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  users&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;users&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;my-user &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    isNormalUser &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    home &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;/home/my-user&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    extraGroups &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [ &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;wheel&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;networkmanager&amp;#34;&lt;/span&gt; ]; &lt;span style=&#34;color:#6272a4&#34;&gt;# Enable ‘sudo’ for the user.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    password &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;hunter2&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  environment&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;systemPackages &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt; pkgs; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    vim
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Whenever we want to deploy the configuration to make it live, we can use the command &lt;code&gt;nixos-rebuild switch&lt;/code&gt;, which will read this file and build the packages and configurations requested.&lt;/p&gt;
&lt;p&gt;Should we want to have Firefox installed? We can just define it:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;environment&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;systemPackages = &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt; pkgs; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  vim
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#6272a4&#34;&gt;# add in the below&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  firefox
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If we want to enable SSH access to our hosts, it&amp;rsquo;s easy to do that:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#6272a4&#34;&gt;# Enable the OpenSSH daemon.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  services&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;openssh = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    enable &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    permitRootLogin &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;no&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    passwordAuthentication &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Services are usually defined with the syntax &lt;code&gt;services.SERVICE_NAME.enable&lt;/code&gt;, however as shown above we can see &lt;code&gt;permitRootLogin&lt;/code&gt; and &lt;code&gt;passwordAuthentication&lt;/code&gt; attributes are defined. These inputs are read by the &lt;a href=&#34;https://github.com/NixOS/nixpkgs/blob/nixos-unstable/nixos/modules/services/networking/ssh/sshd.nix#L148&#34;&gt;service in nixpkgs&lt;/a&gt; to then be &lt;a href=&#34;https://github.com/NixOS/nixpkgs/blob/nixos-unstable/nixos/modules/services/networking/ssh/sshd.nix#L533&#34;&gt;output as configuration&lt;/a&gt; for the service.&lt;/p&gt;
&lt;p&gt;One last example, what if the service we just deployed requires a particular port opened? NixOS can do that too!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;networking&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;firewall&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;allowedTCPPorts = [ &lt;span style=&#34;color:#bd93f9&#34;&gt;4040&lt;/span&gt; ];
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When you compare that against traditional package managers, you would first have to install the software &lt;em&gt;and then&lt;/em&gt; configure it, probably by hand. On top of that, you would have to manage the versioning of the configuration using another tool. &lt;strong&gt;NixOS does all of that for you&lt;/strong&gt; - provided that you&amp;rsquo;re checking in your NixOS configuration files!&lt;/p&gt;
&lt;p&gt;Just to cap off - if something broke during the &lt;code&gt;nixos-rebuild switch&lt;/code&gt; command and you want to go back to the previous state, then we can do so with &lt;code&gt;nixos-rebuild switch --rollback&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;layout&#34;&gt;Layout&lt;/h2&gt;
&lt;p&gt;Before going into the details of the setup I&amp;rsquo;m running I think it helps to have some context of where I was before the migration.&lt;/p&gt;
&lt;h3 id=&#34;legacy&#34;&gt;Legacy&lt;/h3&gt;
&lt;p&gt;Prior to starting this, I had this setup across my network:&lt;/p&gt;
&lt;h4 id=&#34;dee&#34;&gt;dee&lt;/h4&gt;
&lt;p&gt;My starting point for playing with self-hosting, it was a Raspberry Pi 3.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&#34;https://caddyserver.com/&#34;&gt;Caddy&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Reverse proxy for all services in the network&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NFS server&lt;/td&gt;
&lt;td&gt;Network wide storage, backed up to cloud storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&#34;https://pi-hole.net/&#34;&gt;PiHole&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;For adblocking and local DNS resolution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UniFi Controller&lt;/td&gt;
&lt;td&gt;Control plane for my UniFi devices at home&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&#34;frank&#34;&gt;frank&lt;/h4&gt;
&lt;p&gt;A Ubuntu VM on a Proxmox hypervisor, built so that I could play with Proxmox and docker containers.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&#34;https://heimdall.site/&#34;&gt;Heimdall&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Dashboard for services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&#34;https://github.com/huginn/huginn&#34;&gt;Huginn&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Experiment with automation (such as &lt;a href=&#34;https://jdheyburn.co.uk/blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn/&#34;&gt;NHS vaccine alerts&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&#34;https://www.portainer.io/&#34;&gt;Portainer&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;UI for docker containers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;today&#34;&gt;Today&lt;/h3&gt;
&lt;p&gt;Host frank is unchanged from above today, while here&amp;rsquo;s what I have running elsewhere.&lt;/p&gt;
&lt;h4 id=&#34;dee-1&#34;&gt;dee&lt;/h4&gt;
&lt;p&gt;Upgraded to a Raspberry Pi 4 (NixOS is RAM hungry!) in an &lt;a href=&#34;https://thepihut.com/products/argon-one-m-2-raspberry-pi-4-case&#34;&gt;Argon One case&lt;/a&gt;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&#34;https://github.com/AdguardTeam/AdGuardHome&#34;&gt;AdGuardHome&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;For adblocking and local DNS resolution (replaces PiHole)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&#34;https://caddyserver.com/&#34;&gt;Caddy&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Reverse proxy for services &lt;em&gt;local to the host&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&#34;https://healthchecks.io/&#34;&gt;Healthchecks&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Monitor cron jobs (primarily the backup jobs for now)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&#34;https://min.io/&#34;&gt;Minio&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;S3 compatible storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NFS server&lt;/td&gt;
&lt;td&gt;Network wide storage, backed up to cloud storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&#34;https://www.plex.tv/&#34;&gt;Plex&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Music and video player&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UniFi Controller&lt;/td&gt;
&lt;td&gt;Control plane for UniFi devices&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&#34;dennis&#34;&gt;dennis&lt;/h4&gt;
&lt;p&gt;New VM running NixOS on the Proxmox HV. I built this as I had some RAM issues on dee, which I subsequently fixed but ended up putting more services on there to experiment with multiple hosts.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&#34;https://caddyserver.com/&#34;&gt;Caddy&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Reverse proxy for services &lt;em&gt;local to the host&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&#34;https://dashy.to/&#34;&gt;Dashy&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Replaces Heimdall as a dashboard for services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&#34;https://grafana.com/&#34;&gt;Grafana&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Metric and log visualisation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&#34;https://grafana.com/oss/loki/&#34;&gt;Loki&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Log collections&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&#34;https://prometheus.io/&#34;&gt;Prometheus&lt;/a&gt; with &lt;a href=&#34;https://thanos.io/&#34;&gt;Thanos&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Metric scraping and long term storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&#34;https://victoriametrics.com/&#34;&gt;Victoria Metrics&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Metric scraping (just testing as a potential Prometheus replacement)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;additional-features&#34;&gt;Additional features&lt;/h2&gt;
&lt;p&gt;Besides NixOS itself, there are some extra features which I&amp;rsquo;m using in the stack:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://xeiaso.net/blog/nix-flakes-1-2022-02-21&#34;&gt;nix flakes&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Version pin dependencies&lt;/li&gt;
&lt;li&gt;Allows for an additional layer of reproducibility&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/ryantm/agenix&#34;&gt;agenix&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Encryption using &lt;a href=&#34;https://github.com/FiloSottile/age&#34;&gt;age&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Allows for secrets to be safely checked in to Git repos so that they can be used in configurations&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/serokell/deploy-rs&#34;&gt;deploy-rs&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Deploy NixOS configurations to hosts remotely&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/nix-community/home-manager&#34;&gt;home-manager&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Allows configuration of user-level programs and settings&lt;/li&gt;
&lt;li&gt;Typically used as a &lt;a href=&#34;https://www.freecodecamp.org/news/dotfiles-what-is-a-dot-file-and-how-to-create-it-in-mac-and-linux/&#34;&gt;dotfile&lt;/a&gt; manager, which can replace tools such as &lt;a href=&#34;https://www.chezmoi.io/&#34;&gt;chezmoi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Can be applied to non-NixOS machines&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/NixOS/nixos-hardware&#34;&gt;nixos-hardware&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Crowdsourced configuration best practices to get NixOS running smoothly on particular hardware&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;configurations&#34;&gt;Configurations&lt;/h2&gt;
&lt;p&gt;Since NixOS can be used to build packages and services, it can also be used to build configurations. I have defined a service &amp;ldquo;catalog&amp;rdquo; which acts as a source of truth for services, so that dependent services can refer back to this to build their own config. Some examples of this are generating configs used for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DNS rewrites to forward DNS names to the host hosting the service&lt;/li&gt;
&lt;li&gt;Reverse proxy the service to the port&lt;/li&gt;
&lt;li&gt;Dashy for the home dashboard&lt;/li&gt;
&lt;li&gt;Prometheus monitoring service endpoints&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The inspiration for having a centralised location for service catalog came from jhillyerd - where they &lt;a href=&#34;https://github.com/jhillyerd/homelab/blob/main/nixos/catalog.nix&#34;&gt;achieve the same thing&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;My NixOS configurations are stored in &lt;a href=&#34;https://github.com/jdheyburn/nixos-configs&#34;&gt;GitHub&lt;/a&gt;, so you can have a browse through there. Subsequent posts will dive into what each of these is doing in more detail, but here&amp;rsquo;s a high-level of the top-level directories.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;common/&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Settings that are applied to all hosts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;home-manager/&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;User-level programs and settings&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;hosts/&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Configurations for individual hosts, along with their hardware configs via &lt;a href=&#34;https://github.com/NixOS/nixos-hardware&#34;&gt;nixos-hardware&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;modules/&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Where services and their dependencies are defined&lt;/li&gt;
&lt;li&gt;Rarely do I install software via just &lt;code&gt;services.SERVICE_NAME.enable = true&lt;/code&gt;, there is usually extra properties to come with it - so having wrapping them in a custom module is usually easiest for me to maintain&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;overlays/&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Nix overlays allow you to overwrite packages and configurations pulled from a dependency&lt;/li&gt;
&lt;li&gt;In my case here, I&amp;rsquo;m using it to fix some bugs in one program, and add functionality to another&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;secrets/&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Where agenix secrets are stored&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;catalog.nix&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Contains information about the various services and where&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;flake.nix&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The main entry point into the configurations when deploying&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This is only a quick introduction into the series which introduces NixOS, what I&amp;rsquo;m using it for, and a high-level look into the configurations.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Alerting on NHS Coronavirus Vaccine Updates With Huginn</title><enclosure url="https://jdheyburn.co.uk/blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn/cover.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn/</link>
      <pubDate>Thu, 03 Jun 2021 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn/</guid>-->
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;UPDATE 2021-06-06&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A few days after publishing this I came across a bug where agent jobs would be stuck in pending state. I&amp;rsquo;ve since fixed this and documented some additional changes I&amp;rsquo;ve made at the &lt;a href=&#34;https://jdheyburn.co.uk/blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn/#fixing-agent-jobs-stuck-in-pending-state&#34;&gt;end of the post&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The UK&amp;rsquo;s coronavirus vaccine strategy has been to target those most vulnerable first, and then trickle down towards the healthier population. Since that age is creeping down toward my age group, I wanted to see if I could alert myself when I would be eligible for the vax.&lt;/p&gt;
&lt;p&gt;My local GP would send out an SMS text message informing me when I&amp;rsquo;m eligible, however I&amp;rsquo;ve heard that this text can come days after you&amp;rsquo;re eligible. Knowing that the latest guidance is maintained on the &lt;a href=&#34;https://www.nhs.uk/conditions/coronavirus-covid-19/coronavirus-vaccination/coronavirus-vaccine/&#34;&gt;NHS Coronavirus Vaccine site&lt;/a&gt;, I can use &lt;a href=&#34;https://github.com/huginn/huginn#what-is-huginn&#34;&gt;Huginn&lt;/a&gt; to alert me when the page updates with the latest eligibility.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;vaccine-eligibility.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn//blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn/vaccine-eligibility.png&#34;
    alt=&#34;A list of bullet points of who is eligible to receive the vaccine, includes people aged 30 years and older, vulnerable people, etc&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;tldr&#34;&gt;tl;dr&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Huginn is an automation tool with a number of different agents&lt;/li&gt;
&lt;li&gt;It can be configured to monitor a property (or properties) on a web page and trigger an action&lt;/li&gt;
&lt;li&gt;That action can take the form of an email alert&lt;/li&gt;
&lt;li&gt;This can be used to monitor the latest age group eligible for a vaccine&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;deploying-huggin&#34;&gt;Deploying Huggin&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/huginn/huginn&#34;&gt;Huginn&lt;/a&gt; is a self-hosted automation kit that allows you to create agents and workflows in response to events, sort of like your own &lt;a href=&#34;https://ifttt.com/&#34;&gt;IFTTT&lt;/a&gt; (If This Then That).&lt;/p&gt;
&lt;p&gt;Being self-hosted it can be deployed out in a number of ways. I already have &lt;a href=&#34;https://www.portainer.io/&#34;&gt;Portainer&lt;/a&gt; (a GUI for &lt;a href=&#34;https://www.docker.com/&#34;&gt;Docker&lt;/a&gt;) running in a virtual machine - so to deploy it out I can follow the instructions for &lt;a href=&#34;https://github.com/huginn/huginn/blob/master/doc/docker/install.md&#34;&gt;Docker container deployment&lt;/a&gt;. I created a &lt;a href=&#34;https://docs.docker.com/compose/&#34;&gt;docker-compose&lt;/a&gt; file so that it can be easily replicated for yourselves in Docker, or even as a &lt;a href=&#34;https://documentation.portainer.io/v2.0/stacks/create/&#34;&gt;Portainer Stack&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll notice there are some environment variables for SMTP here; the values for these will differ for your SMTP setup. I talk more about how I set this up with my Gmail account &lt;a href=&#34;https://jdheyburn.co.uk/blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn/#configure-huginn-for-sending-email-over-gmail-smtp&#34;&gt;later in the post&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Also big thanks to zblesk for their &lt;a href=&#34;https://zblesk.net/blog/running-huginn-with-docker/&#34;&gt;blog post on Huginn&lt;/a&gt; which helped me iron out some of the environment variables!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;version&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;3&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;huginn&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;command&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - /scripts/init
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;container_name&lt;/span&gt;: huginn
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;environment&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - SMTP_PORT=587
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - SMTP_SERVER=&amp;lt;SMTP_SERVER&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - SMTP_PASSWORD=&amp;lt;SMTP_PASSWORD&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - SMTP_USER_NAME=&amp;lt;SMTP_USER_NAME&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - SMTP_ENABLE_STARTTLS_AUTO=true
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - SMTP_AUTHENTICATION=plain
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - SMTP_DOMAIN=&amp;lt;SMTP_DOMAIN&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - DATABASE_POOL=30
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - TIMEZONE=London
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - IMPORT_DEFAULT_SCENARIO_FOR_ALL_USERS=false
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;image&lt;/span&gt;: huginn/huginn:latest
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#bd93f9&#34;&gt;3000&lt;/span&gt;:&lt;span style=&#34;color:#bd93f9&#34;&gt;3000&lt;/span&gt;/tcp
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;restart&lt;/span&gt;: unless-stopped
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;user&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;1000&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - mysql:/var/lib/mysql
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;working_dir&lt;/span&gt;: /app
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;mysql&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;driver&lt;/span&gt;: local
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Be sure to change &lt;code&gt;TIMEZONE&lt;/code&gt; to your timezone, I found that having an incorrectly set timezone caused Huginn jobs to be backed up in a pending state. I tried &lt;code&gt;Europe/London&lt;/code&gt; first but that caused the process to crash on boot; so I ultimately got it working with just &lt;code&gt;London&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The config above will expose Huginn on the Docker host at port 3000. I like to give my services a nice domain name to access them at using Caddy, which I&amp;rsquo;ve &lt;a href=&#34;https://jdheyburn.co.uk/blog/reverse-proxy-multiple-domains-using-caddy-2/&#34;&gt;written about before&lt;/a&gt;. Here&amp;rsquo;s a condensed version of what my Caddy config file looks like for Huginn.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;apps&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;http&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;servers&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;srv0&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;listen&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;:443&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;routes&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;handle&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;handler&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;subroute&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;routes&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;handle&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                          &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;handler&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;reverse_proxy&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                          &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;upstreams&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                              &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;dial&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;192.168.2.15:3000&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                          ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                      ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                  ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              ],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;match&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;host&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;huginn.joannet.casa&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              ],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;terminal&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#6272a4&#34;&gt;// ... others removed for brevity
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;          ],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;tls_connection_policies&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;match&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;sni&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                  &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;huginn.joannet.casa&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                  &lt;span style=&#34;color:#6272a4&#34;&gt;// ... others removed for brevity
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;                ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;tls&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;automation&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;policies&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;issuer&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;challenges&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;dns&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;provider&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;api_token&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;CLOUDFLARE_API_TOKEN&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;cloudflare&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;module&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;acme&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;subjects&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;huginn.joannet.casa&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              &lt;span style=&#34;color:#6272a4&#34;&gt;// ... others removed for brevity
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;            ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I&amp;rsquo;ll need to also add in a DNS entry in PiHole to route HTTP calls on my network for &lt;code&gt;https://huginn.joannet.casa&lt;/code&gt; to the IP address of my Caddy server.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;huginn-deployed.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn//blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn/huginn-deployed.png&#34;
    alt=&#34;Huginn login page at the domain name for it specified earlier&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Deployed and ready for set up!&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;setting-up-agents&#34;&gt;Setting up agents&lt;/h2&gt;
&lt;p&gt;The workflow we need to set up here is pretty simple whereby we only need two agents; a Website Agent and an Email Agent. The website agent will perform the scraping of the NHS website on a regular basis, and if the component on the web page has changed, then it will invoke it&amp;rsquo;s downstream notifier - the Email Agent.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Website Agent    ------ invokes ------&amp;gt;    Email Agent
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Before we do that we&amp;rsquo;ll need to create an account for it - this is all local to your deployment and is not external. The invitation code you&amp;rsquo;ll need to enter is &lt;code&gt;try-huginn&lt;/code&gt;.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;huginn-account-setup.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn//blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn/huginn-account-setup.png&#34;
    alt=&#34;Huginn account setup page with the form filled in with email address and password. The invitation code is populated with try-huginn&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;When you get through to the main page, I suggest disabling or removing the agents that are there already to prevent some problems later on. If you set &lt;code&gt;IMPORT_DEFAULT_SCENARIO_FOR_ALL_USERS=false&lt;/code&gt; then you should not see any there.&lt;/p&gt;
&lt;h3 id=&#34;website-agent&#34;&gt;Website agent&lt;/h3&gt;
&lt;p&gt;From the Huginn home page, create a new agent, where the type will be Website Agent.&lt;/p&gt;
&lt;p&gt;There will be a bunch of fields that appear, the only ones you need to fill in are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Name
&lt;ul&gt;
&lt;li&gt;e.g. &lt;code&gt;NHSScrape&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Schedule
&lt;ul&gt;
&lt;li&gt;for me, checking once an hour is good enough&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Options&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The other fields serve a purpose that&amp;rsquo;s beyond the scope of this post.&lt;/p&gt;
&lt;p&gt;Within Options comes the configuration used to define the website agent. The documentation for the agent config appears on the right hand side, so you can read through that for reference.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;website-agent-form-populated.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn//blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn/website-agent-form-populated.png&#34;
    alt=&#34;The completed form to create a new website agent will the fields populated as specified previously&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;In order for us to determine how to configure this we first need to decide what part of the web page we want to be alerted on in event of a update. Refer to the screenshot below of the &lt;a href=&#34;https://www.nhs.uk/conditions/coronavirus-covid-19/coronavirus-vaccination/coronavirus-vaccine/&#34;&gt;NHS Coronavirus Vaccine page&lt;/a&gt; and you&amp;rsquo;ll see a number of bullet points listed out of the vaccine criteria. The text we want to be alerted on is &amp;ldquo;people aged 30 and over&amp;rdquo;.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;vaccine-eligibility.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn//blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn/vaccine-eligibility.png&#34;
    alt=&#34;A list of bullet points of who is eligible to receive the vaccine, includes people aged 30 years and older, vulnerable people, etc&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;The website agent supports an &lt;a href=&#34;https://www.w3schools.com/xml/xpath_syntax.asp&#34;&gt;xpath syntax&lt;/a&gt; as a config option, which is an expression syntax used to retrieve objects from XML documents - including HTML. But how do we find out what the xpath for this text field is?&lt;/p&gt;
&lt;p&gt;Enter &lt;a href=&#34;https://selectorgadget.com/&#34;&gt;SelectorGadget&lt;/a&gt;. It&amp;rsquo;s a &lt;a href=&#34;https://en.wikipedia.org/wiki/Bookmarklet&#34;&gt;bookmarklet&lt;/a&gt; tool that helps with just that - I recommend taking a look at the &lt;a href=&#34;https://vimeo.com/52055686&#34;&gt;short tutorial&lt;/a&gt; on how to use it.&lt;/p&gt;
&lt;p&gt;Since I know I only want to select the first bullet point in this particular list, I start off by first clicking that property which turns that node green.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;selector-gadget-1.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn//blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn/selector-gadget-1.png&#34;
    alt=&#34;Selector gadget highlighted in green the bullet point we want to monitor on, the remaining bullet points are highlighted yellow. There are 76 items in scope for this current selection&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Clicking on the property I want to target highlights it green&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;You can see the xpath for this is a &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; node, which returns 76 results on the page we&amp;rsquo;re scraping. These are represented by the boxes that are highlighted yellow.&lt;/p&gt;
&lt;p&gt;What I want to do is whittle it down by filtering out all the other &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; nodes I&amp;rsquo;m not interested in, so that I only have 1 result left. As I&amp;rsquo;ve already made my first selection, any subsequent clicks will now filter those out. So now it&amp;rsquo;s just a case of playing whack-a-mole until all yellow highlighted fields are gone.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;selector-gadget-2.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn//blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn/selector-gadget-2.png&#34;
    alt=&#34;Selector gadget highlighted in green the bullet point we want to monitor on, the second bullet point we want to ignore is highlighted red - indicating we do not want it in scope. There are 17 items in scope for this current selection&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Clicking the second bullet point indicated that I don&amp;rsquo;t care about any other &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; elements in this range.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;selector-gadget-3.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn//blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn/selector-gadget-3.png&#34;
    alt=&#34;Scrolling to the top of the page there is another element that is highlighted yellow, the &amp;#39;Home&amp;#39; button&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; properties also make up headings at the top of the page - so these appear highlighted in yellow too.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;selector-gadget-4.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn//blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn/selector-gadget-4.png&#34;
    alt=&#34;Clicking the home element filters it out and now is highlighted red - there are 13 items in scope for this current selection&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Clicking the Home element filters that out.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;selector-gadget-5.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn//blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn/selector-gadget-5.png&#34;
    alt=&#34;Scrolling further down the page we see another bullet point in a separate list that is highlighted yellow&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Scrolling down further on the page, there&amp;rsquo;s another &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; range which needs to be filtered.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;selector-gadget-6.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn//blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn/selector-gadget-6.png&#34;
    alt=&#34;Filtering out the bullet point highlights it red and now there is only 1 item in scope for this selection&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Boom - filtering that means we only have 1 element being targeted.&lt;/p&gt;
&lt;p&gt;We now only have 1 selection, so now we can click the XPath button in the tool to retrieve the config we need.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;//ul[(((count(preceding-sibling::*) + 1) = 6) and parent::*)]//li[(((count(preceding-sibling::*) + 1) = 1) and parent::*)]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;selector-gadget-7.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn//blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn/selector-gadget-7.png&#34;
    alt=&#34;Clicking on the XPath button in SelectorGadget shows the final xpath syntax that we need&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;blockquote&gt;
&lt;p&gt;N.B. since writing this it looks like the xpath changed - I&amp;rsquo;ve updated the config below with the same. I retrieved it using the same method as described above.&lt;/p&gt;
&lt;p&gt;As a future task it&amp;rsquo;d be great to see if we could be alerted on when the working status of a job fails - but it seems that &lt;a href=&#34;https://github.com/huginn/huginn/issues/1333&#34;&gt;feature is missing&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Going back to the agent config, this xpath syntax then goes into the xpath key.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;expected_update_period_in_days&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;2&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;url&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;https://www.nhs.uk/conditions/coronavirus-covid-19/coronavirus-vaccination/coronavirus-vaccine/&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;html&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;mode&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;on_change&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;extract&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;title&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;xpath&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;//ul[(((count(preceding-sibling::*) + 1) = 4) and parent::*)]//li[(((count(preceding-sibling::*) + 1) = 1) and parent::*)]&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;value&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;normalize-space(.)&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once it&amp;rsquo;s configured then you can click on &lt;strong&gt;Dry Run&lt;/strong&gt; at the bottom to see the text it extracts.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;website-agent-dry-run.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn//blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn/website-agent-dry-run.png&#34;
    alt=&#34;Huginn correctly extracts the text for &amp;#39;people aged 30 and over&amp;#39; from the NHS website&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;email-agent&#34;&gt;Email agent&lt;/h3&gt;
&lt;p&gt;Now that the website agent is set up we need to set up our alert destination; this takes the form of an email agent. At minimum, we only need to configure these fields:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Name
&lt;ul&gt;
&lt;li&gt;e.g. NHSEmail&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Sources
&lt;ul&gt;
&lt;li&gt;select the website agent you created beforehand&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Options&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that an agent such as email can have multiple sources - so if you wanted to be alerted on multiple web pages then you only need one email agent.&lt;/p&gt;
&lt;p&gt;The options field is not as complex as the website agent - mine is configured with this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;subject&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;NHS Coronavirus Page update&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;headline&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Vaccine age updated&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;expected_receive_period_in_days&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;2&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;email-agent-form-populated.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn//blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn/email-agent-form-populated.png&#34;
    alt=&#34;The completed form to create a new email agent will the fields populated as specified previously&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;The real complexity lies in configuring Huginn to send email&amp;hellip;&lt;/p&gt;
&lt;h2 id=&#34;configure-huginn-for-sending-email-over-gmail-smtp&#34;&gt;Configure Huginn for sending email over Gmail SMTP&lt;/h2&gt;
&lt;p&gt;I have a Gmail account which opens up SMTP access to allows applications to send email programatically, where Huginn has support for this. You&amp;rsquo;ll notice earlier when we &lt;a href=&#34;https://jdheyburn.co.uk/blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn/#deploying-huggin&#34;&gt;deployed Huginn&lt;/a&gt; that I had a number of environment variables configured for SMTP. It took a bit of trial-and-error and searching through GitHub issues to get it right, but that config works for me.&lt;/p&gt;
&lt;p&gt;Since my Gmail account is set up with 2FA, I cannot use my actual Gmail password in the SMTP_PASSWORD field. Instead what I have to do is set up an application-specific password that only Huginn is configured for. This restriction known in Google as &lt;a href=&#34;https://support.google.com/accounts/answer/6010255&#34;&gt;less secure app access&lt;/a&gt;.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;google-less-secure-app-access.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn//blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn/google-less-secure-app-access.png&#34;
    alt=&#34;Google accounts less secure app access page - this is disabled because 2-step verification is set up on my account&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;As mentioned, we&amp;rsquo;ll need to set up an app password. For this I navigated to the &lt;a href=&#34;https://myaccount.google.com/apppasswords&#34;&gt;app password page&lt;/a&gt; for my account and selected &lt;em&gt;Other (Custom name)&lt;/em&gt; from the app dropdown, to then enter the name of the app. The name doesn&amp;rsquo;t matter here - it&amp;rsquo;s for your reference.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;google-app-password-setup.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn//blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn/google-app-password-setup.png&#34;
    alt=&#34;Google accounts app password creation page - an entry for Huginn is being created&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;When you click on &lt;strong&gt;Generate&lt;/strong&gt; it will display the password assigned. It is this value that goes into the SMTP_PASSWORD env var for Huginn to pick up.&lt;/p&gt;
&lt;p&gt;As a recap, the environment variables that I needed to set in order to get emails to be sent were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SMTP_DOMAIN=gmail.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SMTP_SERVER=smtp.gmail.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SMTP_PORT=587&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SMTP_AUTHENTICATION=plain&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SMTP_USER_NAME=$EMAIL_ADDRESS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SMTP_PASSWORD=$APP_PASSWORD&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SMTP_ENABLE_STARTTLS_AUTO=true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DATABASE_POOL=30&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;While not directly related to SMTP, it helps ensure there are enough threads to process database requests&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note if you did not start up Huginn with these environment variables already set, then you&amp;rsquo;ll need to restart the service so that it can pick them up.&lt;/p&gt;
&lt;h2 id=&#34;testing-the-flow&#34;&gt;Testing the flow&lt;/h2&gt;
&lt;p&gt;We can test the whole flow by making a modification to the website agent we created. Currently the mode we have it set to is &lt;code&gt;on_change&lt;/code&gt; which is the desired end mode, and will only trigger its receivers if there has been a change in the property being selected. If we change the mode to be &lt;code&gt;all&lt;/code&gt; then it will always invoke the receivers.&lt;/p&gt;
&lt;p&gt;Couple this with setting a frequent schedule (i.e. every 5m) then we should be receiving an email with the current value every 5 minutes.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;example-email.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn//blog/alerting-on-nhs-coronavirus-vaccine-updates-with-huginn/example-email.png&#34;
    alt=&#34;An example email sent out by the agent - it contains the text extracted from the NHS page; &amp;#39;people aged 30 and over&amp;#39;&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Once the flow is tested, we can set the mode on the website agent back to &lt;code&gt;on_change&lt;/code&gt;&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Now we just play the waiting time until getting vaxx&amp;rsquo;ed up as Marc Rebillet says&amp;hellip; &amp;#x1f489;&lt;/p&gt;

&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
  &lt;iframe src=&#34;https://www.youtube.com/embed/qeCwwYjf8gw&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; allowfullscreen title=&#34;YouTube Video&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h2 id=&#34;fixing-agent-jobs-stuck-in-pending-state&#34;&gt;Fixing agent jobs stuck in pending state&lt;/h2&gt;
&lt;p&gt;Using the docker compose file above caused an issue for me where Huginn would get jobs stuck in a pending state - where rebooting the container was the only way to unblock them&amp;hellip; no good for an alerting app!&lt;/p&gt;
&lt;p&gt;Huginn by default includes a mysql daemon as the datastore if none is provided in the environment variables. I decided to have mysql running in a separate container to see if that fixed it&amp;hellip; and it did!&lt;/p&gt;
&lt;p&gt;My new docker compose file looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;huginn&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;command&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - /scripts/init
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;container_name&lt;/span&gt;: huginn_huginn
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;environment&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - SMTP_PORT=587
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - SMTP_SERVER=&amp;lt;SMTP_SERVER&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - SMTP_PASSWORD=&amp;lt;SMTP_PASSWORD&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - SMTP_USER_NAME=&amp;lt;SMTP_USER_NAME&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - SMTP_ENABLE_STARTTLS_AUTO=true
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - SMTP_AUTHENTICATION=plain
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - SMTP_DOMAIN=&amp;lt;SMTP_DOMAIN&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - TIMEZONE=London
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - DATABASE_POOL=30
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - DATABASE_NAME=huginn
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - DATABASE_USERNAME=huginn
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - DATABASE_PASSWORD=&amp;lt;MYSQL_PASSWORD&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - DATABASE_HOST=huginn_mysql
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - DATABASE_PORT=3306
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - START_MYSQL=false
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - DATABASE_ENCODING=utf8mb4
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - IMPORT_DEFAULT_SCENARIO_FOR_ALL_USERS=false
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - DOMAIN=huginn.joannet.casa
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - INVITATION_CODE=&amp;lt;INVITATION_CODE&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;image&lt;/span&gt;: huginn/huginn:latest
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#bd93f9&#34;&gt;3000&lt;/span&gt;:&lt;span style=&#34;color:#bd93f9&#34;&gt;3000&lt;/span&gt;/tcp
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;restart&lt;/span&gt;: unless-stopped
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;user&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;1000&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;working_dir&lt;/span&gt;: /app
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;depends_on&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - mysql
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;mysql&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;image&lt;/span&gt;: mysql
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;container_name&lt;/span&gt;: huginn_mysql
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;restart&lt;/span&gt;: always
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;3306:3306&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;environment&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - MYSQL_ROOT_PASSWORD=&amp;lt;MYSQL_ROOT_PASSWORD&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - MYSQL_DATABASE=huginn
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - MYSQL_USER=huginn
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - MYSQL_PASSWORD=&amp;lt;MYSQL_PASSWORD&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - mysql:/var/lib/mysql
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;mysql&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;driver&lt;/span&gt;: local
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;mysql&lt;/code&gt; container is pretty standard so I won&amp;rsquo;t cover that here. There are some env vars I had to add to the &lt;code&gt;huginn&lt;/code&gt; container:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DATABASE_HOST&lt;/code&gt; - the database hostname to connect to, we can use the container name here&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DATABASE_PORT&lt;/code&gt; - the port to which to connect to the database&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DATABASE_NAME&lt;/code&gt; - the name of the database to use&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DATABASE_USERNAME&lt;/code&gt; - who we should connect to the database as&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DATABASE_PASSWORD&lt;/code&gt; - authentication for the user&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DATABASE_ENCODING&lt;/code&gt; - a requirement when using a newer version of MySQL as defined in the &lt;a href=&#34;https://github.com/huginn/huginn/blob/master/.env.example#L33&#34;&gt;documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;START_MYSQL&lt;/code&gt; - whether to use a local mysql daemon or not&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some additional env vars I added unrelated to the new database:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;IMPORT_DEFAULT_SCENARIO_FOR_ALL_USERS&lt;/code&gt; - I don&amp;rsquo;t care able the agents that are added by default&lt;/li&gt;
&lt;li&gt;&lt;code&gt;INVITATION_CODE&lt;/code&gt; - Lock down Huginn by requiring this code for new user sign ups&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DOMAIN&lt;/code&gt; - the endpoint that Huginn is available at&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;ve also added in a &lt;code&gt;depends_on&lt;/code&gt; on the database container to assist with orchestration. On first boot however it takes some time for mysql to initialise the database, so Huginn may fail as the database is not yet ready to be connected to. Once the initialisation is done then reboot the Huginn container and it should be able to bootstrap the database fine.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Backup to Backblaze B2 using restic and rclone</title><enclosure url="https://jdheyburn.co.uk/blog/backup-to-backblaze-b2-using-restic-and-rclone/cover.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/blog/backup-to-backblaze-b2-using-restic-and-rclone/</link>
      <pubDate>Tue, 04 May 2021 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/backup-to-backblaze-b2-using-restic-and-rclone/</guid>-->
      <description>&lt;p&gt;Over the last UK lockdown I spent some time making sure I had backups of my music collection after I had organised them using &lt;a href=&#34;https://beets.readthedocs.io/en/stable/&#34;&gt;beets&lt;/a&gt;. I used this as a good opportunity to ensure I had other backups in place for some other critical files at home too.&lt;/p&gt;
&lt;h2 id=&#34;backup-tools&#34;&gt;Backup tools&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://restic.net/&#34;&gt;Restic&lt;/a&gt; is a backup snapshot tool that given a set of directories, it will split the data into chunks and de-duplicate these chunks to a specified backup repository. You can specify your backup policy to a repository, where it will manage what daily, weekly, or monthly snapshots to keep.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://rclone.org/&#34;&gt;Rclone&lt;/a&gt; is a cloud file transfer tool that allows you to synchronise, copy, etc., any data across a huge library of cloud backends.&lt;/p&gt;
&lt;p&gt;Both of these are open source tools that are free to use. Getting set up on them is beyond the scope of this post.&lt;/p&gt;
&lt;p&gt;Backups should be automated without us having to think of them (at least until you need to restore!), so for this I&amp;rsquo;ll be using systemd unit services with timers.&lt;/p&gt;
&lt;p&gt;Restic is storing the snapshots in repositories on my local NFS server. I&amp;rsquo;m then using &lt;a href=&#34;https://www.backblaze.com/b2/cloud-storage.html&#34;&gt;Backblaze B2&lt;/a&gt; to store these in the cloud. At the time of writing they charge $0.005 GB/month for storage, and $0.01 per GB downloaded. So it costs more to download from B2, but since this is a disaster recovery backup I don&amp;rsquo;t anticipate downloading all that much. Being able to store it cheap over a long term is most important.&lt;/p&gt;
&lt;p&gt;There are a load of other storage services too; here&amp;rsquo;s a list below taken from &lt;a href=&#34;https://www.backblaze.com/b2/cloud-storage.html&#34;&gt;Backblaze&amp;rsquo;s website&lt;/a&gt;. The other one I was contending with was &lt;a href=&#34;https://wasabi.com/cloud-storage-pricing/#three-info&#34;&gt;Wasabi&lt;/a&gt;; they&amp;rsquo;re only marginally more expensive than B2 for storage ($0.0059 GB/month), but they don&amp;rsquo;t charge for downloads.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;cloud-storage-price-comparison.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/backup-to-backblaze-b2-using-restic-and-rclone//blog/backup-to-backblaze-b2-using-restic-and-rclone/cloud-storage-price-comparison.png&#34;
    alt=&#34;A table showing the cost comparison of B2 versus S3, Azure, and Google Cloud Platform - B2 is the cheapest.&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Cloud storage cost comparisons&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;backup-contents-and-policy&#34;&gt;Backup contents and policy&lt;/h2&gt;
&lt;p&gt;I have restic set up to backup to two respositories:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;small-files
&lt;ul&gt;
&lt;li&gt;PiHole configuration&lt;/li&gt;
&lt;li&gt;UniFi backups&lt;/li&gt;
&lt;li&gt;Logitech Media Server (LMS) backups&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;media
&lt;ul&gt;
&lt;li&gt;all music files&lt;/li&gt;
&lt;li&gt;beets databases&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Reason being why these are split into two repositories is so that I can define a separate backup policy for each of them.&lt;/p&gt;
&lt;p&gt;For example, small-files contains&amp;hellip; small files. You can see from the list above that these are just configuration files or even backups of files themselves. Since config files are important, I&amp;rsquo;d like the option to go back to a particular setting from weeks or maybe months ago. So the snapshots I want to keep for this repository is 30 daily snapshots, 5 weekly snapshots, 12 monthly snapshots, and 3 yearly snapshots. It might be a bit overkill, but I can always reduce that number and restic will remove them.&lt;/p&gt;
&lt;p&gt;Whereas for media, these files tend to be much larger which will be reflected in the repository size. Especially since music files don&amp;rsquo;t tend to change over time - all I&amp;rsquo;m looking for is a way of reverting back to a state I had recently in case I did some reorganising with beets. Based on this I only need to keep 30 daily snapshots.&lt;/p&gt;
&lt;p&gt;Also in the media repository are the beets databases. I&amp;rsquo;ll expand on how I have this set up in a future post, but for now think of it as a database containing the metadata for your music collection. It&amp;rsquo;s useful to include this with my music files so that I can reflect on the state of my collection at a particular point in time.&lt;/p&gt;
&lt;h2 id=&#34;backup-procedure&#34;&gt;Backup procedure&lt;/h2&gt;
&lt;p&gt;I have a 2TB USB hard drive connected to a Pi (dee) which is configured to be the NFS server for my network, along with PiHole for DNS, and a UniFi controller too. The drive holds my music collection and the restic local repositories.&lt;/p&gt;
&lt;p&gt;LMS sits on a different machine to dee so we&amp;rsquo;ll need to retrieve the files first before we perform a snapshot.&lt;/p&gt;
&lt;p&gt;Once restic has taken snapshots and stored them on the USB drive, we&amp;rsquo;ll need to upload the backups to B2 with rclone.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;N.B. the configuration to automate restic with systemd was largely inspired from a post on &lt;a href=&#34;https://fedoramagazine.org/automate-backups-with-restic-and-systemd/&#34;&gt;Fedora Magazine&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can view the code for all these scripts and systemd unit files at my &lt;a href=&#34;https://github.com/jdheyburn/dotfiles/tree/master/restic&#34;&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;retrieving-backups-from-remote-servers&#34;&gt;Retrieving backups from remote servers&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ll have the restic backup script call another script, &lt;code&gt;pull-backups.sh&lt;/code&gt; to retrieve remote files that I want to back up.&lt;/p&gt;
&lt;p&gt;For now I only need to retrieve LMS backups from &lt;a href=&#34;https://www.picoreplayer.org/&#34;&gt;PiCorePlayer&lt;/a&gt; - for which there is a handy command to help with that: &lt;code&gt;pcp bu&lt;/code&gt;. Don&amp;rsquo;t get too invested in the file path locations, these are specific to PiCorePlayer.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# pull-backups.sh&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# Invoke and pull backups from remote servers and place them on USB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;function&lt;/span&gt; pcp&lt;span style=&#34;color:#ff79c6&#34;&gt;()&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;fpath&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;/mnt/mmcblk0p2/tce&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;fname&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;mydata&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;ext&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;tgz&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;now&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;$(&lt;/span&gt;date -u +&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;%Y-%m-%dT%H%M%S&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;dstPath&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;/mnt/usb/Backup/lms/*.&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;ext&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;removing previous backups...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    rm -rf &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$dstPath&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;sourceFile&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;fpath&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;fname&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;ext&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;dstFile&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;/mnt/usb/Backup/lms/&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;fname&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;_&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;now&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;ext&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;starting pcp backup&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ssh -i /home/jdheyburn/.ssh/pcp tc@pcp.joannet.casa -C &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;pcp bu&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;copying &lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$sourceFile&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt; on remote to &lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$dstFile&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    scp -i /home/jdheyburn/.ssh/pcp &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;tc@pcp.joannet.casa:&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;sourceFile&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$dstFile&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;function&lt;/span&gt; main&lt;span style=&#34;color:#ff79c6&#34;&gt;()&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;entered &lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$0&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    pcp
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;main &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$@&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This takes the backup created on the remote at &lt;code&gt;/mnt/mmcblk0p2/tce/mydata.tgz&lt;/code&gt; and places it locally at &lt;code&gt;/mnt/usb/Backup/lms/mydata_DATETIME.tgz&lt;/code&gt;. This &lt;code&gt;lms&lt;/code&gt; directory can then be used as a backup path for restic.&lt;/p&gt;
&lt;p&gt;Should I need to retrieve backups from any other servers, I&amp;rsquo;ll write a new function here and append it to &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;restic-backup&#34;&gt;Restic backup&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# restic-all.sh&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# Master script for backing up anything to do with restic&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;function&lt;/span&gt; do_restic&lt;span style=&#34;color:#ff79c6&#34;&gt;()&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;mode&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;target&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;[&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$mode&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;backup&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;]&lt;/span&gt;; &lt;span style=&#34;color:#ff79c6&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Backing up &lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$target&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        restic backup --verbose --tag systemd.timer &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$BACKUP_EXCLUDES&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$BACKUP_PATHS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Forgetting old &lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$target&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        restic forget --verbose --tag systemd.timer --group-by &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;paths,tags&amp;#34;&lt;/span&gt; --keep-daily &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$RETENTION_DAYS&lt;/span&gt; --keep-weekly &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$RETENTION_WEEKS&lt;/span&gt; --keep-monthly &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$RETENTION_MONTHS&lt;/span&gt; --keep-yearly &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$RETENTION_YEARS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;elif&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;[&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$mode&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;prune&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;]&lt;/span&gt;; &lt;span style=&#34;color:#ff79c6&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;pruning &lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$target&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        restic --verbose prune
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;function&lt;/span&gt; small_files&lt;span style=&#34;color:#ff79c6&#34;&gt;()&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;BACKUP_PATHS&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;/var/lib/unifi/backup/autobackup /etc/pihole /mnt/usb/Backup/lms&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;BACKUP_EXCLUDES&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;RETENTION_DAYS&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;7&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;RETENTION_WEEKS&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;5&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;RETENTION_MONTHS&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;12&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;RETENTION_YEARS&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;3&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;RESTIC_REPOSITORY&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;/mnt/usb/Backup/restic/small-files&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;RESTIC_PASSWORD_FILE&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;/home/restic/.resticpw&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;mode&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    do_restic &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$mode&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;small files&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;function&lt;/span&gt; media&lt;span style=&#34;color:#ff79c6&#34;&gt;()&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;BACKUP_PATHS&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;/mnt/usb/Backup/media/beets-db /mnt/usb/Backup/media/lossless /mnt/usb/Backup/media/music /mnt/usb/Backup/media/vinyl&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;BACKUP_EXCLUDES&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;RETENTION_DAYS&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;30&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;RETENTION_WEEKS&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;RETENTION_MONTHS&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;RETENTION_YEARS&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;RESTIC_REPOSITORY&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;/mnt/usb/Backup/restic/media&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;RESTIC_PASSWORD_FILE&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;/home/restic/.resticmediapw&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;mode&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    do_restic &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$mode&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;media&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;function&lt;/span&gt; main&lt;span style=&#34;color:#ff79c6&#34;&gt;()&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;mode&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;[&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$mode&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;backup&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;]&lt;/span&gt;; &lt;span style=&#34;color:#ff79c6&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        /home/jdheyburn/dotfiles/restic/pull-backups.sh
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    small_files &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$mode&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    media &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$mode&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;main &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$@&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This script is a bit messy, but it gets the job done. It takes in an argument which can be either &lt;code&gt;backup&lt;/code&gt; or &lt;code&gt;prune&lt;/code&gt;, depending on what task needs to run. If the mode is &lt;code&gt;backup&lt;/code&gt; then it will invoke the &lt;code&gt;pull-backups.sh&lt;/code&gt; prior to snapshotting so that it has the latest files to backup. I cover the &lt;code&gt;prune&lt;/code&gt; function &lt;a href=&#34;https://jdheyburn.co.uk/blog/backup-to-backblaze-b2-using-restic-and-rclone/#removing-aged-restic-snapshots&#34;&gt;later&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Both restic repositories will be kept under &lt;code&gt;/mnt/usb/Backup/restic&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Once the script is defined we can have systemd invoke it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-systemd&#34; data-lang=&#34;systemd&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# /etc/systemd/system/restic-all.service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;[Unit]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;Description&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;Restic backup everything service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;OnFailure&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;unit-status-mail@%n.service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;[Service]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;Type&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;oneshot&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;ExecStart&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;/home/jdheyburn/dotfiles/restic/restic-all.sh backup&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;[Install]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;WantedBy&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;multi-user.target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can see that we&amp;rsquo;re passing in the mode as an argument at &lt;code&gt;ExecStart&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We can perform a test to make sure everything is defined correctly by running &lt;code&gt;systemctl start restic-all.service&lt;/code&gt;, and viewing the logs back at &lt;code&gt;journalctl -u restic-all.service -f&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In order to then have this systemd unit invoked on a regular occurrence, we need to define a &lt;code&gt;timer&lt;/code&gt; for this unit.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-systemd&#34; data-lang=&#34;systemd&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# /etc/systemd/system/restic-all.timer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;[Unit]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;Description&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;Backup with restic daily&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;[Timer]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;OnCalendar&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;*-*-* 2:00:00&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;Persistent&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;[Install]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;WantedBy&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;timers.target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will invoke the service at 2am every day once we enable it with &lt;code&gt;systemctl enable restic-all.timer&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We can have a look at the resulting snapshots with the below command.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ restic -r /mnt/usb/Backup/restic/small-files/ snapshots
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;repository 79dbc9b6 opened successfully, password is correct
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;found &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt; old cache directories in /root/.cache/restic, run &lt;span style=&#34;color:#f1fa8c&#34;&gt;`&lt;/span&gt;restic cache --cleanup&lt;span style=&#34;color:#f1fa8c&#34;&gt;`&lt;/span&gt; to remove them
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ID        Time                 Host        Tags           Paths
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;------------------------------------------------------------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;d93573f4  2020-06-30 02:00:01  dee         systemd.timer  /etc/pihole
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                                          /var/lib/unifi/backup/autobackup
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;96ecf97e  2020-07-31 02:00:35  dee         systemd.timer  /etc/pihole
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                                          /var/lib/unifi/backup/autobackup
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;d2b0a78e  2020-08-31 02:00:21  dee         systemd.timer  /etc/pihole
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                                          /var/lib/unifi/backup/autobackup
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;... &lt;span style=&#34;color:#6272a4&#34;&gt;# removed for brevity&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ec6fd77e  2021-05-03 02:00:34  dee         systemd.timer  /etc/pihole
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                                          /mnt/usb/Backup/lms
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                                          /var/lib/unifi/backup/autobackup
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;722fcbbf  2021-05-04 02:00:34  dee         systemd.timer  /etc/pihole
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                                          /mnt/usb/Backup/lms
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                                          /var/lib/unifi/backup/autobackup
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;------------------------------------------------------------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bd93f9&#34;&gt;39&lt;/span&gt; snapshots
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;syncing-to-b2-with-rclone&#34;&gt;Syncing to B2 with rclone&lt;/h2&gt;
&lt;p&gt;Restic has now created snapshots and stored them in their respective repositories on the USB drive - but this isn&amp;rsquo;t really a safe place to keep backups since the USB drive could crap out at any moment. We can use rclone to offload the repositories to B2.&lt;/p&gt;
&lt;p&gt;For this I&amp;rsquo;m going to use the same pattern as before for restic; a shell script invoked by systemd.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# rclone-all.sh&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# Master script for backing up all rclone stuff to various clouds&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;RCLONE_CONFIG&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;/home/jdheyburn/.config/rclone/rclone.conf
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;function&lt;/span&gt; main&lt;span style=&#34;color:#ff79c6&#34;&gt;()&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;rcloning beets-db -&amp;gt; gdrive:media/beets-db&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    rclone -v sync /mnt/usb/Backup/media/beets-db gdrive:media/beets-db --config&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;RCLONE_CONFIG&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;rcloning music -&amp;gt; gdrive:media/music&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    rclone -v sync /mnt/usb/Backup/media/music gdrive:media/music --config&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;RCLONE_CONFIG&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;rcloning lossless -&amp;gt; gdrive:media/lossless&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    rclone -v sync /mnt/usb/Backup/media/lossless gdrive:media/lossless --config&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;RCLONE_CONFIG&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;rcloning vinyl -&amp;gt; gdrive:media/vinyl&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    rclone -v sync /mnt/usb/Backup/media/vinyl gdrive:media/vinyl --config&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;RCLONE_CONFIG&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;rcloning restic -&amp;gt; b2:restic&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    rclone -v sync /mnt/usb/Backup/restic b2:iifu8Noi-backups/restic/ --config&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;RCLONE_CONFIG&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Done rcloning&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;main &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$@&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In addition to the restic repository directory, I&amp;rsquo;m also backing up the beets databases and music files. These are going to my Google Drive storage in case I want to hook it up to some other apps that can pull from there.&lt;/p&gt;
&lt;p&gt;Like &lt;code&gt;restic-all.sh&lt;/code&gt; above, this script is also invoked by systemd.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-systemd&#34; data-lang=&#34;systemd&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# /etc/systemd/system/rclone-all.service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# This should be invoked after restic has done doing the daily backup&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;[Unit]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;Description&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;Rclone backup everything service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;After&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;restic-all.service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;OnFailure&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;unit-status-mail@%n.service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;[Service]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;Type&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;oneshot&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;ExecStart&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;/home/jdheyburn/dotfiles/restic/rclone-all.sh&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;[Install]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;WantedBy&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;restic-all.service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can test it with &lt;code&gt;systemctl start rclone-all.service&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Since we want to have rclone invoked after restic has done its thing, we need to specify &lt;code&gt;After=restic-all.service&lt;/code&gt; and &lt;code&gt;WantedBy=restic-all.service&lt;/code&gt;. Note that this&amp;rsquo;ll run even if &lt;code&gt;restic-all.service&lt;/code&gt; failed - but that&amp;rsquo;s not really an issue for me since rclone is uploading more than just restic respositories.&lt;/p&gt;
&lt;p&gt;We don&amp;rsquo;t need to specify a systemd &lt;code&gt;.timer&lt;/code&gt; file for this unit file since we&amp;rsquo;re using the completion of &lt;code&gt;restic-all.service&lt;/code&gt; as our invocation point, so we can start that service in order to test it&amp;rsquo;ll run afterward.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;systemctl start restic-all.service
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;handling-service-failures&#34;&gt;Handling service failures&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;N.B. big thanks to &lt;a href=&#34;https://northernlightlabs.se/&#34;&gt;Laeffe&lt;/a&gt; for their &lt;a href=&#34;https://northernlightlabs.se/2014-07-05/systemd-status-mail-on-unit-failure.html&#34;&gt;excellent guide&lt;/a&gt; on this.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Automated backups are very useful for setting and forgetting, but when something goes wrong and backups haven&amp;rsquo;t occurred, we need to be made aware of it.&lt;/p&gt;
&lt;p&gt;We can configure each of the systemd files to invoke a script on failure which can then send us an email when this happens.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll notice that each of the systemd unit files have &lt;code&gt;OnFailure=unit-status-mail@%n.service&lt;/code&gt; defined in them. This is an additional unit file where if the scripts fail, this service will be invoked.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-systemd&#34; data-lang=&#34;systemd&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# /etc/systemd/system/unit-status-mail@.service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;[Unit]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;Description&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;Unit Status Mailer Service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;After&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;network.target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;[Service]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;Type&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;simple&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;ExecStart&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;/home/jdheyburn/dotfiles/restic/systemd/unit-status-mail.sh %I &amp;#34;Hostname: %H&amp;#34; &amp;#34;Machine ID: %m&amp;#34; &amp;#34;Boot ID: %b&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;@&lt;/code&gt; symbol in the filename is a special case within systemd that &lt;a href=&#34;https://superuser.com/a/393429&#34;&gt;enables special functionality&lt;/a&gt;. In our case when we invoked it at &lt;code&gt;OnFailure=unit-status-mail@%n.service&lt;/code&gt; the calling unit file will pass its name via &lt;code&gt;%n&lt;/code&gt; to allow the receiving unit file to access it at &lt;code&gt;%I&lt;/code&gt;. So when the &lt;code&gt;restic-all&lt;/code&gt; service fails, this value ends up in &lt;code&gt;unit-status-mail@.service&lt;/code&gt; at &lt;code&gt;%I&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This &lt;code&gt;%I&lt;/code&gt; variable is then being passed as an argument to the script &lt;code&gt;unit-status-mail.sh&lt;/code&gt;. The contents of the script are below:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# unit-status-mail.sh&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;#!/bin/bash&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# From https://northernlightlabs.se/2014-07-05/systemd-status-mail-on-unit-failure.html&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;MAILTO&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ADD_EMAIL_HERE&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;MAILFROM&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;unit-status-mailer&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;UNIT&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;EXTRA&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; e in &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;@:&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#ff79c6&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;EXTRA&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;+=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$e&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;$&amp;#39;\n&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;UNITSTATUS&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;$(&lt;/span&gt;systemctl status &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$UNIT&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sendmail &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$MAILTO&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;From:$MAILFROM
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;To:$MAILTO
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;Subject:Status mail for unit: $UNIT
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;Status report for unit: $UNIT
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;$EXTRA
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;$UNITSTATUS
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; -e &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Status mail sent to: &lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$MAILTO&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt; for unit: &lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$UNIT&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The name of the calling unit file is passed to &lt;code&gt;UNIT&lt;/code&gt; where the status of it is received, and the output is sent in an email to &lt;code&gt;MAILTO&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll need to make sure you have &lt;code&gt;sendmail&lt;/code&gt; installed on the machine to do this.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo apt install sendmail -y
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For me, the emails landed in the spam folder - but once you configure a rule on your email client to always forward them to your inbox, you&amp;rsquo;ll always be notified if there&amp;rsquo;s an error.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;status-email.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/backup-to-backblaze-b2-using-restic-and-rclone//blog/backup-to-backblaze-b2-using-restic-and-rclone/status-email.png&#34;
    alt=&#34;A screenshot showing an example email highlighting there has been a failure in the restic-all service.&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;The resulting email&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;removing-aged-restic-snapshots&#34;&gt;Removing aged restic snapshots&lt;/h2&gt;
&lt;p&gt;One last area to look at with restic is &lt;a href=&#34;https://restic.readthedocs.io/en/latest/060_forget.html&#34;&gt;pruning&lt;/a&gt;, this is where restic will remove old data that has been &amp;ldquo;forgotten&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Back in &lt;code&gt;restic-all.sh&lt;/code&gt; we are calling &lt;code&gt;restic forget&lt;/code&gt; after each backup - this tells restic to clean up any old snapshots not required by our defined backup policy. The script has been written to accommodate for both &lt;code&gt;backup&lt;/code&gt; and &lt;code&gt;prune&lt;/code&gt; functionality, so in order to execute that portion of the script we need to set up another systemd unit file to invoke it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-systemd&#34; data-lang=&#34;systemd&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# /etc/systemd/system/restic-prune.service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;[Unit]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;Description&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;Restic backup service (data pruning)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;OnFailure&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;unit-status-mail@%n.service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;[Service]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;Type&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;oneshot&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;ExecStart&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;/home/jdheyburn/dotfiles/restic/restic-all.sh prune&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Since it is resource intensive to perform &lt;code&gt;prune&lt;/code&gt; against your repository, its best to run this at a different interval to your backups. From the below unit file &lt;code&gt;OnCalendar=*-*-1 10:00:00&lt;/code&gt; corresponds to the first day of the month at 10am.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-systemd&#34; data-lang=&#34;systemd&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;[Unit]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;Description&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;Prune data from the restic repository monthly&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;[Timer]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;OnCalendar&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;*-*-1 10:00:00&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;Persistent&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;[Install]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#50fa7b&#34;&gt;WantedBy&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;timers.target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;restoring-a-backup&#34;&gt;Restoring a backup&lt;/h2&gt;
&lt;p&gt;Now the most important part - how to restore a restic snapshot. Let&amp;rsquo;s start with how to restore from a local repository.&lt;/p&gt;
&lt;h3 id=&#34;restore-from-local-restic-repository&#34;&gt;Restore from local restic repository&lt;/h3&gt;
&lt;p&gt;Firstly we need to find the snapshot ID that we want to restore to - in this example I want the latest snapshot.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ restic -r /mnt/usb/Backup/restic/small-files/ snapshots --last
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;repository 79dbc9b6 opened successfully, password is correct
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;found &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt; old cache directories in /root/.cache/restic, run &lt;span style=&#34;color:#f1fa8c&#34;&gt;`&lt;/span&gt;restic cache --cleanup&lt;span style=&#34;color:#f1fa8c&#34;&gt;`&lt;/span&gt; to remove them
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ID        Time                 Host        Tags           Paths
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;------------------------------------------------------------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;f7ef9c33  2021-04-17 02:00:51  dee         systemd.timer  /etc/pihole
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                                          /mnt/usb/Backup/media/beets-db
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                                          /var/lib/unifi/backup/autobackup
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;e2a73c00  2021-04-21 02:00:21  dee         systemd.timer  /etc/pihole
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                                          /var/lib/unifi/backup/autobackup
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;722fcbbf  2021-05-04 02:00:34  dee         systemd.timer  /etc/pihole
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                                          /mnt/usb/Backup/lms
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                                          /var/lib/unifi/backup/autobackup
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;------------------------------------------------------------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bd93f9&#34;&gt;3&lt;/span&gt; snapshots
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So the ID is &lt;code&gt;722fcbbf&lt;/code&gt;, let&amp;rsquo;s browse the contents to see if the file we want is in there.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ restic -r /mnt/usb/Backup/restic/small-files/ ls 722fcbbf
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;repository 79dbc9b6 opened successfully, password is correct
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;found &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt; old cache directories in /root/.cache/restic, run &lt;span style=&#34;color:#f1fa8c&#34;&gt;`&lt;/span&gt;restic cache --cleanup&lt;span style=&#34;color:#f1fa8c&#34;&gt;`&lt;/span&gt; to remove them
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;snapshot 722fcbbf of &lt;span style=&#34;color:#ff79c6&#34;&gt;[&lt;/span&gt;/var/lib/unifi/backup/autobackup /etc/pihole /mnt/usb/Backup/lms&lt;span style=&#34;color:#ff79c6&#34;&gt;]&lt;/span&gt; filtered by &lt;span style=&#34;color:#ff79c6&#34;&gt;[]&lt;/span&gt; at 2021-05-04 02:00:34.383403601 +0100 BST&lt;span style=&#34;color:#ff79c6&#34;&gt;)&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;/etc
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;/etc/pihole
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;/etc/pihole/GitHubVersions
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;/etc/pihole/adlists.list
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# ... removed for brevity&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s say we want to restore &lt;code&gt;/etc/pihole/adlists.list&lt;/code&gt;, we can use the &lt;code&gt;--include&lt;/code&gt; argument to specify just that. If we didn&amp;rsquo;t use a filter argument then restic would default to restoring the entire contents of the snapshot.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ restic -r /mnt/usb/Backup/restic/small-files/ restore 722fcbbf --target /tmp/restic-restore --include /etc/pihole/adlists.list
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;repository 79dbc9b6 opened successfully, password is correct
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;found &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt; old cache directories in /root/.cache/restic, run &lt;span style=&#34;color:#f1fa8c&#34;&gt;`&lt;/span&gt;restic cache --cleanup&lt;span style=&#34;color:#f1fa8c&#34;&gt;`&lt;/span&gt; to remove them
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;restoring &amp;lt;Snapshot 722fcbbf of &lt;span style=&#34;color:#ff79c6&#34;&gt;[&lt;/span&gt;/var/lib/unifi/backup/autobackup /etc/pihole /mnt/usb/Backup/lms&lt;span style=&#34;color:#ff79c6&#34;&gt;]&lt;/span&gt; at 2021-05-04 02:00:34.383403601 +0100 BST by root@dee&amp;gt; to /tmp/restic-restore
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ tree /tmp/restic-restore/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;/tmp/restic-restore/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;└── etc
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    └── pihole
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        └── adlists.list
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;restore-from-b2-restic-repository&#34;&gt;Restore from B2 restic repository&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;re storing the repositories on B2 too, and restic has B2 integration built into it. So in the scenario where the NFS server had died and we needed to restore a snapshot stored on B2, we can hit it directly without having to use rclone to pull down the entire repository for us to restore from. This&amp;rsquo;ll be a cheaper approach as restic is only pulling down the files it needs to restore from, lowering B2 download costs.&lt;/p&gt;
&lt;p&gt;We just need to configure some variables to permit restic to hit B2. If you&amp;rsquo;re using rclone you can use the same ID and key here.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;B2_ACCOUNT_ID&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ACCCOUNT_ID&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;B2_ACCOUNT_KEY&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ACCCOUNT_KEY&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;RESTIC_PASSWORD_FILE&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;/path/to/passwordfile
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;restic -r b2:BUCKET_NAME:restic/small-files snapshots --last
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;From here you can then use the same commands as in the local repository to traverse the snapshots and restore.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The process above is probably more complex than what it needs to be; having a separate process for backing up and restoring. Since I want to back up to the NFS locally first followed by B2, this approach made the most sense as it allows me to minimise download costs from B2 through targeting the local repository first.&lt;/p&gt;
&lt;p&gt;What&amp;rsquo;s most important is that backups are being made, and that I &lt;em&gt;can&lt;/em&gt; restore from them.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How to automate zero downtime maintenance with AWS SSM &amp; ALBs</title><enclosure url="https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-3/cover.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-3/</link>
      <pubDate>Tue, 19 Jan 2021 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-3/</guid>-->
      <description>&lt;p&gt;Welcome to the last post in this &lt;a href=&#34;https://jdheyburn.co.uk/series/automate-instance-hygiene-with-aws-ssm/&#34;&gt;series&lt;/a&gt; where we&amp;rsquo;ve been exploring SSM Documents, so far we&amp;rsquo;ve covered:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-0/&#34;&gt;Command Documents&lt;/a&gt; can help to execute commands on EC2 Instances&lt;/li&gt;
&lt;li&gt;Automating these Command Documents through &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-1/&#34;&gt;Maintenance Windows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Safely chaining Command Documents through &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2/&#34;&gt;Automation Documents&lt;/a&gt;, and aborting for any failures&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This post will now look into how we can use Automation Documents to perform maintenance on EC2 instances without impacting user experience.&lt;/p&gt;
&lt;h2 id=&#34;tldr&#34;&gt;tl;dr&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;With the introduction of load balancers to front your services, you can control which instances should be receiving traffic&lt;/li&gt;
&lt;li&gt;This enables you to proactively remove instances from rotation so that you can perform maintenance on the backends to minimalise user disruption&lt;/li&gt;
&lt;li&gt;SSM automation documents can enable us to execute pre-maintenance steps such as removing an instance from a load balancer, as well as adding them back after
&lt;ul&gt;
&lt;li&gt;See the &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-3/documents/graceful_patch_instance.yml&#34;&gt;document&lt;/a&gt; produced in this post highlighting this, and where I &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-3/#graceful-load-balancer-document&#34;&gt;explain&lt;/a&gt; how it works, and &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-3/#terraform-additions-and-updates&#34;&gt;how to deploy&lt;/a&gt; it using Terraform&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re just joining in from this post then I recommend reading through the previous posts to gain an understanding of how we got here; or if you know what you&amp;rsquo;re looking for the tl;dr provides a summary.&lt;/p&gt;
&lt;p&gt;As always, the code for this post can be found on &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/tree/main/aws-ssm-automation-3&#34;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A basic understanding and knowledge of &lt;a href=&#34;https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html&#34;&gt;Application Load Balancers&lt;/a&gt; (ALB), and its components (e.g. &lt;a href=&#34;https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-target-groups.html&#34;&gt;target groups&lt;/a&gt;) is required.&lt;/p&gt;
&lt;h2 id=&#34;introducing-load-balancers&#34;&gt;Introducing load balancers&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/Load_balancing_(computing)&#34;&gt;Load balancers&lt;/a&gt; are a key component in software architecture that distribute traffic and requests across backend services in a variety of algorithms, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Round-robin
&lt;ul&gt;
&lt;li&gt;every backend serves the same number of requests&lt;/li&gt;
&lt;li&gt;the most commonly used algorithm&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Weighted round-robin
&lt;ul&gt;
&lt;li&gt;backends receive a fixed percentage of incoming requests&lt;/li&gt;
&lt;li&gt;useful if some backends are more beefy than others&lt;/li&gt;
&lt;li&gt;also used in &lt;a href=&#34;https://martinfowler.com/bliki/CanaryRelease.html&#34;&gt;canary deployments&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Least outstanding requests
&lt;ul&gt;
&lt;li&gt;the backend which is currently processing the least number of requests is forwarded the request&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are multiple benefits to having a load balancer sit in front of your services:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Protecting your infrastructure; user requests are proxied via the load balancer&lt;/li&gt;
&lt;li&gt;Distribute traffic and requests however you like&lt;/li&gt;
&lt;li&gt;Perform healthchecks on backends and don&amp;rsquo;t forward traffic to unhealthy nodes&lt;/li&gt;
&lt;li&gt;Drain and remove backends to permit for rolling upgrades&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While there are several different software-based load balancers out there such as &lt;a href=&#34;https://www.nginx.com/&#34;&gt;nginx&lt;/a&gt; and &lt;a href=&#34;https://www.haproxy.org/&#34;&gt;HAProxy&lt;/a&gt;, AWS has its own managed load balancer service known as an &lt;a href=&#34;https://aws.amazon.com/elasticloadbalancing/&#34;&gt;Elastic Load Balancer&lt;/a&gt; (ELB).&lt;/p&gt;
&lt;h3 id=&#34;adding-web-services-to-our-demo-environment&#34;&gt;Adding web services to our demo environment&lt;/h3&gt;
&lt;p&gt;In order for us to get the benefit of load balancers to front the EC2 instances in the architecture this series left off from &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2/#prerequisites&#34;&gt;last time&lt;/a&gt;, we will need to have a service running on our instances.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s simulate a real web service by running a simple Hello World application across each of the instances. We can utilise EC2s &lt;a href=&#34;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html&#34;&gt;user data&lt;/a&gt; to start a basic service up for us by giving it a script to run on instance provision.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;alb-arch.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-3//blog/automate-instance-hygiene-with-aws-ssm-3/alb-arch.png&#34;
    alt=&#34;An architecture diagram showing a user with an arrow pointing to an application load balancer on port 80. The load balancer then points to 3 EC2 instances on port 8080.&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;This is the architecture we&amp;rsquo;ll be building out in this section, with an ALB fronting our EC2 instances&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Let&amp;rsquo;s use Go to create a web service for us since it is easy to get set up quickly - we&amp;rsquo;ll have the server return a simple &lt;code&gt;Hello, World!&lt;/code&gt; message when a request hits it. Let&amp;rsquo;s also have it return the name of the instance that was hit - this will be used &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-3/#hitting-the-load-balancer&#34;&gt;later&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;&lt;/span&gt;cat &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;lt;&amp;lt;&amp;#39;EOF&amp;#39; &amp;gt; /home/ec2-user/main.go
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;package main
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;import (
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        &amp;#34;fmt&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        &amp;#34;net/http&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        &amp;#34;os&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;func main() {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        http.HandleFunc(&amp;#34;/&amp;#34;, HelloServer)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        http.ListenAndServe(&amp;#34;:8080&amp;#34;, nil)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;func HelloServer(w http.ResponseWriter, r *http.Request) {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        hostname, _ := os.Hostname()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        fmt.Fprintf(w, &amp;#34;Hello, World! From %v\n&amp;#34;, hostname)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;yum install golang -y
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;(&lt;/span&gt;crontab -l 2&amp;gt;/dev/null; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;@reboot nohup go run /home/ec2-user/main.go&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;)&lt;/span&gt; | crontab -
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;GOCACHE&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;/tmp/go-cache
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;nohup go run /home/ec2-user/main.go
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So on instance creation this will:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a new file called &lt;code&gt;main.go&lt;/code&gt; and populate it with the lines between the &lt;code&gt;EOF&lt;/code&gt; delimiters&lt;/li&gt;
&lt;li&gt;Install Go&lt;/li&gt;
&lt;li&gt;Create a &lt;a href=&#34;https://en.wikipedia.org/wiki/Cron&#34;&gt;crontab&lt;/a&gt; entry to run the service on subsequent boots&lt;/li&gt;
&lt;li&gt;Run the Go application in the background immediately&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;While user data is for useful provisioning services, I don&amp;rsquo;t advise storing the source code of your application in there like I&amp;rsquo;ve done - this is just a hacky way to get something up and running.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We&amp;rsquo;ve been using Terraform to provision our nodes. So we need to save this script in a &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-3/scripts/hello_world_user_data.sh&#34;&gt;file&lt;/a&gt; (&lt;code&gt;scripts/hello_world_user_data.sh&lt;/code&gt;), and then pass it into the &lt;code&gt;user_data&lt;/code&gt; attribute of our &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-3/ec2.tf#L27&#34;&gt;EC2 module&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;module&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;hello_world_ec2&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  source         &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;terraform-aws-modules/ec2-instance/aws&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  version        &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;~&amp;gt; 2.0&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;  # ... removed for brevity
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  user_data &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;file&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;scripts/hello_world_user_data.sh&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Terraform will recreate any EC2 nodes with a change in &lt;code&gt;user_data&lt;/code&gt; contents, so when you invoke &lt;code&gt;terraform apply&lt;/code&gt; all instances will be recreated.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;N.B. the AMI I picked up for my EC2 instances has an issue with SSM Agent. Ensure you execute the &lt;code&gt;AWS-UpdateSSMAgent&lt;/code&gt; across your instances after they have provisioned, or you can use an &lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-state-about.html&#34;&gt;SSM Association&lt;/a&gt; document to do that for you as &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-3/update_ssm_agent_association.tf&#34;&gt;shown here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;After they have all successfully deployed, you should be able to &lt;code&gt;curl&lt;/code&gt; the public IP address of each instance from your machine to verify your setup is correct. If you are getting timeouts then make sure your instances have a security group rule permitting traffic from your IP address through port 8080.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ curl http://54.229.209.60:8080
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! From ip-172-31-39-169.eu-west-1.compute.internal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ curl http://3.250.160.209:8080
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! From ip-172-31-21-197.eu-west-1.compute.internal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ curl http://34.254.238.146:8080
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! From ip-172-31-8-52.eu-west-1.compute.internal
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;fronting-instances-with-a-load-balancer&#34;&gt;Fronting instances with a load balancer&lt;/h3&gt;
&lt;p&gt;Now that we have a web service hosted on our instances, let&amp;rsquo;s now add a load balancer in front of it. This load balancer will now become the point of entry for our application instead of hitting the EC2 instances directly.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For this I am using a Terraform &lt;a href=&#34;https://registry.terraform.io/modules/terraform-aws-modules/alb/aws/latest&#34;&gt;ALB module&lt;/a&gt; for provisioning all the components in the load balancer, and expanding on them is beyond the scope of this post.&lt;/p&gt;
&lt;p&gt;You can navigate to the AWS ALB &lt;a href=&#34;https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html&#34;&gt;documentation&lt;/a&gt; to find out about the underlying components the module creates for us.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&#34;security-groups&#34;&gt;Security groups&lt;/h4&gt;
&lt;p&gt;Before we can provision the load balancer, we need to specify the security group (SG) and the rules that should be applied to it. You can view this on &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-3/alb.tf#L45&#34;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Since our application is written to serve request on port 8080, we need to permit both the new &lt;code&gt;aws_security_group.hello_world_alb&lt;/code&gt; SG and the existing &lt;code&gt;aws_security_group.vm_base&lt;/code&gt; SG to communicate between each other.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_security_group&amp;#34; &amp;#34;hello_world_alb&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name   &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;HelloWorldALB&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  vpc_id &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;data&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;aws_vpc&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;default&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_security_group_rule&amp;#34; &amp;#34;alb_egress_ec2&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  security_group_id        &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_security_group&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;hello_world_alb&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  type                     &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;egress&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  from_port                &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  to_port                  &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  protocol                 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;tcp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  source_security_group_id &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_security_group&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;vm_base&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_security_group_rule&amp;#34; &amp;#34;ec2_ingress_alb&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  security_group_id        &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_security_group&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;vm_base&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  type                     &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ingress&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  from_port                &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  to_port                  &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  protocol                 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;tcp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  source_security_group_id &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_security_group&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;hello_world_alb&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we need to open up the ALB to allow traffic to hit it. In our case it will just be us hitting it, but this will change depending on who the consumer of the service is. If it is to serve traffic from the Internet then &lt;code&gt;cidr_blocks&lt;/code&gt; would be &lt;code&gt;[&amp;quot;0.0.0.0/0&amp;quot;]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The ALB will be hosting the traffic on insecure HTTP (port 80).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_security_group_rule&amp;#34; &amp;#34;alb_ingress_user&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  security_group_id &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_security_group&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;hello_world_alb&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  type              &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ingress&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  from_port         &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;80&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  to_port           &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;80&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  protocol          &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;tcp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  cidr_blocks       &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#ff79c6&#34;&gt;local&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;ip_address&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;alb-module&#34;&gt;ALB module&lt;/h4&gt;
&lt;p&gt;The &lt;a href=&#34;https://registry.terraform.io/modules/terraform-aws-modules/alb/aws/latest&#34;&gt;module documentation&lt;/a&gt; will tell us how we need to structure it. Our requirements dictate we need the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Receive traffic on port 80&lt;/li&gt;
&lt;li&gt;Forward traffic to backend targets on port 8080&lt;/li&gt;
&lt;li&gt;Include health checks to ensure we do not forward requests to unhealthy instances&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Translate these requirements into the context of the module and we have &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-3/alb.tf#L1&#34;&gt;something like this&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;module&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;hello_world_alb&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  source  &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;terraform-aws-modules/alb/aws&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  version &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;~&amp;gt; 5.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;HelloWorldALB&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  load_balancer_type &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;application&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  vpc_id          &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;data&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;aws_vpc&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;default&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  subnets         &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;tolist&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;data&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;aws_subnet_ids&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;all&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;ids&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  security_groups &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#ff79c6&#34;&gt;aws_security_group&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;hello_world_alb&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  target_groups &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      name_prefix      &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;pref-&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      backend_protocol &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;HTTP&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      backend_port     &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      target_type      &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;instance&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      health_check &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        enabled             &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#8be9fd&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        healthy_threshold   &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        unhealthy_threshold &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        interval            &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;6&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  http_tcp_listeners &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      port               &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;80&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      protocol           &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;HTTP&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      target_group_index &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;In order to keep this post simple I am not fronting services over HTTPS (secure HTTP) - I would strongly advise against doing this for non-test scenarios.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Another step we will need is to hook up our EC2 instances with the target group created.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_lb_target_group_attachment&amp;#34; &amp;#34;hello_world_tg_att&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  count            &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;length&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;module&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;hello_world_ec2&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  target_group_arn &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;module&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;hello_world_alb&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;target_group_arns&lt;/span&gt;[&lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  target_id        &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;element&lt;/span&gt;(&lt;span style=&#34;color:#ff79c6&#34;&gt;module&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;hello_world_ec2&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;count&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;index&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  port             &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There&amp;rsquo;s some clever Terraform going on here. All we&amp;rsquo;re doing is looping over each of the created EC2 instances in the module and adding it to the target group, to receive traffic on port 8080.&lt;/p&gt;
&lt;h3 id=&#34;hitting-the-load-balancer&#34;&gt;Hitting the load balancer&lt;/h3&gt;
&lt;p&gt;Whereas &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-3/#adding-web-services-to-our-demo-environment&#34;&gt;earlier&lt;/a&gt; when we were testing the services by hitting the EC2 instances directly, we&amp;rsquo;ll now be hitting the ALB instead. You can grab the ALB DNS name from the &lt;a href=&#34;https://console.aws.amazon.com/ec2/v2/home#LoadBalancers:sort=loadBalancerName&#34;&gt;console&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ curl http://HelloWorldALB-128172928.eu-west-1.elb.amazonaws.com:80
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! From ip-172-31-31-223.eu-west-1.compute.internal
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Nice - and we can see from here what instance we&amp;rsquo;ve hit in the backend, since we included the hostname in the response.&lt;/p&gt;
&lt;p&gt;If we now hit the ALB one more time, we will get a different instance respond.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ curl http://HelloWorldALB-128172928.eu-west-1.elb.amazonaws.com:80
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! From ip-172-31-0-10.eu-west-1.compute.internal
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is the load balancer rotating between the backends available to it. We can see the rotation by repeatedly hitting the endpoint.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ &lt;span style=&#34;color:#ff79c6&#34;&gt;while&lt;/span&gt; true; &lt;span style=&#34;color:#ff79c6&#34;&gt;do&lt;/span&gt; curl http://HelloWorldALB-128172928.eu-west-1.elb.amazonaws.com:80 ; sleep 0.5; &lt;span style=&#34;color:#ff79c6&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! From ip-172-31-31-223.eu-west-1.compute.internal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! From ip-172-31-43-11.eu-west-1.compute.internal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! From ip-172-31-0-10.eu-west-1.compute.internal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! From ip-172-31-31-223.eu-west-1.compute.internal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! From ip-172-31-0-10.eu-west-1.compute.internal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! From ip-172-31-43-11.eu-west-1.compute.internal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! From ip-172-31-43-11.eu-west-1.compute.internal
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;so-what-does-all-this-have-to-do-with-our-maintenance-document&#34;&gt;So what does all this have to do with our maintenance document?&lt;/h2&gt;
&lt;p&gt;Now that we have a load balancer fronting our services, let&amp;rsquo;s review executing our automation document from the &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2/#combining-command-docs-into-automation&#34;&gt;last post&lt;/a&gt; and the problem it brings.&lt;/p&gt;
&lt;p&gt;If an instance were to be rebooted during the &lt;strong&gt;AWS-RunPatchBaseline&lt;/strong&gt; stage of the automation document, then there is a chance that a request would have been forwarded to that instance before the health checks against it have failed.&lt;/p&gt;
&lt;p&gt;To simulate this, let&amp;rsquo;s create a new automation document which simulates a reboot. We&amp;rsquo;ll just take our existing &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-3/documents/patch_with_healthcheck_template.yml&#34;&gt;patching document&lt;/a&gt; and replace the patch step with a reboot command, following the &lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/send-commands-reboot.html&#34;&gt;AWS guidelines&lt;/a&gt; to do this. I&amp;rsquo;ve also modified the health check to check to see if the new service came up okay - this may differ for your environment.&lt;/p&gt;
&lt;p&gt;You can view the document on &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-3/documents/reboot_with_healthcheck_template.yml&#34;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;schemaVersion&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;0.3&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: Executes a reboot on the instance followed by a healthcheck
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;parameters&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;InstanceIds&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;type&lt;/span&gt;: StringList
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: The instance to target
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;mainSteps&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: InvokeReboot
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;action&lt;/span&gt;: aws:runCommand
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;DocumentName&lt;/span&gt;: AWS-RunShellScript
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;InstanceIds&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ InstanceIds }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;OutputS3BucketName&lt;/span&gt;: ${output_s3_bucket_name}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;OutputS3KeyPrefix&lt;/span&gt;: ${output_s3_key_prefix}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;Parameters&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;commands&lt;/span&gt;: |&lt;span style=&#34;color:#f1fa8c&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          flag_location=/home/ec2-user/REBOOT_STARTED
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          if [ ! -f $flag_location ]; then
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;            echo &amp;#34;Creating flag file at $flag_location&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;            touch $flag_location
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;            echo &amp;#34;Reboot initiated&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;            exit 194
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          fi
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          echo &amp;#34;Reboot finished, removing flag file at $flag_location&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          rm $flag_location&lt;/span&gt;          
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: ExecuteHealthcheck
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;action&lt;/span&gt;: aws:runCommand
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;DocumentName&lt;/span&gt;: AWS-RunShellScript
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;InstanceIds&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ InstanceIds }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;OutputS3BucketName&lt;/span&gt;: ${output_s3_bucket_name}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;OutputS3KeyPrefix&lt;/span&gt;: ${output_s3_key_prefix}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;Parameters&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;commands&lt;/span&gt;: |&lt;span style=&#34;color:#f1fa8c&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          sleep 60
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          if ! curl http://localhost:8080/; then
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;            exit 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;          fi&lt;/span&gt;          
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then the &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-3/ssm_reboot_with_healthcheck.tf&#34;&gt;Terraform code&lt;/a&gt; to create this document will look like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_ssm_document&amp;#34; &amp;#34;reboot_with_healthcheck&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name            &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;RebootWithHealthcheck&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  document_type   &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Automation&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  document_format &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;YAML&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  content &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;templatefile&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;documents/reboot_with_healthcheck_template.yml&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      output_s3_bucket_name    &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_s3_bucket&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;script_bucket&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      output_s3_key_prefix     &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ssm_output/&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;testing-for-failure&#34;&gt;Testing for failure&lt;/h3&gt;
&lt;p&gt;After this has been applied in our environment let&amp;rsquo;s get our test set up. In a terminal window from your machine have this script running in the background to simulate load.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ &lt;span style=&#34;color:#ff79c6&#34;&gt;while&lt;/span&gt; true;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;resp&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;$(&lt;/span&gt;curl http://HelloWorldALB-128172928.eu-west-1.elb.amazonaws.com:80 2&amp;gt;/dev/null&lt;span style=&#34;color:#ff79c6&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$resp&lt;/span&gt; | grep -q html; &lt;span style=&#34;color:#ff79c6&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;error&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;$(&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$resp&lt;/span&gt; | grep -oPm1 &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;(?&amp;lt;=&amp;lt;title&amp;gt;)[^&amp;lt;]+&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Error - &lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$error&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$resp&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  sleep 0.5
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then we need to invoke the new reboot document against an instance (see &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2/#testing-automation-documents&#34;&gt;here&lt;/a&gt; for how we achieved this last time). Once it is running let&amp;rsquo;s monitor the output of the command in your terminal.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! From ip-172-31-18-158.eu-west-1.compute.internal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! From ip-172-31-14-19.eu-west-1.compute.internal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! From ip-172-31-18-158.eu-west-1.compute.internal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Error - &lt;span style=&#34;color:#bd93f9&#34;&gt;502&lt;/span&gt; Bad Gateway
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Error - &lt;span style=&#34;color:#bd93f9&#34;&gt;502&lt;/span&gt; Bad Gateway
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! From ip-172-31-14-19.eu-west-1.compute.internal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! From ip-172-31-18-158.eu-west-1.compute.internal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Error - &lt;span style=&#34;color:#bd93f9&#34;&gt;502&lt;/span&gt; Bad Gateway
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! From ip-172-31-14-19.eu-west-1.compute.internal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! From ip-172-31-18-158.eu-west-1.compute.internal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! From ip-172-31-14-19.eu-west-1.compute.internal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Error - &lt;span style=&#34;color:#bd93f9&#34;&gt;502&lt;/span&gt; Bad Gateway
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! From ip-172-31-18-158.eu-west-1.compute.internal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Error - &lt;span style=&#34;color:#bd93f9&#34;&gt;502&lt;/span&gt; Bad Gateway
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! From ip-172-31-14-19.eu-west-1.compute.internal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! From ip-172-31-14-19.eu-west-1.compute.internal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! From ip-172-31-18-158.eu-west-1.compute.internal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hello, World! From ip-172-31-18-158.eu-west-1.compute.internal
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Not good! &amp;#x1f627;&lt;/p&gt;
&lt;p&gt;These error messages are coming from the load balancer, &lt;a href=&#34;https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-troubleshooting.html#http-502-issues&#34;&gt;indicating&lt;/a&gt; that the underlying backend wasn&amp;rsquo;t able to complete the request. This is happening when the instance gets rebooted as specified in our document.&lt;/p&gt;
&lt;p&gt;The load balancer hasn&amp;rsquo;t had enough time to determine whether the instance is unhealthy or not - as dictated from our &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-3/alb.tf#L19&#34;&gt;health check policy&lt;/a&gt; (2 failed checks with 6 seconds between them) - and so still forwards traffic to it even though it cannot respond.&lt;/p&gt;
&lt;p&gt;We can actually view this disruption in &lt;a href=&#34;https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/working_with_metrics.html&#34;&gt;CloudWatch Metrics&lt;/a&gt; too. ALBs expose &lt;a href=&#34;https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-cloudwatch-metrics.html#load-balancer-metrics-alb&#34;&gt;metrics&lt;/a&gt; for us which we can monitor against, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RequestCount
&lt;ul&gt;
&lt;li&gt;Informs us how many incoming requests the ALB is receiving&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;HTTPCode_ELB_5XX_Count
&lt;ul&gt;
&lt;li&gt;How many HTTP 5XX error codes are being returned by the ALB&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We can chart them together for visualisation.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;non-graceful-results.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-3//blog/automate-instance-hygiene-with-aws-ssm-3/non-graceful-results.png&#34;
    alt=&#34;A graph in CloudWatch showing the number of requests being served by the ALB, along with occasional HTTP 5XX counts - corresponding at the same time the instances were being rebooted&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;HTTPCode_ELB_5XX_Count only reports on failures - if the metric is missing data points then no errors occurred at that time&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;While we are the only users hitting this, had this been a production box hit by 1,000s of users, each one of them would experience an issue with your application, and equals lost customers! &amp;#x1f621;&lt;/p&gt;
&lt;p&gt;What we need is a means of removing the node from the load balancer rotation so that we can safely perform maintenance on it.&lt;/p&gt;
&lt;h2 id=&#34;removing-instances-from-load-balancer-rotation&#34;&gt;Removing instances from load balancer rotation&lt;/h2&gt;
&lt;p&gt;Load balancer target groups have an API endpoint that allow you to drain connections from backends - where the load balancer stops any &lt;em&gt;new&lt;/em&gt; requests being forwarded to that backend, and allows existing requests to complete. This can be done via - you guessed it - Automation Documents!&lt;/p&gt;
&lt;h3 id=&#34;graceful-load-balancer-document&#34;&gt;Graceful load balancer document&lt;/h3&gt;
&lt;p&gt;You can see the document in its entirety &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-3/documents/graceful_patch_instance.yml&#34;&gt;here&lt;/a&gt;. The steps that it performs are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check that the target group is healthy
&lt;ul&gt;
&lt;li&gt;We want to ensure we&amp;rsquo;re not fuelling a dumpster fire&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Check that the instance we&amp;rsquo;re targeting is in the target group we&amp;rsquo;re modifying
&lt;ul&gt;
&lt;li&gt;Otherwise what&amp;rsquo;s the point? :upside_down:&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Remove the instance from the target group and wait for it to be removed&lt;/li&gt;
&lt;li&gt;Execute our maintenance document&lt;/li&gt;
&lt;li&gt;Register the instance back and wait for it to be added back&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;document-metadata&#34;&gt;Document metadata&lt;/h4&gt;
&lt;p&gt;As this is an automation document, the &lt;code&gt;schemaVersion&lt;/code&gt; should be &lt;code&gt;0.3&lt;/code&gt;. We&amp;rsquo;re using two parameters here to run the document:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TargetGroupArn&lt;/code&gt; - the ARN of the target group we are making modifications too&lt;/li&gt;
&lt;li&gt;&lt;code&gt;InstanceId&lt;/code&gt; - the instance that is undergoing maintenance&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;schemaVersion&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;0.3&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: Gracefully reboot instance with healthchecks
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;parameters&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;InstanceId&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;type&lt;/span&gt;: String
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: The instance to target
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;TargetGroupArn&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;type&lt;/span&gt;: String
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: The target group ARN for the instance
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;sanity-checks&#34;&gt;Sanity checks&lt;/h4&gt;
&lt;p&gt;The first two steps of the document are sanity checking the target group to ensure preconditions are met before we introduce change. We&amp;rsquo;re using the &lt;code&gt;aws:assertAwsResourceProperty&lt;/code&gt; &lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/automation-action-assertAwsResourceProperty.html&#34;&gt;action&lt;/a&gt; to allow us to query against the &lt;a href=&#34;https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/Welcome.html&#34;&gt;AWS ELBv2 API&lt;/a&gt; (Elastic Load Balancer v2) and verify a response is what we expect it to be.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can see all the API endpoints available for ELBv2 at this &lt;a href=&#34;https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_Operations.html&#34;&gt;location&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The response of the &lt;a href=&#34;https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_DescribeTargetHealth.html&#34;&gt;DescribeTargetHealth&lt;/a&gt; endpoint returns an object that is structured like this.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;TargetHealthDescriptions&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;Target&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;Id&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;i-083c8ca9c9b74e1cd&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;Port&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;HealthCheckPort&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;8080&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;TargetHealth&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;State&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;healthy&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;Target&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;Id&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;i-08a63b118a0b2a6b7&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;Port&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;HealthCheckPort&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;8080&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;TargetHealth&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;State&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;healthy&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;Target&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;Id&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;i-08c656b7160dd6729&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;Port&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;HealthCheckPort&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;8080&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;TargetHealth&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;State&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;healthy&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This represents all the backends that are configured against the target group. The property selector &lt;code&gt;$.TargetHealthDescriptions..TargetHealth.State&lt;/code&gt; specified in the document will check against &lt;em&gt;all&lt;/em&gt; state fields to see if they are healthy. If any of them aren&amp;rsquo;t then the document will be aborted. As mentioned before, this check is performed to ensure we&amp;rsquo;re not causing more problems for ourselves if any of the nodes are unhealthy.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: AssertTargetGroupHealthBefore
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: Assert the target group is healthy before we bounce Tomcat
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;action&lt;/span&gt;: aws:assertAwsResourceProperty
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;Service&lt;/span&gt;: elbv2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;Api&lt;/span&gt;: DescribeTargetHealth
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;PropertySelector&lt;/span&gt;: $.TargetHealthDescriptions..TargetHealth.State
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;DesiredValues&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - healthy
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;TargetGroupArn&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ TargetGroupArn }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;maxAttempts&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;timeoutSeconds&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;60&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the next sanity check we&amp;rsquo;re ensuring that the instance is definitely in the target group we want to remove it from. This is a slightly different query to the last step where we&amp;rsquo;re specifically requesting for state health on that one instance.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: AssertInstanceIsInTargetGroup
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: Assert the instance is a healthy target of the target group
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;action&lt;/span&gt;: aws:assertAwsResourceProperty
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;Service&lt;/span&gt;: elbv2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;Api&lt;/span&gt;: DescribeTargetHealth
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;PropertySelector&lt;/span&gt;: $.TargetHealthDescriptions[0].TargetHealth.State
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;DesiredValues&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - healthy
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;TargetGroupArn&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ TargetGroupArn }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;Targets&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;Id&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ InstanceId }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;maxAttempts&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;timeoutSeconds&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;60&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;remove-the-instance-from-rotation&#34;&gt;Remove the instance from rotation&lt;/h4&gt;
&lt;p&gt;Now our preconditions have been met we can remove the instance using the &lt;code&gt;aws:executeAwsApi&lt;/code&gt; &lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/automation-action-executeAwsApi.html&#34;&gt;action&lt;/a&gt;. This action is similar to &lt;code&gt;aws:assertAwsResourceProperty&lt;/code&gt; in that it calls an AWS API endpoint, but we&amp;rsquo;re not checking the response of it - in fact the &lt;a href=&#34;https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_DeregisterTargets.html&#34;&gt;DeregisterTargets&lt;/a&gt; endpoint doesn&amp;rsquo;t return anything for us to check against.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: DeregisterInstanceFromTargetGroup
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: Proactively remove the instance from the target group
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;action&lt;/span&gt;: aws:executeAwsApi
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;Service&lt;/span&gt;: elbv2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;Api&lt;/span&gt;: DeregisterTargets
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;TargetGroupArn&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ TargetGroupArn }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;Targets&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#ff79c6&#34;&gt;Id&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ InstanceId }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once we&amp;rsquo;ve done that we need to verify the instance has definitely been removed. Remember that the target group allows for existing connections to complete their requests when it is draining, so deregistering the instance doesn&amp;rsquo;t happen instantaneously - this is where the &lt;code&gt;aws:waitForAwsResourceProperty&lt;/code&gt; helps us.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: WaitForDeregisteredTarget
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: Wait for the instance to drain connections
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;action&lt;/span&gt;: aws:waitForAwsResourceProperty
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;Service&lt;/span&gt;: elbv2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;Api&lt;/span&gt;: DescribeTargetHealth
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;PropertySelector&lt;/span&gt;: $.TargetHealthDescriptions[0].TargetHealth.State
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;DesiredValues&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - unused
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;TargetGroupArn&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ TargetGroupArn }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;Targets&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;Id&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ InstanceId }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;maxAttempts&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;timeoutSeconds&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;600&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: AssertTargetIsDeregistered
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: Assert the instance is no longer a target
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;action&lt;/span&gt;: aws:assertAwsResourceProperty
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;Service&lt;/span&gt;: elbv2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;Api&lt;/span&gt;: DescribeTargetHealth
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;PropertySelector&lt;/span&gt;: $.TargetHealthDescriptions[0].TargetHealth.State
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;DesiredValues&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - unused
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;TargetGroupArn&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ TargetGroupArn }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;Targets&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;Id&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ InstanceId }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;execute-maintenance&#34;&gt;Execute maintenance&lt;/h4&gt;
&lt;p&gt;At this point we&amp;rsquo;re 100% sure that the instance is now removed from the target group and is no longer receiving requests, so let&amp;rsquo;s go ahead and use &lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/automation-action-executeAutomation.html&#34;&gt;&lt;code&gt;aws:executeAutomation&lt;/code&gt;&lt;/a&gt; to invoke the &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-3/#so-what-does-all-this-have-to-do-with-our-maintenance-document&#34;&gt;maintenance document&lt;/a&gt; from earlier. Remember it takes in the &lt;code&gt;InstanceIds&lt;/code&gt; as a parameter to execute on, so we&amp;rsquo;ll need to pass it there too.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re specifying an &lt;code&gt;onFailure&lt;/code&gt; too, this tells the document should the step fail then move onto this step instead of the default action which is to abort the rest of the document.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;step:RegisterTarget&lt;/code&gt; is actually the next step after this one, which adds the instance back to the target group. Since it performs health checks for us and won&amp;rsquo;t actually forward to an unhealthy instance, we&amp;rsquo;ll let the target group make the call if this instance can receive traffic or not.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: RebootWithHealthcheck
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: Reboot the instance with a healthcheck afterward
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;action&lt;/span&gt;: aws:executeAutomation
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;DocumentName&lt;/span&gt;: RebootWithHealthcheck
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;RuntimeParameters&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;InstanceIds&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ InstanceId }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;maxAttempts&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;onFailure&lt;/span&gt;: step:RegisterTarget
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;add-instance-back-to-target-group&#34;&gt;Add instance back to target group&lt;/h4&gt;
&lt;p&gt;In a similar vein to DeregisterTargets, this &lt;a href=&#34;https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_RegisterTargets.html&#34;&gt;action&lt;/a&gt; will register the instance back to the target group.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: RegisterTarget
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: Add the instance back as a target
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;action&lt;/span&gt;: aws:executeAwsApi
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;Service&lt;/span&gt;: elbv2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;Api&lt;/span&gt;: RegisterTargets
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;TargetGroupArn&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ TargetGroupArn }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;Targets&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - &lt;span style=&#34;color:#ff79c6&#34;&gt;Id&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ InstanceId }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Registering the instance happens instantaneously, but we will have to wait for the target group to perform initial health checks against the instance. Once we&amp;rsquo;ve asserted that it&amp;rsquo;s healthy, then the document is complete!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: WaitForHealthyTargetGroup
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: Wait for the target group to become healthy again
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;action&lt;/span&gt;: aws:waitForAwsResourceProperty
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;Service&lt;/span&gt;: elbv2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;Api&lt;/span&gt;: DescribeTargetHealth
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;PropertySelector&lt;/span&gt;: $.TargetHealthDescriptions..TargetHealth.State
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;DesiredValues&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - healthy
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;TargetGroupArn&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ TargetGroupArn }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;maxAttempts&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: AssertTargetGroupHealthAfter
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: Assert the target group is healthy after activity
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;action&lt;/span&gt;: aws:assertAwsResourceProperty
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;Service&lt;/span&gt;: elbv2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;Api&lt;/span&gt;: DescribeTargetHealth
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;PropertySelector&lt;/span&gt;: $.TargetHealthDescriptions..TargetHealth.State
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;DesiredValues&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - healthy
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;TargetGroupArn&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ TargetGroupArn }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;maxAttempts&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;timeoutSeconds&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;60&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;isEnd&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Remember that this document only handles one instance at a time, it will typically be up to the caller (i.e. a maintenance window) to rate limit the execution of multiple instances one at a time. We explored this in the &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2/&#34;&gt;previous post&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;terraform-additions-and-updates&#34;&gt;Terraform additions and updates&lt;/h3&gt;
&lt;p&gt;The above document can be represented in &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-3/ssm_document_graceful_reboot.tf&#34;&gt;Terraform&lt;/a&gt; to provision it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_ssm_document&amp;#34; &amp;#34;graceful_reboot_instance&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name            &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;RebootInstanceGraceful&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  document_type   &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Automation&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  document_format &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;YAML&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  content &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;templatefile&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;documents/graceful_patch_instance.yml&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      reboot_with_healthcheck_document_arn &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_document&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;reboot_with_healthcheck&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;arn&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We&amp;rsquo;ll also need to update our &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-3/maintenance_window.tf#L22&#34;&gt;maintenance window task&lt;/a&gt; to correctly reflect this new document, along with the new parameters it takes.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_ssm_maintenance_window_task&amp;#34; &amp;#34;patch_with_healthcheck&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  window_id        &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_maintenance_window&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;patch_with_healthcheck&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  task_type        &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;AUTOMATION&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  task_arn         &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_document&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;graceful_reboot_instance&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;arn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  priority         &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  service_role_arn &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_iam_role&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;patch_mw_role&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;arn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  max_concurrency &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  max_errors      &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;targets&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    key    &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;WindowTargetIds&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    values &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_maintenance_window_target&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;patch_with_healthcheck_target&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;task_invocation_parameters&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;automation_parameters&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      document_version &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;$LATEST&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;parameter&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;InstanceId&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        values &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ TARGET_ID }}&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;parameter&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;TargetGroupArn&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        values &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#ff79c6&#34;&gt;module&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;hello_world_alb&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;target_group_arns&lt;/span&gt;[&lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt;]]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And lastly, we&amp;rsquo;ll need to update the &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-3/maintenance_window_iam.tf#L30&#34;&gt;IAM role permissions&lt;/a&gt; for &lt;code&gt;aws_iam_role.patch_mw_role.arn&lt;/code&gt; as it will be invoking more actions.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;data&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_iam_policy_document&amp;#34; &amp;#34;mw_role_additional&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;statement&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sid    &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;AllowSSM&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    effect &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Allow&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    actions &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ssm:DescribeInstanceInformation&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ssm:ListCommandInvocations&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    resources &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;*&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;statement&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sid    &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;AllowElBRead&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    effect &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Allow&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    actions &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;elasticloadbalancing:DescribeTargetHealth&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    resources &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;*&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;statement&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sid &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;AllowELBWrite&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    effect &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Allow&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    actions &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;elasticloadbalancing:DeregisterTargets&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;elasticloadbalancing:RegisterTargets&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    resources &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#ff79c6&#34;&gt;module&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;hello_world_alb&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;target_group_arns&lt;/span&gt;[&lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt;]]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;testing-the-new-document&#34;&gt;Testing the new document&lt;/h3&gt;
&lt;p&gt;You can test the automation document by following the &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2/#testing-automation-documents&#34;&gt;same process as before&lt;/a&gt;, else you can test the whole stack via changing the &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2/#testing-automation-documents-in-maintenance-windows&#34;&gt;execution time of the maintenance window&lt;/a&gt;. I&amp;rsquo;ll be following along with the latter.&lt;/p&gt;
&lt;p&gt;While the document is running you can re-use the same command to hit the ALB endpoint &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-3/#testing-for-failure&#34;&gt;from earlier&lt;/a&gt; to see how traffic is distributed amongst the instances. You&amp;rsquo;ll first see that it will only execute on one instance at a time, which was the enhancement we introduced in the &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2/&#34;&gt;last post&lt;/a&gt;.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;graceful-document-overview.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-3//blog/automate-instance-hygiene-with-aws-ssm-3/graceful-document-overview.png&#34;
    alt=&#34;AWS console showing the automation document execution view. There are 3 task invocations, one for each instance in scope - they have all executed successfully and only one invocation was executed at a time.&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;When we drill down into each invocation we can see the automation steps doing their magic.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;graceful-document-invocation-detail.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-3//blog/automate-instance-hygiene-with-aws-ssm-3/graceful-document-invocation-detail.png&#34;
    alt=&#34;AWS console showing the individual steps of the automation document removing the targeted instance from rotation before executing the maintenance automation document&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;We can have a look back at the ALB metrics again to see if we received any errors.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;graceful-results.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-3//blog/automate-instance-hygiene-with-aws-ssm-3/graceful-results.png&#34;
    alt=&#34;CloudWatch metrics view for the ALB RequestCount and HTTP_ELB_5XX_Count. The former hovers at approximately 100 requests per minute, whereas there are no error counts being reported.&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;No error - no problem!&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Each post prior to this one in the series has been leading up to where we are now - a means of achieving automated zero downtime maintenance for anything that sits behind an AWS ALB.&lt;/p&gt;
&lt;p&gt;SSM is very much a bit of a beast and I hope that this series has helped clear the fog and given yourselves an idea of what you can do with SSM to automate a variety of tasks in your AWS estate.&lt;/p&gt;
&lt;p&gt;Happy automating! &amp;#x1f4aa; &amp;#x1f64c;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Who Goes Blogging 7: Hugo Minify RSS Code Indentation Fix</title><enclosure url="https://jdheyburn.co.uk/blog/who-goes-blogging-7-hugo-minify-rss-code-indentation-fix/cover.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/blog/who-goes-blogging-7-hugo-minify-rss-code-indentation-fix/</link>
      <pubDate>Mon, 14 Dec 2020 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/who-goes-blogging-7-hugo-minify-rss-code-indentation-fix/</guid>-->
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;UPDATE 2021-12-15&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The minify config was incorrect at time of publication, and so it&amp;rsquo;s been corrected so that minification &lt;em&gt;does&lt;/em&gt; happen while not impacting XML files.&lt;/p&gt;
&lt;p&gt;Thanks to &lt;a href=&#34;https://aradaelli.com/&#34;&gt;Andrea&lt;/a&gt; for pointing this out to me!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It&amp;rsquo;s certainly been a while since the &lt;a href=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-6-three-steps-to-improve-hugos-rss-feeds/&#34;&gt;previous post&lt;/a&gt; in this series, which has become the home of any updates I make to my &lt;a href=&#34;https://gohugo.io/&#34;&gt;Hugo&lt;/a&gt; website.&lt;/p&gt;
&lt;p&gt;This post is a quick one but it&amp;rsquo;s something I&amp;rsquo;ve been meaning to fix for a while. The RSS feeds that are generated will also include code blocks as defined through code fences (```) or through &lt;code&gt;{{&amp;lt; highlight &amp;gt;}}&lt;/code&gt; shortcodes in your post content.&lt;/p&gt;
&lt;p&gt;However for some reason the code blocks generated in my RSS feeds were losing their indentation. Take this code snippet example from my &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2/&#34;&gt;latest post&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_ssm_document&amp;#34; &amp;#34;patch_with_healthcheck&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;PatchWithHealthcheck&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;document_type &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Automation&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;document_format &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;YAML&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;content &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;templatefile&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;documents/patch_with_healthcheck_template.yml&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;healthcheck_document_arn &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_document&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;perform_healthcheck_s3&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;arn&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;output_s3_bucket_name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_s3_bucket&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;script_bucket&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;output_s3_key_prefix &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ssm_output/&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Whereas it should be rendering as:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_ssm_document&amp;#34; &amp;#34;patch_with_healthcheck&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name            &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;PatchWithHealthcheck&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  document_type   &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Automation&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  document_format &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;YAML&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  content &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;templatefile&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;documents/patch_with_healthcheck_template.yml&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      healthcheck_document_arn &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_document&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;perform_healthcheck_s3&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;arn&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      output_s3_bucket_name    &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_s3_bucket&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;script_bucket&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      output_s3_key_prefix     &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ssm_output/&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is happening during the &lt;a href=&#34;https://github.com/jdheyburn/jdheyburn.co.uk/blob/master/.github/workflows/deploy.yml#L31&#34;&gt;GitHub Action&lt;/a&gt; that builds the website - I had previously been using the &lt;code&gt;--minify&lt;/code&gt; flag which follows &lt;a href=&#34;https://gohugo.io/getting-started/configuration/#configure-minify&#34;&gt;this configuration&lt;/a&gt; by default.&lt;/p&gt;
&lt;p&gt;What&amp;rsquo;s happening is the RSS XML that Hugo generates for us is then being &lt;a href=&#34;https://en.wikipedia.org/wiki/Minification_(programming)&#34;&gt;minified&lt;/a&gt; to remove the whitespace generated in that file. This includes the whitespace that&amp;rsquo;s used to indent the code in the RSS feeds. Not. Good.&lt;/p&gt;
&lt;p&gt;On original publication I thought setting &lt;code&gt;minify.tdewolff.xml.keepWhitespace = true&lt;/code&gt; would be enough. On &lt;a href=&#34;https://github.com/tdewolff/minify#xml&#34;&gt;closer reading&lt;/a&gt;, it does not have the desired effect.&lt;/p&gt;
&lt;h2 id=&#34;the-fix&#34;&gt;The Fix&lt;/h2&gt;
&lt;p&gt;In order to fix this we need to add in the below to the site&amp;rsquo;s config. Then we can remove the &lt;code&gt;--minify&lt;/code&gt; flag from our build script, since minification is enabled via our config now.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[minify]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  disableXML = &lt;span style=&#34;color:#ff79c6&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  minifyOutput = &lt;span style=&#34;color:#ff79c6&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;I&amp;rsquo;m using &lt;code&gt;toml&lt;/code&gt; for my config, so make sure you change it to what your config is defined in (&lt;code&gt;yaml&lt;/code&gt; / &lt;code&gt;json&lt;/code&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hope this help you with fixing your RSS feeds too!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Automate Instance Hygiene with AWS SSM: Automation Documents</title><enclosure url="https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2/cover.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2/</link>
      <pubDate>Fri, 04 Dec 2020 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2/</guid>-->
      <description>&lt;p&gt;In &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-1/&#34;&gt;part two&lt;/a&gt; of this &lt;a href=&#34;https://jdheyburn.co.uk/series/automate-instance-hygiene-with-aws-ssm/&#34;&gt;series&lt;/a&gt; we looked at how we can automate SSM command documents using SSM Maintenance Windows.&lt;/p&gt;
&lt;p&gt;This part will now explore another type of SSM Document; Automation.&lt;/p&gt;
&lt;h2 id=&#34;tldr&#34;&gt;tl;dr&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Automation documents can allow us to combine command documents together&lt;/li&gt;
&lt;li&gt;With this, we can utilise maintenance window error thresholds to stop further invocations&lt;/li&gt;
&lt;li&gt;Dynamic invocation of command documents can also be achieved with automation documents&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;All the code for this post can be found on &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/tree/main/aws-ssm-automation-2&#34;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll notice that we have changed some things around in this post, so if you&amp;rsquo;ve been using &lt;code&gt;terraform apply&lt;/code&gt; in other posts to deploy to your AWS environment, you will notice some destructions.&lt;/p&gt;
&lt;p&gt;Instead of having 1 Windows and 1 Linux EC2 instance, we&amp;rsquo;re now using &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-2/ec2.tf&#34;&gt;3 Linux EC2 instances&lt;/a&gt; - to emulate an application running across multiple instances for redundancy. You don&amp;rsquo;t actually need anything to be running on these instances, just have them visible in the &lt;a href=&#34;https://console.aws.amazon.com/systems-manager/managed-instances&#34;&gt;Managed Instances&lt;/a&gt; console.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that the EC2 instances are tagged with the key &lt;code&gt;App&lt;/code&gt; and value &lt;code&gt;HelloWorld&lt;/code&gt; - we&amp;rsquo;ll be using this to specify our automation document targets.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;automation-documents&#34;&gt;Automation Documents&lt;/h2&gt;
&lt;p&gt;Back in &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-0/&#34;&gt;part one&lt;/a&gt; I gave a brief intro to automation documents. To save the click:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Automation document can call and orchestrate AWS API endpoints on your behalf, including executing Command documents on instances&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Essentially we can combine two command documents into one with an automation document. But why would we want to do this?&lt;/p&gt;
&lt;h3 id=&#34;introducing-proactive-healthchecks&#34;&gt;Introducing proactive healthchecks&lt;/h3&gt;
&lt;p&gt;Well in the last post, we set up a maintenance window with two tasks; one for invoking &lt;strong&gt;AWS-RunPatchBaseline&lt;/strong&gt; and another for &lt;strong&gt;PerformHealthcheckS3&lt;/strong&gt; (our healthcheck SSM Document) - both of these are &lt;em&gt;command documents&lt;/em&gt;. Say if we had a policy that wanted to ensure that after &lt;strong&gt;AWS-RunPatchBaseline&lt;/strong&gt; was invoked, we would &lt;em&gt;always&lt;/em&gt; want the &lt;strong&gt;PerformHealthcheckS3&lt;/strong&gt; invoked afterward&amp;hellip; the Automation Document would help us get there.&lt;/p&gt;
&lt;p&gt;Not only that, the way that our maintenance window is &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-1/#maintenance-window-tasks&#34;&gt;currently structured&lt;/a&gt; is it will invoke &lt;strong&gt;AWS-RunPatchBaseline&lt;/strong&gt; across all instances in scope at the same time. Once they are all done then it will invoke &lt;strong&gt;PerformHealthcheckS3&lt;/strong&gt; across all instances at the same time. This looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Given we have 2 instances; i-111, i-222
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+0: Invoke AWS-RunPatchBaseline on i-111, i-222
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+1: AWS-RunPatchBaseline finishes: i-111, i-222
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+2: Invoke PerformHealthcheckS3 on i-111, i-222
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+3: PerformHealthcheckS3 finishes: i-111, i-222
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Say if you wanted to limit the rate of patching across these instances so that only one instance at a time was patched, and any healthcheck failures aborted the rest of patching, then simply changing &lt;code&gt;max_concurrency&lt;/code&gt; from &lt;code&gt;100%&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt; for each maintenance window task &lt;em&gt;will not achieve this&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Maintenance windows complete one task across all instances in scope before moving onto the next task. If we have Task 1 for Patching and Task 2 for Healthchecking (is that even a word?), then the maintenance window is going to patch &lt;strong&gt;all&lt;/strong&gt; instances first before it performs healthchecks on the instances. There is no way to execute the tasks synchronously on one instance at a time.&lt;/p&gt;
&lt;p&gt;This means that if a bad patch were to be installed in your estate, you could have this order of events:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Given we have 2 instances; i-111, i-222
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+0: Invoke AWS-RunPatchBaseline on i-111
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+1: AWS-RunPatchBaseline finishes: i-111
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+2: Invoke AWS-RunPatchBaseline on i-222
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+3: AWS-RunPatchBaseline finishes: i-222
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+4: Invoke PerformHealthcheckS3 on i-111
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+5: PerformHealthcheckS3 FAILS: i-111
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+6: Invoke PerformHealthcheckS3 on i-222
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+7: PerformHealthcheckS3 FAILS: i-222
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now the healthcheck has failed for &lt;code&gt;i-222&lt;/code&gt; as well because the bad patch landed on both instances, and production now has an outage.&lt;/p&gt;
&lt;h4 id=&#34;solution&#34;&gt;Solution&lt;/h4&gt;
&lt;p&gt;Thankfully, Automation Documents help us avoid that - by combining the two command documents (AWS-RunPatchBaseline and PerformHealthcheckS3), we can mark this new automation document as a &lt;em&gt;solo maintenance window task&lt;/em&gt; and have it invoked one at a time on instances, and have it abort further invocations if any sub-documents failed within in:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Given we have 2 instances; i-111, i-222
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+0: Invoke AWS-RunPatchBaseline on i-111
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+1: AWS-RunPatchBaseline finishes: i-111
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+2: Invoke PerformHealthcheckS3 on i-111
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+3: PerformHealthcheckS3 FAILS: i-111
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+4: Abort invoking AWS-RunPatchBaseline on i-222
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+5: Abort invoking PerformHealthcheckS3 on i-222
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Notice how the failed healthcheck on the first instance caused the rest of task invocations to abort? Here is the order of events for a happy path.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Given we have 2 instances; i-111, i-222
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+0: Invoke AWS-RunPatchBaseline on i-111
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+1: AWS-RunPatchBaseline finishes: i-111
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+2: Invoke PerformHealthcheckS3 on i-111
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+3: PerformHealthcheckS3 succeeds: i-111
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+4: Invoke AWS-RunPatchBaseline on i-222
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+5: AWS-RunPatchBaseline finishes: i-222
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+6: Invoke PerformHealthcheckS3 on i-222
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;T+7: PerformHealthcheckS3 succeeds: i-222
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So we know that automation documents allow us to combine command documents together - this is done using the &lt;code&gt;aws:runCommand&lt;/code&gt; action, though there are &lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/automation-actions.html&#34;&gt;many more&lt;/a&gt; actions available to you, some of which we&amp;rsquo;ll explore later.&lt;/p&gt;
&lt;h2 id=&#34;combining-command-docs-into-automation&#34;&gt;Combining command docs into automation&lt;/h2&gt;
&lt;p&gt;We combine command documents as below in YAML; like command documents they can also be defined in JSON.&lt;/p&gt;
&lt;p&gt;For the first time we are defining a parameter called &lt;code&gt;InstanceIds&lt;/code&gt;, which takes in a list of instance IDs to then pass down to the command documents, as they will need to know what instances to invoke the commands on. The value assigned to this parameter is retrieved back with the notation &lt;code&gt;&amp;quot;{{ InstanceIds }}&amp;quot;&lt;/code&gt;, which you can see being passed into the inputs of the sub-documents.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;InstanceIds&lt;/code&gt; is a special parameter name that AWS recognises and so will provide an instance picker in the GUI of Execute Automation, as shown below.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;instance-id-picker.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2//blog/automate-instance-hygiene-with-aws-ssm-2/instance-id-picker.png&#34;
    alt=&#34;A screenshot of the execute automation page with the instance ID picker being used to select instances in scope&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;The instance picker can be helpful if you only want to target a subset of instances in your estate.&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;We&amp;rsquo;re also following logging best practices by having the command output logged to S3.&lt;/p&gt;
&lt;p&gt;Other than that, there&amp;rsquo;s not really a whole lot of difference between this and a command document, so far!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It&amp;rsquo;s important to note the &lt;code&gt;schemaVersion&lt;/code&gt; must be &lt;code&gt;0.3&lt;/code&gt; for automation documents.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;schemaVersion&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;0.3&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: Executes a patching event on the instance followed by a healthcheck
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;parameters&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;InstanceIds&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;type&lt;/span&gt;: StringList
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: The instance to target
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;mainSteps&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: InvokePatchEvent
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;action&lt;/span&gt;: aws:runCommand
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;DocumentName&lt;/span&gt;: AWS-RunPatchBaseline
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;InstanceIds&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ InstanceIds }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;OutputS3BucketName&lt;/span&gt;: jdheyburn-scripts
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;OutputS3KeyPrefix&lt;/span&gt;: ssm_output/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;Parameters&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;Operation&lt;/span&gt;: Scan
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: ExecuteHealthcheck
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;action&lt;/span&gt;: aws:runCommand
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;DocumentName&lt;/span&gt;: PerformHealthcheckS3
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;InstanceIds&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ InstanceIds }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;OutputS3BucketName&lt;/span&gt;: jdheyburn-scripts
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;OutputS3KeyPrefix&lt;/span&gt;: ssm_output/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;terraform-automation-documents&#34;&gt;Terraform automation documents&lt;/h3&gt;
&lt;p&gt;We can deploy these to AWS using Terraform once again. Note that the &lt;code&gt;document_type&lt;/code&gt; is &lt;code&gt;Automation&lt;/code&gt; and that we&amp;rsquo;re using templating to set the variables in the document, such as referencing the PerformHealthcheckS3 command document ARN.&lt;/p&gt;
&lt;p&gt;You can see the templated version of the document in &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-2/documents/patch_with_healthcheck_template.yml&#34;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_ssm_document&amp;#34; &amp;#34;patch_with_healthcheck&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name            &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;PatchWithHealthcheck&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  document_type   &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Automation&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  document_format &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;YAML&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  content &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;templatefile&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;documents/patch_with_healthcheck_template.yml&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      healthcheck_document_arn &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_document&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;perform_healthcheck_s3&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;arn&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      output_s3_bucket_name    &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_s3_bucket&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;script_bucket&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      output_s3_key_prefix     &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ssm_output/&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;View the above resource in &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-2/ssm_combined_command.tf&#34;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;testing-automation-documents&#34;&gt;Testing automation documents&lt;/h3&gt;
&lt;p&gt;Now let&amp;rsquo;s go and test this by manually invoking it. This can be done by navigating to &lt;a href=&#34;https://console.aws.amazon.com/systems-manager/automation/executions&#34;&gt;Automation&lt;/a&gt; within Systems Manager and clicking &lt;a href=&#34;https://console.aws.amazon.com/systems-manager/automation/execute&#34;&gt;Execute Automation&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For a shortcut of invoking the commands, you can use the below command to invoke the below CLI command. Then you may skip to &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2/#results&#34;&gt;the results&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;aws ssm start-automation-execution &lt;span style=&#34;color:#f1fa8c&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&lt;/span&gt;  --document-name &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;PatchWithHealthcheck&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&lt;/span&gt;  --document-version &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;\$DEFAULT&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&lt;/span&gt;  --target-parameter-name InstanceIds &lt;span style=&#34;color:#f1fa8c&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&lt;/span&gt;  --max-errors &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;0&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&lt;/span&gt;  --max-concurrency &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;1&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&lt;/span&gt;  --region eu-west-1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;setup&#34;&gt;Setup&lt;/h4&gt;
&lt;p&gt;Navigate to the &lt;strong&gt;Owned by me&lt;/strong&gt; tab and select the name of your created document, &lt;strong&gt;PatchWithHealthcheck&lt;/strong&gt;, then click &lt;strong&gt;Next&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Because we want to test this document executing one at a time on an instance, we&amp;rsquo;ll need to select the &lt;strong&gt;Rate control&lt;/strong&gt; option.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;execute-automation-1.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2//blog/automate-instance-hygiene-with-aws-ssm-2/execute-automation-1.png&#34;
    alt=&#34;Execute automation document page for PatchHealthcheck, Rate Control is selected&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;We&amp;rsquo;ll need to select what instances are our targets. The instances in scope for this document are &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-2/ec2.tf#L23&#34;&gt;tagged&lt;/a&gt; with the key &lt;code&gt;App&lt;/code&gt; with the value &lt;code&gt;HelloWorld&lt;/code&gt;, so let&amp;rsquo;s use that as our criteria. Note this is the most scalable solution for targeting instances.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;execute-automation-2.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2//blog/automate-instance-hygiene-with-aws-ssm-2/execute-automation-2.png&#34;
    alt=&#34;A screenshot of the execute automation page with tag key App and tag value HelloWorld specified&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Then we need to specify how the rate of invocation should be controlled. Our criteria for this is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Execute on 1 instance at a time&lt;/li&gt;
&lt;li&gt;Abort further invocations if any produce a error&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Therefore we need to set the concurrency to 1 and the error threshold to 0.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;execute-automation-3.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2//blog/automate-instance-hygiene-with-aws-ssm-2/execute-automation-3.png&#34;
    alt=&#34;A screenshot of the execute automation page with concurrency set to 1 and the error threshold set to 0&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Once all done then click &lt;strong&gt;Execute&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&#34;results&#34;&gt;Results&lt;/h4&gt;
&lt;p&gt;As the execution progresses you&amp;rsquo;ll notice it invokes the document on one instance at a time. As it completes you&amp;rsquo;ll have a screen that looks like the below as you click onto the execution detail page. Notice that the start and end times of each instance invocation do not overlap with one another.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Step name is the same as the instance ID in this case&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;execute-automation-4.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2//blog/automate-instance-hygiene-with-aws-ssm-2/execute-automation-4.png&#34;
    alt=&#34;A screenshot of the successfully completed execution detail page, all executed steps across all instances are successful and did not overlap one another&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;We can dive into each step invocation (the blue text in the above screenshot) to view the commands that were invoked.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;execute-automation-5.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2//blog/automate-instance-hygiene-with-aws-ssm-2/execute-automation-5.png&#34;
    alt=&#34;A screenshot of a successful automation step, there is a clickable URL for the step execution ID&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;execute-automation-6.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2//blog/automate-instance-hygiene-with-aws-ssm-2/execute-automation-6.png&#34;
    alt=&#34;A screenshot of a successful automation document PatchWithHealthcheck - both patching and healthcheck run command steps are successful&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;At this detail we can see the individual &lt;code&gt;aws:runCommand&lt;/code&gt; actions performed on the instance.&lt;/p&gt;
&lt;h3 id=&#34;failure-testing&#34;&gt;Failure testing&lt;/h3&gt;
&lt;p&gt;Okay so we&amp;rsquo;ve confirmed the document now only invokes synchronously. Let&amp;rsquo;s now test to see if further invocations are aborted when there is a failure.&lt;/p&gt;
&lt;p&gt;To simulate the failure, we can borrow a trick from the &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-0/#testing-in-aws-ssm-console&#34;&gt;first post&lt;/a&gt; in this series by flipping the healthcheck script from &lt;code&gt;&amp;gt;&lt;/code&gt; to &lt;code&gt;&amp;lt;&lt;/code&gt;. Once that change is deployed we can re-run the document using the same method as &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2/#testing-automation-documents&#34;&gt;above&lt;/a&gt;.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;failed-automation-1.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2//blog/automate-instance-hygiene-with-aws-ssm-2/failed-automation-1.png&#34;
    alt=&#34;Simulating a failure, now the rate-limited automation document fails on the first instance invocation&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;We can see the failure, and that the automation did not invoke on more instances! We can drill down into the invocation to see why it failed.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;failed-automation-2.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2//blog/automate-instance-hygiene-with-aws-ssm-2/failed-automation-2.png&#34;
    alt=&#34;The execution detail page for PatchWithHealthcheck, the patch event succeeded but the healthcheck is marked as failed&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;From this page you can then continue to drill down to the run command output to determine the cause of the failure.&lt;/p&gt;
&lt;h2 id=&#34;configuring-automation-tasks-for-maintenance-windows&#34;&gt;Configuring automation tasks for maintenance windows&lt;/h2&gt;
&lt;p&gt;Now that we&amp;rsquo;ve tested the automation document and we&amp;rsquo;re happy to have it automated, let&amp;rsquo;s get this added to a maintenance window task. We can reuse the same maintenance window as we created last time, but with some differences.&lt;/p&gt;
&lt;h3 id=&#34;maintenance-window-task&#34;&gt;Maintenance window task&lt;/h3&gt;
&lt;p&gt;Since we are targeting an automation document, there are some differences we need to account for when compared to &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-1/#maintenance-window-tasks&#34;&gt;command tasks&lt;/a&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Specify the &lt;code&gt;task_type&lt;/code&gt; as &lt;code&gt;AUTOMATION&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Update the &lt;code&gt;task_arn&lt;/code&gt; to our new document accordingly&lt;/li&gt;
&lt;li&gt;Ensure our new combined document is only invoked on one instance at a time, so &lt;code&gt;max_concurrency&lt;/code&gt; is set to &lt;code&gt;1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Within &lt;code&gt;task_invocation_parameters&lt;/code&gt; we use &lt;code&gt;automation_parameters&lt;/code&gt; as opposed to &lt;code&gt;run_command_parameters&lt;/code&gt;.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;document_version&lt;/code&gt; allows us to target a specific document version&lt;/li&gt;
&lt;li&gt;any parameters required by the document are defined within &lt;code&gt;parameter&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Remember that our document takes in &lt;code&gt;InstanceIds&lt;/code&gt; as a parameter? Well you&amp;rsquo;ll notice that the value is set to &lt;code&gt;&amp;quot;{{ TARGET_ID }}&amp;quot;&lt;/code&gt;. This is known in AWS as a &lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/mw-cli-register-tasks-parameters.html&#34;&gt;pseudo parameter&lt;/a&gt;, whereby the instance ID returned by &lt;code&gt;WindowTargetIds&lt;/code&gt; will be passed into the automation document.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Depending on what &lt;code&gt;resource_type&lt;/code&gt; your &lt;code&gt;aws_ssm_maintenance_window_target&lt;/code&gt; is set up as will result in a different value to &lt;code&gt;{{ TARGET_ID }}&lt;/code&gt; - in our case ours is &lt;code&gt;INSTANCE&lt;/code&gt;, so this becomes the instance ID.&lt;/p&gt;
&lt;p&gt;See the &lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/mw-cli-register-tasks-parameters.html#pseudo-parameters&#34;&gt;AWS docs&lt;/a&gt; for a full breakdown.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_ssm_maintenance_window_task&amp;#34; &amp;#34;patch_with_healthcheck&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  window_id        &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_maintenance_window&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;patch_with_healthcheck&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  task_type        &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;AUTOMATION&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  task_arn         &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_document&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;patch_with_healthcheck&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;arn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  priority         &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  service_role_arn &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_iam_role&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;patch_mw_role&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;arn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  max_concurrency &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  max_errors      &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;targets&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    key    &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;WindowTargetIds&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    values &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_maintenance_window_target&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;patch_with_healthcheck_target&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;task_invocation_parameters&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;automation_parameters&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      document_version &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;$LATEST&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;parameter&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name   &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;InstanceIds&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        values &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ TARGET_ID }}&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can view the above resource in &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-2/maintenance_window.tf#L22&#34;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;maintenance-window-target&#34;&gt;Maintenance window target&lt;/h3&gt;
&lt;p&gt;We need to update the target to use tag lookups against the instances - this mimics how we tested our automation document earlier on.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_ssm_maintenance_window_target&amp;#34; &amp;#34;patch_with_healthcheck_target&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  window_id     &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_maintenance_window&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;patch_with_healthcheck&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name          &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;PatchWithHealthcheckTargets&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  description   &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;All instances that should be patched with a healthcheck after&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  resource_type &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;INSTANCE&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;targets&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    key    &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;tag:App&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    values &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;HelloWorld&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can view the above resource in &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-2/maintenance_window.tf#L10&#34;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;additional-iam-policies&#34;&gt;Additional IAM policies&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;ll need to also attach some new permissions to the IAM role for the maintenance window, &lt;code&gt;aws_iam_role.patch_mw_role.arn&lt;/code&gt;, to allow the automation document to perform a lookup on instances by their tag as defined in the updated &lt;code&gt;aws_ssm_maintenance_window_target.patch_with_healthcheck_target&lt;/code&gt; resource.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;data&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_iam_policy_document&amp;#34; &amp;#34;mw_role_additional&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;statement&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sid    &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;AllowSSM&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    effect &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Allow&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    actions &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ssm:DescribeInstanceInformation&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ssm:ListCommandInvocations&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    resources &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;*&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_iam_policy&amp;#34; &amp;#34;mw_role_add&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name        &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;MwRoleAdd&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  description &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Additonal permissions needed for MW&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  policy &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;data&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;aws_iam_policy_document&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;mw_role_additional&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_iam_role_policy_attachment&amp;#34; &amp;#34;mw_role_add&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  role       &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_iam_role&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;patch_mw_role&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  policy_arn &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_iam_policy&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;mw_role_add&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;arn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can view the above resources in &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-2/maintenance_window_iam.tf#L30&#34;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Simply put, we&amp;rsquo;re creating an new IAM policy with anything additional that the maintenance window requires for it to operate. We&amp;rsquo;ll use this policy to add any new actions if we need to in the future.&lt;/p&gt;
&lt;h3 id=&#34;testing-automation-documents-in-maintenance-windows&#34;&gt;Testing automation documents in maintenance windows&lt;/h3&gt;
&lt;p&gt;Once you&amp;rsquo;ve got the config above applied you&amp;rsquo;ll need to run a test. Just like &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-1/#testing-the-barebones-maintenance-window&#34;&gt;last time&lt;/a&gt;, you can do this by changing the maintenance window execution time to something relatively close to your current time.&lt;/p&gt;
&lt;p&gt;Once the execution is complete (and hopefully it was successful, if not then check back at the configuration), you&amp;rsquo;ll see that there will be 3 task invocations, one for each instance, and that none of them overlapped one another.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;automation-maint-window-success.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2//blog/automate-instance-hygiene-with-aws-ssm-2/automation-maint-window-success.png&#34;
    alt=&#34;Execution overview for the maintenance window invocation - we have 3 task invocations, one for each instance and all successful, for which only one was invoked at a time&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Notice how the start and end times don&amp;rsquo;t overlap&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Just like for command documents, you can view the detail for each invocation made on the instance. Here we can see the two steps that make up our newly created command document.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;automation-maint-window-success-detail.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2//blog/automate-instance-hygiene-with-aws-ssm-2/automation-maint-window-success-detail.png&#34;
    alt=&#34;Task invocation detail view for one instance. There are two steps, one for invoking a patch event and another for performing a healthcheck - both are successful&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;But the whole point of this exercise was to proactively handle errors right? So let&amp;rsquo;s introduce some by doing what we&amp;rsquo;ve done &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2/#failure-testing&#34;&gt;before&lt;/a&gt; and change the healthcheck script to fail.&lt;/p&gt;
&lt;p&gt;Once the new &amp;ldquo;broken&amp;rdquo; script is applied we can rerun another test of the maintenance window.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;automation-maint-window-failure.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2//blog/automate-instance-hygiene-with-aws-ssm-2/automation-maint-window-failure.png&#34;
    alt=&#34;Execution overview for the maintenance window invocation - we have 3 task invocations, one for each instance, 1 failed which then aborted further task invocations on the remaining instances&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;This time round we can see that there was a failure in one task invocation, which then aborted further invocations on the remaining instances! Just as before, we can do a deep dive into the invocation to determine why it had failed.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;automation-maint-window-failure-detail.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2//blog/automate-instance-hygiene-with-aws-ssm-2/automation-maint-window-failure-detail.png&#34;
    alt=&#34;Task invocation detail view for one instance. There are two steps, one for invoking a patch event and another for performing a healthcheck - the patch was successful but the healthcheck failed&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;From here you can follow the command invoked by the step to troubleshoot the failure&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;bonus-using-automation-to-dynamically-invoke-command-documents&#34;&gt;Bonus: Using automation to dynamically invoke command documents&lt;/h2&gt;
&lt;p&gt;In this post we&amp;rsquo;ve looked at a common maintenance task in the form of an SSM command document called &lt;strong&gt;AWS-RunPatchBaseline&lt;/strong&gt;, and created an automation document that will always invoke our healthcheck script after this invocation.&lt;/p&gt;
&lt;p&gt;You may have more command documents that perform some form of maintenance on instances, for which you would also want the healthcheck script to execute after as well.&lt;/p&gt;
&lt;p&gt;Instead of copying and pasting these automation documents, we can create just one automation document which takes in the command document ARN as a parameter, dynamically invoke it, and then have a hardcoded step afterward for executing a healthcheck!&lt;/p&gt;
&lt;p&gt;In it&amp;rsquo;s raw YAML form, we would get a document that looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;schemaVersion&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;0.3&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: Executes a maintenance event on the instance followed by a healthcheck
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;parameters&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;InstanceIds&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;type&lt;/span&gt;: StringList
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: The instance to target
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;DocumentArn&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;type&lt;/span&gt;: String
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: The document arn to invoke
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;InputParameters&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;type&lt;/span&gt;: StringMap
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: Parameters that should be passed to the document specified in DocumentArn
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;default&lt;/span&gt;: {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;mainSteps&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: InvokeMaintenanceEvent
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;action&lt;/span&gt;: aws:runCommand
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;DocumentName&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ DocumentArn }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;InstanceIds&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ InstanceIds }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;OutputS3BucketName&lt;/span&gt;: ${output_s3_bucket_name}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;OutputS3KeyPrefix&lt;/span&gt;: ${output_s3_key_prefix}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;Parameters&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ InputParameters }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: ExecuteHealthcheck
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;action&lt;/span&gt;: aws:runCommand
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;DocumentName&lt;/span&gt;: ${healthcheck_document_arn}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;InstanceIds&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ InstanceIds }}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;OutputS3BucketName&lt;/span&gt;: ${output_s3_bucket_name}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;OutputS3KeyPrefix&lt;/span&gt;: ${output_s3_key_prefix}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Just like what we&amp;rsquo;ve done for &lt;code&gt;InstanceIds&lt;/code&gt;, we&amp;rsquo;re taking in the &lt;code&gt;DocumentArn&lt;/code&gt; as a parameter and providing it as the input for the &lt;code&gt;aws:runCommand&lt;/code&gt; step. Some documents will also take parameters, so we can allow the caller to specify them using &lt;code&gt;InputParameters&lt;/code&gt;, which is defined as a &lt;code&gt;StringMap&lt;/code&gt; type - allowing it to then be used in as parameters for the document we are invoking.&lt;/p&gt;
&lt;p&gt;When we create this document in the console and then execute it, we can then dynamically add in the document we want to invoke.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;maintenance-wrapper-setup.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2//blog/automate-instance-hygiene-with-aws-ssm-2/maintenance-wrapper-setup.png&#34;
    alt=&#34;The setup page for the maintenance wrapper document, we&amp;#39;re specifying in the text field the command document to invoke, which is AWS-RunPatchBaseline. We&amp;#39;ve also added in the parameters for the document to run under InputParameters&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Then just like any other document we can invoke it.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;maintenance-wrapper-success.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2//blog/automate-instance-hygiene-with-aws-ssm-2/maintenance-wrapper-success.png&#34;
    alt=&#34;The automation execution overview page shows that two steps were invoked, InvokeMaintenanceEvent and ExecuteHealthcheck, both are successful. We can also see the input for InvokeMaintenanceEvent is specified as AWS-RunPatchBaseline, and the parameters we used previously.&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;We can see that the input from the previous screen was passed to the step. Let&amp;rsquo;s keep going until we hit the command invocation.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;maintenance-wrapper-command-detail.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-2//blog/automate-instance-hygiene-with-aws-ssm-2/maintenance-wrapper-command-detail.png&#34;
    alt=&#34;A screenshot of the command invocation page - it executed successfully and the document listed as being invoked was AWS-RunPatchBaseline&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;The document name was dynamically passed to the command execution&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;terraforming-dynamic-command-documents&#34;&gt;Terraforming dynamic command documents&lt;/h3&gt;
&lt;p&gt;So now we&amp;rsquo;ve confirmed that documents can be dynamically invoked, let&amp;rsquo;s get this Terraformed. You can view this in &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-2/ssm_maintenance_document.tf&#34;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_ssm_document&amp;#34; &amp;#34;maintenance_wrapper&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name            &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;MaintenanceWithHealthcheck&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  document_type   &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Automation&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  document_format &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;YAML&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  content &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;templatefile&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;documents/maintenance_wrapper_template.yml&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      healthcheck_document_arn &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_document&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;perform_healthcheck_s3&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;arn&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      output_s3_bucket_name    &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_s3_bucket&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;script_bucket&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      output_s3_key_prefix     &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ssm_output/&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Not a lot has really changed in this when we compare it to our &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-2/ssm_combined_command.tf&#34;&gt;one from earlier&lt;/a&gt;, but the difference is in the &lt;code&gt;parameters&lt;/code&gt; field for the &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-2/documents/maintenance_wrapper_template.yml&#34;&gt;document&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And then you can include it as a maintenance window task as below; I&amp;rsquo;m reusing the same maintenance window task as before.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_ssm_maintenance_window_task&amp;#34; &amp;#34;patch_with_healthcheck&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  window_id        &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_maintenance_window&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;patch_with_healthcheck&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  task_type        &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;AUTOMATION&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  task_arn         &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_document&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;maintenance_wrapper&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;arn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  priority         &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  service_role_arn &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_iam_role&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;patch_mw_role&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;arn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  max_concurrency &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  max_errors      &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;targets&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    key    &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;WindowTargetIds&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    values &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_maintenance_window_target&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;patch_with_healthcheck_target&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;task_invocation_parameters&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;automation_parameters&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      document_version &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;$LATEST&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;parameter&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name   &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;InstanceIds&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        values &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ TARGET_ID }}&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;parameter&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name   &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;DocumentArn&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        values &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;AWS-RunPatchBaseline&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;parameter&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name   &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;InputParameters&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        values &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [jsonencode({ Operation &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Scan&amp;#34;&lt;/span&gt; })]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can then copy and paste the same task resource, only changing the values for the &lt;code&gt;InputParameters&lt;/code&gt; and &lt;code&gt;DocumentArn&lt;/code&gt; parameters accordingly. If the document you are calling doesn&amp;rsquo;t take any parameters, then you can just omit that block.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You&amp;rsquo;ll have to ensure that the IAM role the maintenance window task is assuming has the correct IAM permissions as required by the document you&amp;rsquo;re calling.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;What we&amp;rsquo;ve done in this post is taken our rudimentary command document, prone to introducing errors into our estate, and converted it to an automation document. With the right SSM maintenance window settings, you can ensure that any maintenance tasks you need to perform on your EC2 instances are done so in a manner that reduces the risk of errors in your environment.&lt;/p&gt;
&lt;p&gt;Next time, we&amp;rsquo;ll be taking this a &lt;em&gt;step further&lt;/em&gt; to proactively remove EC2 instances from circulation when behind a load balancer for maintenance activities.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Automate Instance Hygiene with AWS SSM: Maintenance Windows</title><enclosure url="https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-1/cover.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-1/</link>
      <pubDate>Mon, 16 Nov 2020 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-1/</guid>-->
      <description>&lt;p&gt;&lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-0/&#34;&gt;Last time&lt;/a&gt; we looked at writing our own SSM Command Document for the purpose of executing a healthcheck script on a set of EC2 instances across multiple platforms.&lt;/p&gt;
&lt;p&gt;In this post we&amp;rsquo;ll be exploring how we can automate this using maintenance windows - also within the SSM suite. This is something I&amp;rsquo;ve &lt;a href=&#34;https://jdheyburn.co.uk/blog/using-terraform-to-manage-aws-patch-baselines-at-enterprise-scale/#ssm--patch-manager&#34;&gt;covered before&lt;/a&gt;, but want to extend on that to show how its done.&lt;/p&gt;
&lt;h2 id=&#34;tldr&#34;&gt;tl;dr&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;We can use SSM Maintenance Windows to automate our newly created command documents on a schedule&lt;/li&gt;
&lt;li&gt;Multiple command documents can be combined in a maintenance window, such as a patching event followed by a healthcheck&lt;/li&gt;
&lt;li&gt;This provides us with a means of viewing historical invocations on whatever workflow we&amp;rsquo;ve automated&lt;/li&gt;
&lt;li&gt;By storing command outputs to S3, we can ensure we can recover logs that are too large to display in the console&lt;/li&gt;
&lt;li&gt;Using S3 Lifecycle Rules we can remove aged logs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once again all the Terraform code for this post is available on GitHub. It is split into two parts:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/tree/main/aws-ssm-automation-1-barebones&#34;&gt;aws-ssm-automation-1-barebones&lt;/a&gt; is for the &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-1/#barebones-maintenance-window-with-aws-runpatchbaseline&#34;&gt;barebones walkthrough&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/tree/main/aws-ssm-automation-1-logging&#34;&gt;aws-ssm-automation-1-logging&lt;/a&gt; is for the &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-1/#logging-command-output-to-s3&#34;&gt;logging enhancement&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;intro-to-maintenance-windows&#34;&gt;Intro to Maintenance Windows&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-maintenance.html&#34;&gt;Maintenance Windows&lt;/a&gt;, are a means of executing some automation workflow in your AWS estate on a schedule. Got an SSM Document you&amp;rsquo;ve written and want it automated? What about a &lt;a href=&#34;https://aws.amazon.com/lambda/&#34;&gt;Lambda&lt;/a&gt; you want invoked at a regular schedule? Or maybe it&amp;rsquo;s a &lt;a href=&#34;https://aws.amazon.com/step-functions/&#34;&gt;Step Function&lt;/a&gt;? Whatever the use case, Maintenance Windows are for you - just don&amp;rsquo;t be fooled by the name - they don&amp;rsquo;t necessarily have to be &lt;em&gt;just&lt;/em&gt; for maintenance!&lt;/p&gt;
&lt;h3 id=&#34;similarities-to-eventbridge-rules&#34;&gt;Similarities to EventBridge Rules&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;But wait&amp;rdquo;&lt;/strong&gt;, &lt;em&gt;I hear you ask&lt;/em&gt;, &lt;strong&gt;&amp;ldquo;don&amp;rsquo;t CloudWatch/EventBridge Rules also allow you to invoke events on a schedule too?&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yes they do - both Maintenance Windows and &lt;a href=&#34;https://docs.aws.amazon.com/eventbridge/latest/userguide/what-is-amazon-eventbridge.html&#34;&gt;EventBridge&lt;/a&gt; Rules (the bigger sibling of CloudWatch Rules) use &lt;a href=&#34;https://en.wikipedia.org/wiki/Cron#CRON_expression&#34;&gt;cron expressions&lt;/a&gt; to define the schedule they should run on. The primary difference between the two is that Maintenance Windows allow you to &lt;strong&gt;specify the timezone&lt;/strong&gt; that the cron expression adheres to, whereas EventBridge is &lt;a href=&#34;https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html&#34;&gt;&lt;strong&gt;tied to UTC&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;eventbridge-rule-create.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-1//blog/automate-instance-hygiene-with-aws-ssm-1/eventbridge-rule-create.png&#34;
    alt=&#34;The EventBridge rule creation page, there is no option to schedule the rule to a timezone&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;No timezone, no party&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;So using maintenance windows can be handy if you&amp;rsquo;re in a non-UTC timezone and you don&amp;rsquo;t have to constantly convert your local timezone to UTC to schedule events. More importantly, maintenance windows will respect daylight savings time (DST) if your timezone observes it, so you can be sure your automation will be invoked at the same time in the specified timezone throughout the year.&lt;/p&gt;
&lt;p&gt;On the other hand, EventBridge Rules are fixed to UTC; meaning if your timezone does observe DST, then you&amp;rsquo;ll find your automation could be off by an hour for some portion of the year (unless you change it of course - but who wants to be changing automation twice a year??).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I don&amp;rsquo;t think I&amp;rsquo;ve ever met a software engineer that&amp;rsquo;s a fan of DST!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Notably as well, you can view the execution history of maintenance windows as they&amp;rsquo;ve occurred in the past, allowing you to quickly see whether a particular invocation was successful or not - and drill down into any failures.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not bashing EventBridge Rules, in fact, it is easier to set up than Maintenance Windows. But there&amp;rsquo;s always the right tool for the job.&lt;/p&gt;
&lt;p&gt;For the rest of this post, we&amp;rsquo;re going to be exploring how to automate the command document we created last time. Later on in the series we&amp;rsquo;ll be looking at using maintenance windows to automate automation documents.&lt;/p&gt;
&lt;h3 id=&#34;automating-command-documents-with-maintenance-windows&#34;&gt;Automating command documents with maintenance windows&lt;/h3&gt;
&lt;p&gt;So what&amp;rsquo;s the purpose of creating the command document we achieved last time? Well we&amp;rsquo;re not going to be manually invoking it like what we have been doing so far - as engineers we need to be automating as many repetitive tasks as possible.&lt;/p&gt;
&lt;p&gt;To summarise where we are now, we&amp;rsquo;ve produced a Command document which when executed, automates the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Downloads a healthcheck script from S3&lt;/li&gt;
&lt;li&gt;Executes the healthcheck script, failing the command invocation if healthcheck does not pass&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Healthchecks are important to run both continuously in our environment, as a means of monitoring and verifying the estate is working as intended, before your users notice. They are also necessary to run after a change has been introduced to the environment, such as a new code deployment, or even a patching event via the &lt;strong&gt;AWS-RunPatchBaseline&lt;/strong&gt; document.&lt;/p&gt;
&lt;h2 id=&#34;barebones-maintenance-window-with-aws-runpatchbaseline&#34;&gt;Barebones maintenance window with AWS-RunPatchBaseline&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;re going to use Terraform again to build out a minimal maintenance window. You can view the code for it &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-1-barebones/maintenance_window.tf&#34;&gt;here&lt;/a&gt;. You&amp;rsquo;ll notice the repository this file sits in is identical to the one from the &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/tree/main/aws-ssm-automation-0&#34;&gt;first post&lt;/a&gt;, except I&amp;rsquo;ve included the new resources that will enable us to accomplish the requirement.&lt;/p&gt;
&lt;p&gt;Here is a breakdown of each of the resources we&amp;rsquo;re going to create.&lt;/p&gt;
&lt;h3 id=&#34;maintenance-window&#34;&gt;Maintenance Window&lt;/h3&gt;
&lt;p&gt;This creates the maintenance window resource, which is then referred to in the subsequent resources we create.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It&amp;rsquo;s nothing more than that cron expression I mentioned earlier, along with the timezone it should execute in&lt;/li&gt;
&lt;li&gt;We specify how long the window lasts for, and the cutoff; both of which are specified in hours
&lt;ul&gt;
&lt;li&gt;The cutoff indicates how long before the end of the window should AWS not schedule any new tasks in that window&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_ssm_maintenance_window&amp;#34; &amp;#34;patch_with_healthcheck&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name              &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;PatchWithHealthcheck&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  description       &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Daily patch event with a healthcheck afterward&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  schedule          &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;cron(0 9 ? * * *)&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt; # Everyday at 9am UK time
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;  schedule_timezone &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Europe/London&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  duration          &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  cutoff            &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;maintenance-window-target&#34;&gt;Maintenance Window Target&lt;/h3&gt;
&lt;p&gt;We need a means of telling the window what instances to target, and the &lt;code&gt;aws_ssm_maintenance_window_target&lt;/code&gt; resource is how you do it. Below I&amp;rsquo;m demonstrating two methods of doing this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Specify the instance IDs directly
&lt;ul&gt;
&lt;li&gt;Handy if you have a fixed list of instances you only want to be included in the maintenance window&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Target instances by their tags
&lt;ul&gt;
&lt;li&gt;This is much for scalable, and means you don&amp;rsquo;t have to keep adding instance IDs to the list&lt;/li&gt;
&lt;li&gt;When the maintenance window executes, it will filter instances with this tag key and value combo for what to target&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;An optimum tag to use would be &lt;code&gt;Patch Group&lt;/code&gt; &lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-patch-patchgroups.html&#34;&gt;described here&lt;/a&gt; - which I have &lt;a href=&#34;https://jdheyburn.co.uk/blog/using-terraform-to-manage-aws-patch-baselines-at-enterprise-scale/#ssm--patch-manager&#34;&gt;mentioned previously&lt;/a&gt;. For the sake of this demo, we will keep it simple by targeting instance IDs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_ssm_maintenance_window_target&amp;#34; &amp;#34;patch_with_healthcheck_target&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  window_id     &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_maintenance_window&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;patch_with_healthcheck&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name          &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;PatchWithHealthcheckTargets&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  description   &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;All instances that should be patched with a healthcheck after&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  resource_type &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;INSTANCE&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;targets&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    key &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;InstanceIds&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    values &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;concat&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;module&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;windows_ec2&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;module&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;linux_ec2&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }&lt;span style=&#34;color:#6272a4&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;  # Using tags is more scalable
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;  #   targets {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;  #     key    = &amp;#34;tag:Terraform&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;  #     values = [&amp;#34;true&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;  #   }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;maintenance-window-tasks&#34;&gt;Maintenance Window Tasks&lt;/h3&gt;
&lt;h4 id=&#34;patching-task&#34;&gt;Patching task&lt;/h4&gt;
&lt;p&gt;Now for the tasks&amp;hellip; remember that we want to execute our healthcheck SSM document after a patch event right? We need to build a task for executing the &lt;strong&gt;AWS-RunPatchBaseline&lt;/strong&gt; document.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_ssm_maintenance_window_task&amp;#34; &amp;#34;patch_task&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  window_id        &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_maintenance_window&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;patch_with_healthcheck&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  task_type        &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;RUN_COMMAND&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  task_arn         &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;AWS-RunPatchBaseline&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  priority         &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  service_role_arn &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_iam_role&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;patch_mw_role&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;arn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  max_concurrency &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;100%&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  max_errors      &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;targets&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    key    &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;WindowTargetIds&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    values &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_maintenance_window_target&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;patch_with_healthcheck_target&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;task_invocation_parameters&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;run_command_parameters&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      timeout_seconds  &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;3600&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;parameter&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Operation&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        values &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Scan&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s run through the main attributes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;window_id&lt;/code&gt; - the maintenance window to associate this task with&lt;/li&gt;
&lt;li&gt;&lt;code&gt;task_type&lt;/code&gt; - what kind of task this is
&lt;ul&gt;
&lt;li&gt;since we&amp;rsquo;re executing a command document, the value here is &lt;code&gt;RUN_COMMAND&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;task_arn&lt;/code&gt; - the ARN of the document you wish to run
&lt;ul&gt;
&lt;li&gt;note the document name can also be used here, as demonstrated above&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;priority&lt;/code&gt; - defines in what order should tasks be executed in, whereby the lower the number given, the earlier the task is executed in the window
&lt;ul&gt;
&lt;li&gt;e.g. a task priority of &lt;code&gt;1&lt;/code&gt; gets executed before one with &lt;code&gt;10&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;tasks with the same priority get executed in parallel&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;service_role_arn&lt;/code&gt; - tells which IAM role should be assumed to execute this task as
&lt;ul&gt;
&lt;li&gt;we&amp;rsquo;ll get an explanation of this later&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;max_concurrency&lt;/code&gt; - specifies how many instances this task should be invoked on simultaenously
&lt;ul&gt;
&lt;li&gt;it can take a percentage as a value, only applying to that percentage of target instances at a time
&lt;ul&gt;
&lt;li&gt;i.e. &lt;code&gt;50%&lt;/code&gt; indicates only half of targeted instances will have the task executed at a time&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;or it can take a fixed number such as &lt;code&gt;1&lt;/code&gt;; indicating that only one instance should have this task executed at a time&lt;/li&gt;
&lt;li&gt;&lt;code&gt;100%&lt;/code&gt; indicates this task will be invoked on all targets at the same time&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;max_errors&lt;/code&gt; - indicates how many errors should be thrown before we abort further invocations
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;0&lt;/code&gt; indicates any error will abort the maintenance window and set its result to failed&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;targets&lt;/code&gt; block allows us to define what instances to target this on - we&amp;rsquo;re referencing the &lt;code&gt;aws_ssm_maintenance_window_target&lt;/code&gt; resource we created previously.&lt;/p&gt;
&lt;p&gt;Lastly the &lt;code&gt;task_invocation_parameters&lt;/code&gt; allows us to customise how the document should be ran via the &lt;code&gt;parameter&lt;/code&gt; setting - which is passed to the document. For this example we&amp;rsquo;re only performing the &lt;code&gt;Scan&lt;/code&gt; operation on the document, for testing purposes.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Scan&lt;/code&gt; will only check for missing patches - it won&amp;rsquo;t actually install them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A full list of available commands can be found in the AWS-RunPatchBaseline &lt;a href=&#34;https://console.aws.amazon.com/systems-manager/documents/AWS-RunPatchBaseline/content&#34;&gt;command document&lt;/a&gt;.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;run-patch-baseline-parameters.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-1//blog/automate-instance-hygiene-with-aws-ssm-1/run-patch-baseline-parameters.png&#34;
    alt=&#34;Available parameters for the Run Patch Baseline document; Operation, Snapshot ID, Install Override List, and Reboot Option&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;In the console GUI we can see the available parameters for the AWS-RunPatchBaseline document&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id=&#34;healthcheck-task&#34;&gt;Healthcheck task&lt;/h4&gt;
&lt;p&gt;Next we want to define the healthcheck task.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_ssm_maintenance_window_task&amp;#34; &amp;#34;healthcheck_task&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  window_id        &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_maintenance_window&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;patch_with_healthcheck&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  task_type        &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;RUN_COMMAND&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  task_arn         &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_document&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;perform_healthcheck_s3&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;arn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  priority         &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;20&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  service_role_arn &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_iam_role&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;patch_mw_role&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;arn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  max_concurrency &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;100%&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  max_errors      &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;targets&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    key    &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;WindowTargetIds&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    values &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_maintenance_window_target&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;patch_with_healthcheck_target&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;task_invocation_parameters&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;run_command_parameters&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      timeout_seconds  &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;600&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There&amp;rsquo;s not a whole lot of difference here compared to the patching task; our healthcheck script takes no &lt;code&gt;parameters&lt;/code&gt; so we can leave them out (although if yours does, you&amp;rsquo;ll need to add it here!), and the &lt;code&gt;task_arn&lt;/code&gt; points to the command document we created last time.&lt;/p&gt;
&lt;p&gt;Probably the most significant change though is the &lt;code&gt;priority&lt;/code&gt;. Remember that the priority number indicates the ordering of tasks to be invoked? Our patching task had a priority of &lt;code&gt;10&lt;/code&gt;, whereby our healthcheck task is &lt;code&gt;20&lt;/code&gt;. &lt;strong&gt;This means the patch task will be invoked &lt;em&gt;before&lt;/em&gt; the healthcheck one.&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I could have set the priority of the patching and healthcheck tasks to &lt;code&gt;1&lt;/code&gt; and &lt;code&gt;2&lt;/code&gt; respectively to achieve the same thing.&lt;/p&gt;
&lt;p&gt;However, giving some distance between them means you can programmatically add new tasks before/after each other.&lt;/p&gt;
&lt;p&gt;Want a post-patch, pre-healthcheck task? Attach a new task with priority &lt;code&gt;15&lt;/code&gt;!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;iam-role-for-maintenance-window&#34;&gt;IAM role for maintenance window&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;ve been referencing &lt;code&gt;aws_iam_role.patch_mw_role.arn&lt;/code&gt; as our task &lt;code&gt;service_role_arn&lt;/code&gt;. You can view the code for it &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-1-barebones/maintenance_window_iam.tf&#34;&gt;here&lt;/a&gt; - but let&amp;rsquo;s run through them quickly.&lt;/p&gt;
&lt;p&gt;All we&amp;rsquo;re doing is creating an IAM role, allowing the EC2 and SSM AWS services to assume said role, and applying the predefined AWS policy &lt;a href=&#34;https://console.aws.amazon.com/iam/home#/policies/arn:aws:iam::aws:policy/service-role/AmazonSSMMaintenanceWindowRole&#34;&gt;AmazonSSMMaintenanceWindowRole&lt;/a&gt; to that role. This policy gives some basic permissions to the role which allow it to execute commands and more on the instances.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;data&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_iam_policy_document&amp;#34; &amp;#34;patch_mw_role_assume&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;statement&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    actions &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;sts:AssumeRole&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;principals&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      type &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Service&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      identifiers &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ec2.amazonaws.com&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ssm.amazonaws.com&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_iam_role&amp;#34; &amp;#34;patch_mw_role&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name               &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;PatchingMaintWindow&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  assume_role_policy &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;data&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;aws_iam_policy_document&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;patch_mw_role_assume&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;data&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_iam_policy&amp;#34; &amp;#34;ssm_maintenance_window&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  arn &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;arn:aws:iam::aws:policy/service-role/AmazonSSMMaintenanceWindowRole&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_iam_role_policy_attachment&amp;#34; &amp;#34;patch_mw_role_attach&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  role       &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_iam_role&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;patch_mw_role&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  policy_arn &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;data&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;aws_iam_policy&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;ssm_maintenance_window&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;arn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;testing-the-barebones-maintenance-window&#34;&gt;Testing the barebones maintenance window&lt;/h3&gt;
&lt;p&gt;Once we&amp;rsquo;ve ran &lt;code&gt;terraform apply&lt;/code&gt; on all the above, we can test the maintenance window out. Currently we have it set to run at 9am UK time, which may or may not be a long time away - so change it manually in &lt;a href=&#34;https://console.aws.amazon.com/systems-manager/maintenance-windows&#34;&gt;the console&lt;/a&gt; to a time not far away from your time now.&lt;/p&gt;
&lt;p&gt;Once it&amp;rsquo;s done executing, you can navigate to the history tab to view the execution.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;maintenance-window-history.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-1//blog/automate-instance-hygiene-with-aws-ssm-1/maintenance-window-history.png&#34;
    alt=&#34;The execution history for the maintenance window, showing both successful and failed executions&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Let&amp;rsquo;s hope you have more luck on your first attempts running this than I did!&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;You can select any execution and drill down into it with the &lt;strong&gt;View details&lt;/strong&gt; button.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;mw-execution-details.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-1//blog/automate-instance-hygiene-with-aws-ssm-1/mw-execution-details.png&#34;
    alt=&#34;A detailed look into a maintenance window execution&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;You can see that the tasks got executed in the order we defined them in&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;We can go deeper in the execution details and pull out the result of individual commands by selecting &lt;strong&gt;View details&lt;/strong&gt; on the task invocation.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;task-invocation-command-detail.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-1//blog/automate-instance-hygiene-with-aws-ssm-1/task-invocation-command-detail.png&#34;
    alt=&#34;Detailed breakdown of the RunPatchBaseline command, showing success all round&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;The maintenance window has redirected us to the same page as when we manually invoked the command documents in the last post&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Clicking on one of the instance IDs in the above screenshot will take us to the command output for that instance.&lt;/p&gt;
&lt;h2 id=&#34;logging-command-output-to-s3&#34;&gt;Logging command output to S3&lt;/h2&gt;
&lt;p&gt;Maintenance windows by default only capture the first 2500 characters of a command output, if your command outputs more than this then it gets truncated. This can be a problem if you have a task failure and need to examine the output for the reason why it failed.&lt;/p&gt;
&lt;p&gt;Take the &lt;strong&gt;AWS-RunPatchBaseline&lt;/strong&gt; output on a Linux instance for example. It&amp;rsquo;s pretty hefty, and so we lose a lot of context on what actually happened:&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;truncated-command-output.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-1//blog/automate-instance-hygiene-with-aws-ssm-1/truncated-command-output.png&#34;
    alt=&#34;Log output of the patch event on a Linux instance, with the words Output Truncated at the end.&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;method&#34;&gt;Method&lt;/h3&gt;
&lt;p&gt;To combat this, maintenance windows allow you to dump command output to an S3 bucket, so that you can retrieve it later. In the last post we created an S3 bucket to store our SSM scripts (&lt;code&gt;aws_s3_bucket.script_bucket.arn&lt;/code&gt;), we can reuse that bucket to store our command logs too.&lt;/p&gt;
&lt;p&gt;In order to do this there are some steps we need to take:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The S3 bucket policy needs to permit the EC2 instance role &lt;code&gt;aws_iam_role.vm_base&lt;/code&gt; to &lt;code&gt;s3:PutObject&lt;/code&gt; on &lt;code&gt;&amp;quot;${aws_s3_bucket.script_bucket.arn}/ssm_output/*&amp;quot;&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ssm_output/&lt;/code&gt; is the directory/prefix in the S3 bucket where we will store the logs&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The KMS key used to encrypt objects in the target S3 bucket needs to permit instance role &lt;code&gt;aws_iam_role.vm_base&lt;/code&gt; to &lt;code&gt;kms:GenerateDataKey&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The instance role &lt;code&gt;aws_iam_role.vm_base&lt;/code&gt; needs permissions to do the above on its respective side&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can view the changes required in &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/tree/main/aws-ssm-automation-1-logging&#34;&gt;GitHub&lt;/a&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-1-logging/ssm_command_s3.tf#L99&#34;&gt;S3 bucket policy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-1-logging/ssm_command_s3.tf#L35&#34;&gt;KMS key policy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-1-logging/ec2_iam.tf#L33&#34;&gt;EC2 instance role&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once that is done we&amp;rsquo;ll need to add some new attributes to the maintenance window tasks, telling it where to dump the command output to.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_ssm_maintenance_window_task&amp;#34; &amp;#34;task_name&amp;#34;&lt;/span&gt; {&lt;span style=&#34;color:#6272a4&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;  # ... other attributes hidden
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;task_invocation_parameters&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;run_command_parameters&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      output_s3_bucket     &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_s3_bucket&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;script_bucket&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      output_s3_key_prefix &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ssm_output/&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;      # ... other attributes hidden
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When you have the new config written, then you can &lt;code&gt;terraform apply&lt;/code&gt; and run another test on the maintenance window.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;command-s3-output-button.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-1//blog/automate-instance-hygiene-with-aws-ssm-1/command-s3-output-button.png&#34;
    alt=&#34;Detailed view of the run patch baseline task, showing a button called Amazon S3 which redirects us to where the logs are stored in S3&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;We now get a button that can redirect us to where the logs are stored in S3&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;If we click on this button, we can view the logs being stored in S3. Follow the path in S3 until you reach the S3 object containing the logs.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;log-object-in-s3.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-1//blog/automate-instance-hygiene-with-aws-ssm-1/log-object-in-s3.png&#34;
    alt=&#34;The S3 object containing the logs&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Now you can open this using the &lt;strong&gt;Object actions&lt;/strong&gt; button in the top-right hand corner to view the entire logs!&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;open-logs-in-browser.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-1//blog/automate-instance-hygiene-with-aws-ssm-1/open-logs-in-browser.png&#34;
    alt=&#34;Opening the logs in the browser, we see the complete text output of the run patch baseline command invoked&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;logging-problems&#34;&gt;Logging problems&lt;/h3&gt;
&lt;p&gt;It&amp;rsquo;s worth noting that SSM does not raise an error if an instance cannot push logs to S3 - the &lt;strong&gt;Amazon S3&lt;/strong&gt; button will redirect you to an object in S3 that does not exist. So if your logs are not appearing in S3 then ensure you&amp;rsquo;ve followed the steps above.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;failed-log-upload-to-s3.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-1//blog/automate-instance-hygiene-with-aws-ssm-1/failed-log-upload-to-s3.png&#34;
    alt=&#34;An empty S3 object page, caused by incorrectly setting up S3 output logging&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;An example showing if logs were not successfully uploaded to S3 - if you get this then double check you&amp;rsquo;ve set everything up right!&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;removing-old-command-logs&#34;&gt;Removing old command logs&lt;/h3&gt;
&lt;p&gt;Now that we have maintenance windows storing our logs in S3, we should ensure we&amp;rsquo;re maintaining a good level of hygiene by removing old logs - otherwise our S3 bucket is going to store more and more logs, costing us more money.&lt;/p&gt;
&lt;p&gt;S3 has a feature called &lt;a href=&#34;https://docs.aws.amazon.com/AmazonS3/latest/dev/object-lifecycle-mgmt.html&#34;&gt;Lifecycle Rules&lt;/a&gt; which tells S3 how to handle objects throughout their lifecycle. We can tell it to move old files to a cheaper storage class, archive them to &lt;a href=&#34;https://aws.amazon.com/glacier/&#34;&gt;S3 Glacier&lt;/a&gt; (AWS&amp;rsquo;s long-term storage service), or just simply delete them!&lt;/p&gt;
&lt;p&gt;Given we&amp;rsquo;re not exactly sentimental with logs, we can define a policy that will remove any logs older than 3 months (90 days).&lt;/p&gt;
&lt;p&gt;This is very easy for us to add, we simply need to make the addition below to our &lt;code&gt;aws_s3_bucket&lt;/code&gt; resource.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_s3_bucket&amp;#34; &amp;#34;script_bucket&amp;#34;&lt;/span&gt; {&lt;span style=&#34;color:#6272a4&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;  # ... other attributes hidden
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;  # Remove old SSM command output logs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;lifecycle_rule&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    id      &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;RemoveOldSSMOutputLogs&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    enabled &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#8be9fd&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    prefix &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ssm_output/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;expiration&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      days &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;90&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can add more rules if you like, such as a &lt;code&gt;transition&lt;/code&gt; block to move it to cold storage before deleting if you wish. Take a look at the &lt;a href=&#34;https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket#using-object-lifecycle&#34;&gt;Terraform documentation&lt;/a&gt; for the resource for example of this.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;s3-lifecycle-policy.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-1//blog/automate-instance-hygiene-with-aws-ssm-1/s3-lifecycle-policy.png&#34;
    alt=&#34;The created S3 lifecycle rule can be seen here, indicating that objects in the ssm_output directory are discarded after 90 days&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Logs older than 90 days certainly do not spark joy&amp;hellip;!&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;With the first two posts of this series, you have enough to be able to create your own automated series of commands that can be executed on your EC2 instances.&lt;/p&gt;
&lt;p&gt;You can also use SSM documents to retrieve files from instances - such as log files. Or even use them to update third-party software on the instances.&lt;/p&gt;
&lt;p&gt;The next post and thereafter we&amp;rsquo;ll be exploring the Command Documents sibling; Automation Documents, and exploring how these can further enhance automation to other AWS services beyond EC2 instances.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Automate Instance Hygiene with AWS SSM: Command Documents</title><enclosure url="https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-0/cover.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-0/</link>
      <pubDate>Thu, 29 Oct 2020 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-0/</guid>-->
      <description>&lt;p&gt;It&amp;rsquo;s been a while since my &lt;a href=&#34;https://jdheyburn.co.uk/blog/assertions-in-gotests-test-generation/&#34;&gt;last post&lt;/a&gt;&amp;hellip; which could be down to me trying to salvage something out of summer! &amp;#x1f605;&lt;/p&gt;
&lt;p&gt;In this post I want to talk a little bit more about &lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/what-is-systems-manager.html&#34;&gt;AWS SSM&lt;/a&gt;. This was something I touched on when discussing patch baselines in a &lt;a href=&#34;https://jdheyburn.co.uk/blog/using-terraform-to-manage-aws-patch-baselines-at-enterprise-scale/#ssm--patch-manager&#34;&gt;previous post&lt;/a&gt;, and within there is another service known as &lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-automation.html&#34;&gt;Automation&lt;/a&gt;. On first glance it may look pretty dull, but once you scratch the surface there are a number of capabilities it can unlock for you. I found a distinct lack of resources on how to write these documents, so this post will aim to help you get started on doing so!&lt;/p&gt;
&lt;p&gt;This will be part one of a four part series about SSM Automation. The outline of the posts will be:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Re-introduction to SSM Documents in general, specifically the Command document by writing our own healthcheck document&lt;/li&gt;
&lt;li&gt;How to automate command documents with SSM Maintenance Windows&lt;/li&gt;
&lt;li&gt;Looking into Automation documents, and integrating them with maintenance windows&lt;/li&gt;
&lt;li&gt;Advanced use case for Automation documents&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;tldr&#34;&gt;tl;dr&lt;/h2&gt;
&lt;p&gt;To summarise the key parts of this post:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SSM can be used to automate several parts of your estate&lt;/li&gt;
&lt;li&gt;Command documents are a means of executing logically indifferent commands across multiple platforms&lt;/li&gt;
&lt;li&gt;How to write a &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-0/#terraforming-command-documents&#34;&gt;basic command document&lt;/a&gt; in Terraform&lt;/li&gt;
&lt;li&gt;How to write a &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-0/#terraforming-verbose-command-documents&#34;&gt;verbose command document&lt;/a&gt; in Terraform&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;This series assumes you have some knowledge of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Terraform&lt;/li&gt;
&lt;li&gt;These AWS services:
&lt;ul&gt;
&lt;li&gt;EC2&lt;/li&gt;
&lt;li&gt;IAM&lt;/li&gt;
&lt;li&gt;S3&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All source code for this post is available on &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/tree/main/aws-ssm-automation-0&#34;&gt;GitHub&lt;/a&gt;, I&amp;rsquo;ll be referencing it throughout.&lt;/p&gt;
&lt;h2 id=&#34;ssm-re-primer&#34;&gt;SSM Re-primer&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve mentioned &lt;a href=&#34;https://jdheyburn.co.uk/blog/using-terraform-to-manage-aws-patch-baselines-at-enterprise-scale/#ssm--patch-manager&#34;&gt;SSM Documents&lt;/a&gt; before; to save you a click:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;An &lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-ssm-docs.html&#34;&gt;SSM Document&lt;/a&gt; is essentially an automation script that you can perform on one or more instances at a time, with conditions to apply different sets of scripts depending on the operating system (OS) platform (i.e. Windows / Linux).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In that post I talk about how &lt;strong&gt;AWS-RunPatchBaseline&lt;/strong&gt; is an example of a &lt;code&gt;COMMAND&lt;/code&gt; document. Another type of document is known as &lt;code&gt;AUTOMATION&lt;/code&gt;. AWS &lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-ssm-docs.html&#34;&gt;describes them&lt;/a&gt; as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Command
&lt;ul&gt;
&lt;li&gt;Uses command documents to run commands&lt;/li&gt;
&lt;li&gt;State Manager uses command documents to apply a configuration&lt;/li&gt;
&lt;li&gt;These actions can be run on one or more targets at any point during the lifecycle of an instance&lt;/li&gt;
&lt;li&gt;Maintenance Windows uses command documents to apply a configuration based on the specified schedule&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Automation
&lt;ul&gt;
&lt;li&gt;Use automation documents when performing common maintenance and deployment tasks such as creating or updating an Amazon Machine Image (AMI)&lt;/li&gt;
&lt;li&gt;State Manager uses automation documents to apply a configuration&lt;/li&gt;
&lt;li&gt;These actions can be run on one or more targets at any point during the lifecycle of an instance&lt;/li&gt;
&lt;li&gt;Maintenance Windows uses automation documents to perform common maintenance and deployment tasks based on the specified schedule&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I like to use the below to differentiate between the two:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Command documents execute scripts on instances&lt;/li&gt;
&lt;li&gt;Automation document can call and orchestrate AWS API endpoints on your behalf, including executing Command documents on instances&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are also &lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-ssm-docs.html&#34;&gt;other types&lt;/a&gt; too which are beyond the scope of this series.&lt;/p&gt;
&lt;h3 id=&#34;ssm-managed-instances&#34;&gt;SSM Managed Instances&lt;/h3&gt;
&lt;p&gt;In order to have command documents executed on your instances, they will need to become &lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/managed_instances.html&#34;&gt;managed instances&lt;/a&gt;. This requires having the below set up correctly on your instances:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the &lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent.html&#34;&gt;SSM Agent&lt;/a&gt; installed on your instance
&lt;ul&gt;
&lt;li&gt;done so by default on all Amazon Linux AMIs and Windows AMIs&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;connectivity from your instances to the &lt;a href=&#34;https://docs.aws.amazon.com/general/latest/gr/ssm.html&#34;&gt;following endpoints&lt;/a&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;https://ssm.REGION.amazonaws.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://ssmmessages.REGION.amazonaws.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://ec2messages.REGION.amazonaws.com&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;the &lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-setting-up-messageAPIs.html&#34;&gt;correct IAM permissions&lt;/a&gt; applied on your EC2 instance profile
&lt;ul&gt;
&lt;li&gt;these are all provided by the AWS IAM policy &lt;a href=&#34;https://console.aws.amazon.com/iam/home#/policies/arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore$serviceLevelSummary&#34;&gt;AmazonSSMManagedInstanceCore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;the Terraform example for this post shows the &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-0/ec2_iam.tf&#34;&gt;policy being attached&lt;/a&gt; to the EC2 IAM role&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optional&lt;/strong&gt;: &lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/setup-instance-profile.html&#34;&gt;additional policies&lt;/a&gt; that may be required based on your use case&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;managed-instances.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-0//blog/automate-instance-hygiene-with-aws-ssm-0/managed-instances.png&#34;
    alt=&#34;AWS SSM Managed Instances view with two instances appearing as online and managed&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;If your instances are appearing in the &lt;a href=&#34;https://console.aws.amazon.com/systems-manager/managed-instances&#34;&gt;managed instances console&lt;/a&gt; then everything is set up correctly; if not then follow the &lt;a href=&#34;https://aws.amazon.com/premiumsupport/knowledge-center/systems-manager-ec2-instance-not-appear/&#34;&gt;troubleshooting guide&lt;/a&gt;.&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;command-documents&#34;&gt;Command documents&lt;/h2&gt;
&lt;p&gt;Documents are defined in either JSON or, &lt;a href=&#34;https://github.com/cblp/yaml-sucks&#34;&gt;for better or for worse&lt;/a&gt;, YAML. You can also define what OS each command should be executed on. This could be helpful if you wanted a healthcheck script to be executed across all (or a subset of) your instances in one action. As an example, my healthcheck script could be to check to see if the CPU is overloaded.&lt;/p&gt;
&lt;h3 id=&#34;constructing-a-healthcheck-script&#34;&gt;Constructing a healthcheck script&lt;/h3&gt;
&lt;p&gt;I could use the below to perform a healthcheck on a Windows box:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$Avg&lt;/span&gt; = (&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;Get-WmiObject&lt;/span&gt; Win32_Processor | &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;Measure-Object&lt;/span&gt; -Property LoadPercentage -Average | &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;Select &lt;/span&gt;Average).Average
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;If&lt;/span&gt; (&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$Avg&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;-gt&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;90&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;Throw&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Instance is unhealthy - Windows&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;Write-Output&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Instance is healthy - Windows&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And the equivalent for Linux would be:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# Sources:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# https://stackoverflow.com/a/9229580&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# https://bits.mdminhazulhaque.io/linux/round-number-in-bash-script.html&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;avg_cpu&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;$(&lt;/span&gt;grep &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;cpu &amp;#39;&lt;/span&gt; /proc/stat | awk &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;{usage=($2+$4)*100/($2+$4+$5)} END {print int(usage)+1}&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;((&lt;/span&gt; avg_cpu &amp;gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;90&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;))&lt;/span&gt;; &lt;span style=&#34;color:#ff79c6&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Instance is unhealthy - Linux&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;exit&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Instance is healthy - Linux&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;You&amp;rsquo;ll notice the scripts output the OS they are running on - this will serve as an explanation for when we run the scripts as a command document later on.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Note these scripts are just rudimentary examples of healthcheck scripts. Your healthcheck script may be checking that services are running ok, whether a task can be performed, etc. For the sake of this example, I&amp;rsquo;ve decided to do a simple check against the CPU load for demonstration.&lt;/p&gt;
&lt;h3 id=&#34;testing-in-aws-ssm-console&#34;&gt;Testing in AWS SSM Console&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s now test them in the AWS SSM Console. For this example I&amp;rsquo;ve spun up two EC2 instances, one Linux and one Windows, &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-0/ec2.tf&#34;&gt;using Terraform&lt;/a&gt;. To run the commands we can navigate to &lt;a href=&#34;https://console.aws.amazon.com/systems-manager/run-command/send-command&#34;&gt;Run Command&lt;/a&gt; in the console, and run &lt;code&gt;AWS-RunPowerShellScript&lt;/code&gt; and &lt;code&gt;AWS-RunShellScript&lt;/code&gt; for both Windows and Linux EC2s respectively. We don&amp;rsquo;t care about logging the output of the scripts just yet. Make sure when running the script you manually select what instance to run it on.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;run-command-script-success-linux.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-0//blog/automate-instance-hygiene-with-aws-ssm-0/run-command-script-success-linux.png&#34;
    alt=&#34;Executing the Linux script successfully on the Linux instance&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;run-command-script-success-windows.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-0//blog/automate-instance-hygiene-with-aws-ssm-0/run-command-script-success-windows.png&#34;
    alt=&#34;Executing the Windows script successfully on the Windows instance&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;We can see they have executed fine. Let&amp;rsquo;s flip the condition in the healthcheck script so we can test them failing (done by changing &lt;code&gt;&amp;gt;&lt;/code&gt; to &lt;code&gt;&amp;lt;&lt;/code&gt;).&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;run-command-script-failure-linux.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-0//blog/automate-instance-hygiene-with-aws-ssm-0/run-command-script-failure-linux.png&#34;
    alt=&#34;AWS indicating a failure in the command due to a failure occuring in the script&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;This is cool - in the script we can define what constitutes as a failure and have this propagate up to AWS - notice how the status was Failed. This will come in use later on in the series.&lt;/p&gt;
&lt;h3 id=&#34;terraforming-command-documents&#34;&gt;Terraforming command documents&lt;/h3&gt;
&lt;p&gt;Now let&amp;rsquo;s get these scripts Terraformed so we can reap the &lt;a href=&#34;https://jdheyburn.co.uk/blog/using-terraform-to-manage-aws-patch-baselines-at-enterprise-scale/#infrastructure-as-code-primer&#34;&gt;benefits&lt;/a&gt; of infrastructure-as-code. First we need to define the document in the YAML format.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# documents/perform_healthcheck.yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;schemaVersion&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;2.2&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: Perform a healthcheck on the target instance
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;mainSteps&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: PerformHealthCheckWindows
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;action&lt;/span&gt;: aws:runPowerShellScript
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;precondition&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;StringEquals&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - platformType
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - Windows
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;runCommand&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;$Avg = (Get-WmiObject Win32_Processor | Measure-Object -Property LoadPercentage -Average | Select Average).Average&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;If ($Avg -gt 90) {&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;  Throw &amp;#34;Instance is unhealthy- Linux&amp;#34;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;Write-Output &amp;#34;Instance is healthy - Windows&amp;#34;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: PerformHealthCheckLinux
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;action&lt;/span&gt;: aws:runShellScript
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;precondition&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;StringEquals&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - platformType
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - Linux
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;runCommand&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;avg_cpu=$(grep &amp;#39;cpu &amp;#39; /proc/stat | awk &amp;#39;{usage=($2+$4)*100/($2+$4+$5)} END {print int(usage)+1}&amp;#39;)&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;if (( avg_cpu &amp;gt; 90 )); then&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;  echo &amp;#34;Instance is unhealthy - Linux&amp;#34;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;  exit 1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;fi&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;echo &amp;#34;Instance is healthy - Linux&amp;#34;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-0/documents/perform_healthcheck.yml&#34;&gt;GitHub URL&lt;/a&gt; for the above&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Since a document is meant to perform an action (or group of actions) on a set of instances regardless of their operating system (OS), only the steps that apply to the OS platform for the instance they are being executed on will be invoked.&lt;/p&gt;
&lt;p&gt;Therefore when we target this document to run on two types of EC2 instances, one Windows and one Linux, the step &lt;code&gt;PerformHealthCheckWindows&lt;/code&gt; will be executed on the Windows box and vice versa for &lt;code&gt;PerformHealthCheckLinux&lt;/code&gt;. This is because of the &lt;code&gt;precondition&lt;/code&gt; key which filters on the &lt;code&gt;platformType&lt;/code&gt; for the instance. Notably as well, we are targeting the appropriate actions for each platform; &lt;code&gt;aws:runPowerShellScript&lt;/code&gt; for Windows and &lt;code&gt;aws:runShellScript&lt;/code&gt; for Linux.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-plugins.html&#34;&gt;See here&lt;/a&gt; for a full list of what actions you can perform in a command document.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Using Terraform you can deploy out the document to your environment like so.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_ssm_document&amp;#34; &amp;#34;perform_healthcheck&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name            &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;PerformHealthcheck&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  document_type   &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Command&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  document_format &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;YAML&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  content &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;file&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;documents/perform_healthcheck.yml&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-0/ssm_command.tf&#34;&gt;GitHub URL&lt;/a&gt; for the above&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Once deployed, we can navigate to System Manager in the AWS Console to invoke the document on our estate. This is done in the same manner as when we ran &lt;code&gt;AWS-RunShellScript&lt;/code&gt; above, except now we are targeting &lt;code&gt;PerformHealthcheck&lt;/code&gt;. Since our document has commands for both Linux and Windows, we can have it invoked across both platform types and only the scripts written for their platform will be invoked.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;command-document-success.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-0//blog/automate-instance-hygiene-with-aws-ssm-0/command-document-success.png&#34;
    alt=&#34;Successfully executing the new command document across both Linux and Windows instances&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;We can see that both executed successfully! You can view the output of each command invocation on each instance like previously. For the screenshot below of the Linux instance, only the Linux step was executed, whereas the Windows step was skipped. You&amp;rsquo;ll notice our message from earlier &amp;ldquo;&lt;code&gt; - Linux&lt;/code&gt;&amp;rdquo; is there, reassuring us that only the Linux script was executed.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;command-document-success-linux.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-0//blog/automate-instance-hygiene-with-aws-ssm-0/command-document-success-linux.png&#34;
    alt=&#34;Focusing on the Linux invocation, highlighting that only the Linux step was invoked&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Let&amp;rsquo;s dive into some more intermediate documents.&lt;/p&gt;
&lt;h2 id=&#34;verbose-command-documents&#34;&gt;Verbose command documents&lt;/h2&gt;
&lt;p&gt;The example above was a very basic example of such a document where we only had a few lines of code to execute. The healthcheck script you write for your service may have several more lines of code to execute, and trying to read lines of code in amongst the document markup format is not easy on the eyes. Here is a snippet of the &lt;strong&gt;AWS-RunPatchBaseline&lt;/strong&gt; document as an example of what I mean. It is written in JSON and has over 100 lines in the &lt;code&gt;runCommand&lt;/code&gt; section.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#6272a4&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;mainSteps&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;precondition&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;StringEquals&amp;#34;&lt;/span&gt;: [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;platformType&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Windows&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;action&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws:runPowerShellScript&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;name&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;PatchWindows&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;inputs&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;timeoutSeconds&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#bd93f9&#34;&gt;7200&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;#34;runCommand&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;# Check the OS version&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;if ([Environment]::OSVersion.Version.Major -le 5) {&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;    Write-Error &amp;#39;This command is not supported on Windows 2003 or lower.&amp;#39;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;    exit -1&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;} elseif ([Environment]::OSVersion.Version.Major -ge 10) {&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;    $sku = (Get-CimInstance -ClassName Win32_OperatingSystem).OperatingSystemSKU&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;    if ($sku -eq 143 -or $sku -eq 144) {&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;        Write-Host &amp;#39;This command is not supported on Windows 2016 Nano Server.&amp;#39;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;        exit -1&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;    }&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;}&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;# Check the SSM agent version&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;$ssmAgentService = Get-ItemProperty &amp;#39;HKLM:SYSTEM\\CurrentControlSet\\Services\\AmazonSSMAgent\\&amp;#39;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;if (-not $ssmAgentService -or $ssmAgentService.Version -lt &amp;#39;2.0.533.0&amp;#39;) {&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;    Write-Host &amp;#39;This command is not supported with SSM Agent version less than 2.0.533.0.&amp;#39;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;    exit -1&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;}&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#6272a4&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;        ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;You can see the &lt;a href=&#34;https://console.aws.amazon.com/systems-manager/documents/AWS-RunPatchBaseline/content&#34;&gt;whole thing&lt;/a&gt; on AWS.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Even from this snippet it is hard to distinguish what is going on. Losing out on &lt;a href=&#34;https://en.wikipedia.org/wiki/Syntax_highlighting&#34;&gt;syntax highlighting&lt;/a&gt; means the code isn&amp;rsquo;t readable by any means. And since this is a JSON file format, we lose type hinting for the language we are writing the script in (PowerShell in this case). If the script was saved in as a PowerShell file (&lt;code&gt;.ps1&lt;/code&gt;) then we&amp;rsquo;d get all those benefits.&lt;/p&gt;
&lt;p&gt;We can fix all these issues by adopting a common pattern when composing command documents. We can have S3 store the script, then have the command document perform these actions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Download the script in question from S3&lt;/li&gt;
&lt;li&gt;Execute the script from the download location&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Such a command document would have a composition as below, whereas this is performing the same healthcheck script previously.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# documents/perform_healthcheck_s3.yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;schemaVersion&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;2.2&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: Perform a healthcheck on the target instance
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;mainSteps&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ff79c6&#34;&gt;action&lt;/span&gt;: aws:downloadContent
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: DownloadScriptWindows
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;precondition&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;StringEquals&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - platformType
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - Windows
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;sourceType&lt;/span&gt;: S3
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;sourceInfo&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;{&amp;#34;path&amp;#34;:&amp;#34;https://s3.amazonaws.com/jdheyburn-scripts/ssm_scripts/PerformHealthcheck.ps1&amp;#34;}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ff79c6&#34;&gt;action&lt;/span&gt;: aws:runPowerShellScript
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: ExecutePerformHealthCheckWindows
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;precondition&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;StringEquals&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - platformType
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - Windows
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;runCommand&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - ..\downloads\PerformHealthcheck.ps1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ff79c6&#34;&gt;action&lt;/span&gt;: aws:downloadContent
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: DownloadScriptLinux
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;precondition&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;StringEquals&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - platformType
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - Linux
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;sourceType&lt;/span&gt;: S3
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;sourceInfo&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;{&amp;#34;path&amp;#34;:&amp;#34;https://s3.amazonaws.com/jdheyburn-scripts/ssm_scripts/perform_healthcheck.sh&amp;#34;}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ff79c6&#34;&gt;action&lt;/span&gt;: aws:runShellScript
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: ExecutePerformHealthCheckLinux
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;precondition&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;StringEquals&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - platformType
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - Linux
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;runCommand&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - ../downloads/perform_healthcheck.sh
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-0/documents/perform_healthcheck_s3.yml&#34;&gt;GitHub URL&lt;/a&gt; for the above&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Note the new action called &lt;code&gt;aws:downloadContent&lt;/code&gt; - which you can view the documentation for &lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-plugins.html#aws-downloadContent&#34;&gt;here&lt;/a&gt;. Again we&amp;rsquo;re using the &lt;code&gt;precondition&lt;/code&gt; key to ensure each platforms downloads their respective script. We&amp;rsquo;re also using the &lt;code&gt;inputs&lt;/code&gt; key to instruct the action where it can download the script from; S3 in this case, at the given S3 bucket location. There is an optional &lt;code&gt;destinationPath&lt;/code&gt; field which allows you to change where it downloads to.&lt;/p&gt;
&lt;p&gt;Once the script is downloaded to the instance, we will need to have it executed. &lt;code&gt;aws:downloadContent&lt;/code&gt; saves the script to a temporary directory for executing SSM command invocations on instances, so we need to reference it in the &lt;code&gt;downloads&lt;/code&gt; directory for it; indicated by the &lt;code&gt;../downloads/perform_healthcheck.sh&lt;/code&gt; command.&lt;/p&gt;
&lt;h3 id=&#34;terraforming-verbose-command-documents&#34;&gt;Terraforming verbose command documents&lt;/h3&gt;
&lt;p&gt;This involves having your script first uploaded to S3. Thankfully, through the power of &lt;a href=&#34;https://jdheyburn.co.uk/blog/using-terraform-to-manage-aws-patch-baselines-at-enterprise-scale/#infrastructure-as-code-primer&#34;&gt;infrastructure-as-code&lt;/a&gt;, you can have this automatically deployed to your environment once the module is written.&lt;/p&gt;
&lt;p&gt;Check the &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-0/ssm_command_s3.tf&#34;&gt;GitHub repository&lt;/a&gt; for the full example, for now let me break down what each bit is doing.&lt;/p&gt;
&lt;h4 id=&#34;kms&#34;&gt;KMS&lt;/h4&gt;
&lt;p&gt;I want my S3 bucket to encrypt objects that are stored there, so we&amp;rsquo;ll need to create a &lt;a href=&#34;https://aws.amazon.com/kms/&#34;&gt;KMS key&lt;/a&gt; and give it an IAM policy permitting any users of the account to decrypt using the key. You can make this more secure by only targeting the IAM roles that require it, instead of the whole account, adopting the &lt;a href=&#34;https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege&#34;&gt;principle of least privilege&lt;/a&gt; best practice &amp;#x1f512;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can read up more about IAM policies &lt;a href=&#34;https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;data&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_iam_policy_document&amp;#34; &amp;#34;kms_allow_decrypt&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;statement&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sid    &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;AllowKMSAdministration&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    effect &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Allow&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    actions &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;kms:Create*&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;kms:Describe*&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;kms:Enable*&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;kms:List*&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;kms:Put*&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;kms:Update*&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;kms:Revoke*&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;kms:Disable*&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;kms:Get*&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;kms:Delete*&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;kms:ScheduleKeyDeletion&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;kms:CancelKeyDeletion&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;principals&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      type        &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;AWS&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      identifiers &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;arn:aws:iam::${data.aws_caller_identity.current.account_id}:user/jdheyburn&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    resources &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;*&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;statement&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sid    &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;AllowDecrypt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    effect &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Allow&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    actions &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;kms:Decrypt&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;principals&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      type        &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;AWS&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      identifiers &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;arn:aws:iam::${data.aws_caller_identity.current.account_id}:root&amp;#34;&lt;/span&gt;]&lt;span style=&#34;color:#6272a4&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;      # TIP: For increased security only give decrypt permissions to roles that need it
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;      # identifiers = [aws_iam_role.vm_base.arn]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    resources &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;*&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_kms_key&amp;#34; &amp;#34;script_bucket_key&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  description &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;This key is used to encrypt bucket objects&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  policy      &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;data&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;aws_iam_policy_document&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;kms_allow_decrypt&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;s3&#34;&gt;S3&lt;/h4&gt;
&lt;p&gt;Next we&amp;rsquo;re creating the S3 bucket to store the scripts; we&amp;rsquo;re encrypting it with the KMS key we created, &lt;code&gt;aws_kms_key.script_bucket_key&lt;/code&gt;. We&amp;rsquo;re also applying an IAM policy on the bucket permitting any users of the account to download anything from the &lt;code&gt;ssm_scripts/&lt;/code&gt; prefix in the bucket (this prefix is where the scripts will be stored).&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_s3_bucket&amp;#34; &amp;#34;script_bucket&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  bucket &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;jdheyburn-scripts&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;  # Encrypt objects stored in S3
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;server_side_encryption_configuration&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;rule&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;apply_server_side_encryption_by_default&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        kms_master_key_id &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_kms_key&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;script_bucket_key&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;arn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        sse_algorithm     &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws:kms&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;data&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_caller_identity&amp;#34; &amp;#34;current&amp;#34;&lt;/span&gt; {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;data&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_iam_policy_document&amp;#34; &amp;#34;s3_allow_script_download&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;statement&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sid    &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;AllowAccountAccess&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    effect &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Allow&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    actions &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;s3:GetObject&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;principals&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      type        &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;AWS&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      identifiers &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;arn:aws:iam::${data.aws_caller_identity.current.account_id}:root&amp;#34;&lt;/span&gt;]&lt;span style=&#34;color:#6272a4&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;      # TIP: For increased security only give decrypt permissions to roles that need it
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;      # identifiers = [aws_iam_role.vm_base.arn]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    resources &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;${aws_s3_bucket.script_bucket.arn}/ssm_scripts/*&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_s3_bucket_policy&amp;#34; &amp;#34;script_bucket_policy&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  bucket &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_s3_bucket&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;script_bucket&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  policy &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;data&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;aws_iam_policy_document&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;s3_allow_script_download&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;store-scripts-in-s3&#34;&gt;Store Scripts in S3&lt;/h4&gt;
&lt;p&gt;Now we are uploading the scripts from their &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/tree/main/aws-ssm-automation-0/scripts&#34;&gt;location in the repository&lt;/a&gt; to S3, at the prefix where we defined an IAM policy to pull them from.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;locals&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  perform_healthcheck_script_fname_windows &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;PerformHealthcheck.ps1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  perform_healthcheck_script_fname_linux   &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;perform_healthcheck.sh&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_s3_bucket_object&amp;#34; &amp;#34;perform_healthcheck_windows&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  bucket  &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_s3_bucket&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;script_bucket&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  key     &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ssm_scripts/${local.perform_healthcheck_script_fname_windows}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  content &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;file&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;scripts/${local.perform_healthcheck_script_fname_windows}&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_s3_bucket_object&amp;#34; &amp;#34;perform_healthcheck_linux&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  bucket  &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_s3_bucket&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;script_bucket&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  key     &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;ssm_scripts/${local.perform_healthcheck_script_fname_linux}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  content &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;file&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;scripts/${local.perform_healthcheck_script_fname_linux}&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;ssm-document&#34;&gt;SSM Document&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_ssm_document&amp;#34; &amp;#34;perform_healthcheck_s3&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name            &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;PerformHealthcheckS3&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  document_type   &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Command&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  document_format &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;YAML&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  content &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;templatefile&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;documents/perform_healthcheck_s3_template.yml&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      bucket_name   &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_s3_bucket&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;script_bucket&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      linux_fname   &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;local&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;perform_healthcheck_script_fname_linux&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      linux_key     &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_s3_bucket_object&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;perform_healthcheck_linux&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      windows_fname &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;local&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;perform_healthcheck_script_fname_windows&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      windows_key   &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_s3_bucket_object&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;perform_healthcheck_windows&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here we&amp;rsquo;re creating an &lt;code&gt;aws_ssm_document&lt;/code&gt; as we had done &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-0/#terraforming-command-documents&#34;&gt;before&lt;/a&gt;, but the document file we&amp;rsquo;re targeting this time is a template file.&lt;/p&gt;
&lt;p&gt;Notice how we have the presence of &lt;code&gt;${bucket_name}&lt;/code&gt; and others? These are template variables. With the use of the &lt;a href=&#34;https://www.terraform.io/docs/configuration/functions/templatefile.html&#34;&gt;Terraform function&lt;/a&gt; &lt;code&gt;templatefile()&lt;/code&gt;, we can insert Terraform variables into the config to have the template name replaced with the value we&amp;rsquo;re passing in. In this case, &lt;code&gt;${bucket_name}&lt;/code&gt; will get replaced with the output of &lt;code&gt;aws_s3_bucket.script_bucket.id&lt;/code&gt;, which is &lt;code&gt;jdheyburn-scripts&lt;/code&gt;, and the same for the remaining variables.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# documents/perform_healthcheck_s3_template.yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;schemaVersion&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;2.2&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: Perform a healthcheck on the target instance
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;mainSteps&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ff79c6&#34;&gt;action&lt;/span&gt;: aws:downloadContent
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: DownloadScriptWindows
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;precondition&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;StringEquals&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - platformType
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - Windows
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;sourceType&lt;/span&gt;: S3
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;sourceInfo&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;{&amp;#34;path&amp;#34;:&amp;#34;https://s3.amazonaws.com/${bucket_name}/${windows_key}&amp;#34;}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ff79c6&#34;&gt;action&lt;/span&gt;: aws:runPowerShellScript
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: ExecutePerformHealthCheckWindows
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;precondition&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;StringEquals&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - platformType
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - Windows
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;runCommand&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - ..\downloads\${windows_fname}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ff79c6&#34;&gt;action&lt;/span&gt;: aws:downloadContent
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: DownloadScriptLinux
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;precondition&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;StringEquals&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - platformType
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - Linux
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;sourceType&lt;/span&gt;: S3
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;sourceInfo&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;{&amp;#34;path&amp;#34;:&amp;#34;https://s3.amazonaws.com/${bucket_name}/${linux_key}&amp;#34;}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ff79c6&#34;&gt;action&lt;/span&gt;: aws:runShellScript
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: ExecutePerformHealthCheckLinux
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;precondition&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;StringEquals&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - platformType
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - Linux
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;inputs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;runCommand&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        - ../downloads/${linux_fname}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-0/documents/perform_healthcheck_s3_template.yml&#34;&gt;GitHub URL&lt;/a&gt; for the above&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&#34;ec2-iam-permissions&#34;&gt;EC2 IAM Permissions&lt;/h4&gt;
&lt;p&gt;For the pattern to work, you&amp;rsquo;ll need to attach an IAM policy to the instance to allow it to pull the scripts from the S3 bucket. If we don&amp;rsquo;t do this then the &lt;code&gt;aws:downloadContent&lt;/code&gt; action will fail. From the Terraform above we&amp;rsquo;ve already applied the corresponding permissions on the S3 bucket, allowing all users and roles in the account to perform &lt;code&gt;s3:GetObject&lt;/code&gt; on the scripts. We&amp;rsquo;ve also allowed done the same for performing &lt;code&gt;kms:Decrypt&lt;/code&gt; on the KMS key that encrypts the S3 objects.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve been following the &lt;a href=&#34;https://github.com/jdheyburn/terraform-examples/blob/main/aws-ssm-automation-0/ec2_iam.tf#L33&#34;&gt;GitHub example&lt;/a&gt;, these required permissions have already been defined - for a recap:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;data&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_iam_policy_document&amp;#34; &amp;#34;ssm_scripts&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;statement&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sid    &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;AllowS3&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    effect &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Allow&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    actions &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;s3:GetObject&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    resources &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;${aws_s3_bucket.script_bucket.arn}/ssm_scripts/*&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;statement&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sid    &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;AllowKMS&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    effect &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Allow&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    actions &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;kms:Decrypt&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    resources &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color:#ff79c6&#34;&gt;aws_kms_key&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;script_bucket_key&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;arn&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_iam_policy&amp;#34; &amp;#34;ssm_scripts&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name        &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;PullSSMScripts&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  description &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Enables instances to download SSM scripts from S3&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  policy &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;data&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;aws_iam_policy_document&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;ssm_scripts&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_iam_role_policy_attachment&amp;#34; &amp;#34;instance_download_scripts&amp;#34;&lt;/span&gt; {&lt;span style=&#34;color:#6272a4&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;  # Change this to point to the role(s) for your instances
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;  role       &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_iam_role&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;vm_base&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  policy_arn &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_iam_policy&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;ssm_scripts&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;arn&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is an important concept to remember about setting IAM policies - you will need to ensure that the correct permissions are applied on both the &lt;em&gt;source&lt;/em&gt; of the requestor, as well as the &lt;em&gt;destination&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id=&#34;testing-verbose-documents&#34;&gt;Testing verbose documents&lt;/h3&gt;
&lt;p&gt;Now, let&amp;rsquo;s give this command a spin in the console. We&amp;rsquo;re going to execute it the same way we did for other documents earlier, except now targeting &lt;code&gt;PerformHealthcheckS3&lt;/code&gt;.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;s3-command-document-success.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-0//blog/automate-instance-hygiene-with-aws-ssm-0/s3-command-document-success.png&#34;
    alt=&#34;Successful invocations for the new document pulling the script to be executed from S3&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;If you got any failures, make sure to dive into the failed invocation and see why it failed. Did it fail because of your healthcheck command? Then it is working as intended! Although if it failed on &lt;code&gt;aws:downloadContent&lt;/code&gt;, check to make sure your instances are running the latest version of SSM agent. You can do this with the &lt;code&gt;AWS-UpdateSSMAgent&lt;/code&gt; SSM document. Don&amp;rsquo;t be like me and spend hours troubleshooting against an out-of-date SSM agent! &amp;#x1f602;&lt;/p&gt;
&lt;blockquote class=&#34;twitter-tweet&#34;&gt;&lt;p lang=&#34;en&#34; dir=&#34;ltr&#34;&gt;Trying to write my next blog post about AWS SSM, and had a load of hours wiped out trying to troubleshoot an issue with SSM agents... turns out AWS AMIs install these bad agent versions by default 😭&lt;a href=&#34;https://t.co/mgFMVyB5fL&#34;&gt;https://t.co/mgFMVyB5fL&lt;/a&gt;&lt;/p&gt;&amp;mdash; Joseph D. Heyburn (@jdheyburn) &lt;a href=&#34;https://twitter.com/jdheyburn/status/1320011701480284162?ref_src=twsrc%5Etfw&#34;&gt;October 24, 2020&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async src=&#34;https://platform.twitter.com/widgets.js&#34; charset=&#34;utf-8&#34;&gt;&lt;/script&gt;


&lt;p&gt;Let&amp;rsquo;s now dive into the output of the Linux instance.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;s3-command-document-success-linux.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-0//blog/automate-instance-hygiene-with-aws-ssm-0/s3-command-document-success-linux.png&#34;
    alt=&#34;Breakdown of steps invoked on Linux for a the command document we executed earlier, Windows steps are skipped&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Just like with the previous document with the script embedded &lt;code&gt;PerformHealthcheck&lt;/code&gt;, we can see the steps conditioned for Windows have been skipped (steps 1-2). Step 3 is where the document is doing real work, downloading the Linux script from the S3 location into the temp directory for SSM, and then executing it in step 4.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I think this has been my longest post thus far, and it&amp;rsquo;s only part one of this series. SSM is a bit of a beast and is often shrugged off as being a pain to set up and troubleshoot. I put that down to it encapsulating several other services, and a lack of tried and tested documented methods - which I hope this series resolves.&lt;/p&gt;
&lt;p&gt;We covered in this post:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What SSM Documents are; and the differences between Command and Automation documents&lt;/li&gt;
&lt;li&gt;How to create our own command documents to execute the same business logic on differing instance platforms&lt;/li&gt;
&lt;li&gt;Adopting best practices by storing scripts in S3 and have a command document orchestrate the download and execution of said scripts&lt;/li&gt;
&lt;li&gt;&amp;hellip; all while written in Terraform!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If any mistakes were made in this post then please &lt;a href=&#34;https://jdheyburn.co.uk/blog/automate-instance-hygiene-with-aws-ssm-0/#contact&#34;&gt;contact&lt;/a&gt; me - thanks for reading! &amp;#x1f603;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Assertions in gotests Test Generation</title><enclosure url="https://jdheyburn.co.uk/blog/assertions-in-gotests-test-generation/cover.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/blog/assertions-in-gotests-test-generation/</link>
      <pubDate>Mon, 20 Jul 2020 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/assertions-in-gotests-test-generation/</guid>-->
      <description>&lt;p&gt;I&amp;rsquo;ve been doing some programming in &lt;a href=&#34;https://golang.org/&#34;&gt;Go&lt;/a&gt; for a side project again, and I&amp;rsquo;m back using &lt;a href=&#34;https://github.com/cweill/gotests&#34;&gt;gotests&lt;/a&gt; to generate unit tests for functions. For this I&amp;rsquo;ve been referencing a post I&amp;rsquo;ve &lt;a href=&#34;https://jdheyburn.co.uk/blog/extending-gotests-for-strict-error-tests/&#34;&gt;previously written&lt;/a&gt; in order to help me get them set up. If you&amp;rsquo;d like more context on the background I&amp;rsquo;d recommend reading there first.&lt;/p&gt;
&lt;p&gt;Today I&amp;rsquo;ll be talking about a small enhancement to how the tests are generated to make use of the &lt;a href=&#34;https://godoc.org/github.com/stretchr/testify/assert&#34;&gt;assert&lt;/a&gt; package within &lt;a href=&#34;https://github.com/stretchr/testify&#34;&gt;testify&lt;/a&gt;. Go already comes with enough for you to write tests, but &lt;code&gt;assert&lt;/code&gt; provides me with more options for comparison in a natural language form. I&amp;rsquo;ll also be adding support for when a test case returns an unexpected error.&lt;/p&gt;
&lt;p&gt;Don&amp;rsquo;t care about the waffle? Jump straight to &lt;a href=&#34;https://jdheyburn.co.uk/blog/assertions-in-gotests-test-generation/#customising-gotests-generated-test-v2&#34;&gt;it here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;recap&#34;&gt;Recap&lt;/h2&gt;
&lt;p&gt;From the last time we visited this, our test code took the format below.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;Test_validateDogName&lt;/span&gt;(t &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;testing.T) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; args &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name &lt;span style=&#34;color:#8be9fd&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tests &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; []&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name    &lt;span style=&#34;color:#8be9fd&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        args    args
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        want    &lt;span style=&#34;color:#8be9fd&#34;&gt;bool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        wantErr &lt;span style=&#34;color:#8be9fd&#34;&gt;error&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Test error was thrown for dog name with symbols&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        args: args{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            name: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;GoodestBoy#1&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        want: &lt;span style=&#34;color:#ff79c6&#34;&gt;false&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        wantErr: errors.&lt;span style=&#34;color:#50fa7b&#34;&gt;New&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;dog cannot have symbols in their name&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; _, tt &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;range&lt;/span&gt; tests {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        t.&lt;span style=&#34;color:#50fa7b&#34;&gt;Run&lt;/span&gt;(tt.name, &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;func&lt;/span&gt;(t &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;testing.T) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            got, err &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;validateDogName&lt;/span&gt;(tt.args.name)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; tt.wantErr &lt;span style=&#34;color:#ff79c6&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; !reflect.&lt;span style=&#34;color:#50fa7b&#34;&gt;DeepEqual&lt;/span&gt;(err, tt.wantErr) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                t.&lt;span style=&#34;color:#50fa7b&#34;&gt;Errorf&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;validateDogName() error = %v, wantErr %v&amp;#34;&lt;/span&gt;, err, tt.wantErr)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; got &lt;span style=&#34;color:#ff79c6&#34;&gt;!=&lt;/span&gt; tt.want {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                t.&lt;span style=&#34;color:#50fa7b&#34;&gt;Errorf&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;validateDogName() = %v, want %v&amp;#34;&lt;/span&gt;, got, tt.want)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Although this works nicely, there is one issue - we&amp;rsquo;re not capturing unexpected errors. Or rather, if an error is returned to &lt;code&gt;err&lt;/code&gt;, and &lt;code&gt;tt.wantErr&lt;/code&gt; is set to &lt;code&gt;nil&lt;/code&gt;, then the test will not fail.&lt;/p&gt;
&lt;p&gt;Okay, so we still have the &lt;code&gt;if got != tt.want&lt;/code&gt; condition to fail the test if needed. Although we still could have this condition pass, we want to make sure we capture the error. The test suite is doing currently is &lt;em&gt;assuming&lt;/em&gt; we don&amp;rsquo;t care about the outcome of &lt;code&gt;err&lt;/code&gt;, just because we didn&amp;rsquo;t want one as described by &lt;code&gt;tt.wantErr&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I seem to remember assumptions being the &lt;del&gt;brother&lt;/del&gt; mother of something&amp;hellip;&lt;/p&gt;

&lt;div style=&#34;position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;&#34;&gt;
  &lt;iframe src=&#34;https://www.youtube.com/embed/G-2NimrRPAQ&#34; style=&#34;position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;&#34; allowfullscreen title=&#34;YouTube Video&#34;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h2 id=&#34;enhancing-for-unexpected-errors&#34;&gt;Enhancing for Unexpected Errors&lt;/h2&gt;
&lt;p&gt;In order to enhance what we have already from the original modified &lt;a href=&#34;https://gist.github.com/jdheyburn/978e7b84dc9c197bcdd41afece2edab5&#34;&gt;function.tmpl&lt;/a&gt;, we could have it output something like this to capture unexpected errors.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;// ... removed for brevity
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; _, tt &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;range&lt;/span&gt; tests {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    t.&lt;span style=&#34;color:#50fa7b&#34;&gt;Run&lt;/span&gt;(tt.name, &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;func&lt;/span&gt;(t &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;testing.T) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        got, err &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;validateDogName&lt;/span&gt;(tt.args.name)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; err &lt;span style=&#34;color:#ff79c6&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; tt.wantErr &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;            t.&lt;span style=&#34;color:#50fa7b&#34;&gt;Errorf&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;validateDogName() unexpected error = %v&amp;#34;&lt;/span&gt;, err)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; tt.wantErr &lt;span style=&#34;color:#ff79c6&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; !reflect.&lt;span style=&#34;color:#50fa7b&#34;&gt;DeepEqual&lt;/span&gt;(err, tt.wantErr) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            t.&lt;span style=&#34;color:#50fa7b&#34;&gt;Errorf&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;validateDogName() error = %v, wantErr %v&amp;#34;&lt;/span&gt;, err, tt.wantErr)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; got &lt;span style=&#34;color:#ff79c6&#34;&gt;!=&lt;/span&gt; tt.want {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            t.&lt;span style=&#34;color:#50fa7b&#34;&gt;Errorf&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;validateDogName() = %v, want %v&amp;#34;&lt;/span&gt;, got, tt.want)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The highlighted lines show the new addition. This is something we could implement fairly easily. On the other hand, the &lt;code&gt;assert&lt;/code&gt; library gives us a lot more to play with. It essentially is doing the same as the above under the hood, albeit in a cleaner fashion&amp;hellip; and I&amp;rsquo;m all for better code readability!&lt;/p&gt;
&lt;h3 id=&#34;rewriting-for-assert&#34;&gt;Rewriting for Assert&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;Test_validateDogName&lt;/span&gt;(t &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;testing.T) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; args &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name &lt;span style=&#34;color:#8be9fd&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tests &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; []&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name    &lt;span style=&#34;color:#8be9fd&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        args    args
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        want    &lt;span style=&#34;color:#8be9fd&#34;&gt;bool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        wantErr &lt;span style=&#34;color:#8be9fd&#34;&gt;error&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Test error was thrown for dog name with symbols&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        args: args{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            name: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;GoodestBoy#1&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        want: &lt;span style=&#34;color:#ff79c6&#34;&gt;false&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        wantErr: errors.&lt;span style=&#34;color:#50fa7b&#34;&gt;New&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;dog cannot have symbols in their name&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; _, tt &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;range&lt;/span&gt; tests {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        t.&lt;span style=&#34;color:#50fa7b&#34;&gt;Run&lt;/span&gt;(tt.name, &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;func&lt;/span&gt;(t &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;testing.T) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            got, err &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;validateDogName&lt;/span&gt;(tt.args.name)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; err &lt;span style=&#34;color:#ff79c6&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; tt.wantErr &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;                assert.&lt;span style=&#34;color:#50fa7b&#34;&gt;Fail&lt;/span&gt;(t, fmt.&lt;span style=&#34;color:#50fa7b&#34;&gt;Sprintf&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Error not expected but got one:\n&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;+&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;                        &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;error: %q&amp;#34;&lt;/span&gt;, err),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;                )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; tt.wantErr &lt;span style=&#34;color:#ff79c6&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;                assert.&lt;span style=&#34;color:#50fa7b&#34;&gt;EqualError&lt;/span&gt;(t, err, tt.wantErr.&lt;span style=&#34;color:#50fa7b&#34;&gt;Error&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;            assert.&lt;span style=&#34;color:#50fa7b&#34;&gt;Equal&lt;/span&gt;(t, tt.want, got)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The above code block is what we get when we take the test code above that is using the &lt;code&gt;t.Errorf&lt;/code&gt; function call to record a test failure, and rewrite it to use &lt;code&gt;assert&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;What we now need to do is have &lt;code&gt;gotests&lt;/code&gt; generate it for us.&lt;/p&gt;
&lt;h2 id=&#34;customising-gotests-generated-test-v2&#34;&gt;Customising Gotests Generated Test v2&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;ll be following a process similar to when I &lt;a href=&#34;https://jdheyburn.co.uk/blog/extending-gotests-for-strict-error-tests/#customising-gotests-generated-test&#34;&gt;last did this&lt;/a&gt;. I&amp;rsquo;m still using VSCode, so you&amp;rsquo;ll need to find the &lt;a href=&#34;https://github.com/cweill/gotests/#demo&#34;&gt;correct plugin&lt;/a&gt; for your editor.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check out gotests and copy the templates directory to a place of your choosing
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git clone https://github.com/cweill/gotests.git&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cp -R gotests/internal/render/templates ~/.vscode/gotests/templates&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;In order for us to achieve the generated test using &lt;code&gt;assert&lt;/code&gt;, this time we&amp;rsquo;re going to need to edit two files; &lt;code&gt;function.tmpl&lt;/code&gt; and &lt;code&gt;results.tmpl&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Overwrite the contents of &lt;code&gt;function.tmpl&lt;/code&gt; with the &lt;a href=&#34;https://gist.github.com/jdheyburn/94eb1513395ae46eac6aa9721d089d3c#file-function-tmpl&#34;&gt;contents of this Gist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Overwrite the contents of &lt;code&gt;results.tmpl&lt;/code&gt; with the &lt;a href=&#34;https://gist.github.com/jdheyburn/94eb1513395ae46eac6aa9721d089d3c#file-results-tmpl&#34;&gt;contents of this Gist&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Add the following setting to VSCode’s settings.json
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;go.generateTestsFlags&amp;quot;: [&amp;quot;--template_dir=~/.vscode/gotests/templates&amp;quot;]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now we can generate tests of functions that return the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;only returns an error&lt;/li&gt;
&lt;li&gt;a value, and an error&lt;/li&gt;
&lt;li&gt;multiple values, and an error&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I have the &lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=golang.go&#34;&gt;Go plugin&lt;/a&gt; for VSCode, so I just need to right click over a function to have the dropdown menu appear with an option to generate tests.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;generate-unit-tests.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/assertions-in-gotests-test-generation//blog/assertions-in-gotests-test-generation/generate-unit-tests.png&#34;
    alt=&#34;VSCode dropdown with Go plugin&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;only-returns-an-error&#34;&gt;Only returns an error&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;Test_validateDogName&lt;/span&gt;(t &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;testing.T) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; args &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name &lt;span style=&#34;color:#8be9fd&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tests &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; []&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name    &lt;span style=&#34;color:#8be9fd&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        args    args
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        wantErr &lt;span style=&#34;color:#8be9fd&#34;&gt;error&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#6272a4&#34;&gt;// TODO: Add test cases.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; _, tt &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;range&lt;/span&gt; tests {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        t.&lt;span style=&#34;color:#50fa7b&#34;&gt;Run&lt;/span&gt;(tt.name, &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;func&lt;/span&gt;(t &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;testing.T) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            err &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;validateDogName&lt;/span&gt;(tt.args.name)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; err &lt;span style=&#34;color:#ff79c6&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; tt.wantErr &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                assert.&lt;span style=&#34;color:#50fa7b&#34;&gt;Fail&lt;/span&gt;(t, fmt.&lt;span style=&#34;color:#50fa7b&#34;&gt;Sprintf&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Error not expected but got one:\n&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;+&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;error: %q&amp;#34;&lt;/span&gt;, err),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; tt.wantErr &lt;span style=&#34;color:#ff79c6&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                assert.&lt;span style=&#34;color:#50fa7b&#34;&gt;EqualError&lt;/span&gt;(t, err, tt.wantErr.&lt;span style=&#34;color:#50fa7b&#34;&gt;Error&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;returns-a-value-and-an-error&#34;&gt;Returns a value, and an error&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;Test_validateDogName&lt;/span&gt;(t &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;testing.T) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; args &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name &lt;span style=&#34;color:#8be9fd&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tests &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; []&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name    &lt;span style=&#34;color:#8be9fd&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        args    args
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        want    &lt;span style=&#34;color:#8be9fd&#34;&gt;bool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        wantErr &lt;span style=&#34;color:#8be9fd&#34;&gt;error&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#6272a4&#34;&gt;// TODO: Add test cases.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; _, tt &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;range&lt;/span&gt; tests {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        t.&lt;span style=&#34;color:#50fa7b&#34;&gt;Run&lt;/span&gt;(tt.name, &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;func&lt;/span&gt;(t &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;testing.T) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            got, err &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;validateDogName&lt;/span&gt;(tt.args.name)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; err &lt;span style=&#34;color:#ff79c6&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; tt.wantErr &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                assert.&lt;span style=&#34;color:#50fa7b&#34;&gt;Fail&lt;/span&gt;(t, fmt.&lt;span style=&#34;color:#50fa7b&#34;&gt;Sprintf&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Error not expected but got one:\n&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;+&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;error: %q&amp;#34;&lt;/span&gt;, err),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; tt.wantErr &lt;span style=&#34;color:#ff79c6&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                assert.&lt;span style=&#34;color:#50fa7b&#34;&gt;EqualError&lt;/span&gt;(t, err, tt.wantErr.&lt;span style=&#34;color:#50fa7b&#34;&gt;Error&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            assert.&lt;span style=&#34;color:#50fa7b&#34;&gt;Equal&lt;/span&gt;(t, tt.want, got)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;returns-multiple-values-and-an-error&#34;&gt;Returns multiple values, and an error&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;Test_validateDogName&lt;/span&gt;(t &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;testing.T) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; args &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name &lt;span style=&#34;color:#8be9fd&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tests &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; []&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name    &lt;span style=&#34;color:#8be9fd&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        args    args
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        want    &lt;span style=&#34;color:#8be9fd&#34;&gt;bool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        want1   &lt;span style=&#34;color:#8be9fd&#34;&gt;bool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        wantErr &lt;span style=&#34;color:#8be9fd&#34;&gt;error&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#6272a4&#34;&gt;// TODO: Add test cases.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; _, tt &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;range&lt;/span&gt; tests {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        t.&lt;span style=&#34;color:#50fa7b&#34;&gt;Run&lt;/span&gt;(tt.name, &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;func&lt;/span&gt;(t &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;testing.T) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            got, got1, err &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;validateDogName&lt;/span&gt;(tt.args.name)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; err &lt;span style=&#34;color:#ff79c6&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; tt.wantErr &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                assert.&lt;span style=&#34;color:#50fa7b&#34;&gt;Fail&lt;/span&gt;(t, fmt.&lt;span style=&#34;color:#50fa7b&#34;&gt;Sprintf&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Error not expected but got one:\n&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;+&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;error: %q&amp;#34;&lt;/span&gt;, err),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; tt.wantErr &lt;span style=&#34;color:#ff79c6&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                assert.&lt;span style=&#34;color:#50fa7b&#34;&gt;EqualError&lt;/span&gt;(t, err, tt.wantErr.&lt;span style=&#34;color:#50fa7b&#34;&gt;Error&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            assert.&lt;span style=&#34;color:#50fa7b&#34;&gt;Equal&lt;/span&gt;(t, tt.want, got)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            assert.&lt;span style=&#34;color:#50fa7b&#34;&gt;Equal&lt;/span&gt;(t, tt.want1, got1)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;bonus-only-returns-values&#34;&gt;Bonus: Only returns values&lt;/h3&gt;
&lt;p&gt;The below example is for a function that doesn&amp;rsquo;t produce any errors, but I&amp;rsquo;m including it for the sake of completeness.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;Test_validateDogName&lt;/span&gt;(t &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;testing.T) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; args &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name &lt;span style=&#34;color:#8be9fd&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tests &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; []&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name &lt;span style=&#34;color:#8be9fd&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        args args
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        want &lt;span style=&#34;color:#8be9fd&#34;&gt;bool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#6272a4&#34;&gt;// TODO: Add test cases.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; _, tt &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;range&lt;/span&gt; tests {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        t.&lt;span style=&#34;color:#50fa7b&#34;&gt;Run&lt;/span&gt;(tt.name, &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;func&lt;/span&gt;(t &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;testing.T) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            got &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;validateDogName&lt;/span&gt;(tt.args.name)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            assert.&lt;span style=&#34;color:#50fa7b&#34;&gt;Equal&lt;/span&gt;(t, tt.want, got)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;thats-it&#34;&gt;That&amp;rsquo;s It!&lt;/h2&gt;
&lt;p&gt;This is just a short update to an enhancement I &lt;a href=&#34;https://jdheyburn.co.uk/blog/extending-gotests-for-strict-error-tests/&#34;&gt;previously made&lt;/a&gt; to gotests. The &lt;code&gt;assert&lt;/code&gt; library is awesome for test cases and it&amp;rsquo;s great to have it autogenerated in my tests too.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Using Terraform to Manage AWS Patch Baselines at Enterprise Scale</title><enclosure url="https://jdheyburn.co.uk/blog/using-terraform-to-manage-aws-patch-baselines-at-enterprise-scale/cover.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/blog/using-terraform-to-manage-aws-patch-baselines-at-enterprise-scale/</link>
      <pubDate>Thu, 02 Jul 2020 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/using-terraform-to-manage-aws-patch-baselines-at-enterprise-scale/</guid>-->
      <description>&lt;p&gt;If you’re new to AWS and patching principles then continue reading, else you can &lt;a href=&#34;https://jdheyburn.co.uk/blog/using-terraform-to-manage-aws-patch-baselines-at-enterprise-scale/#data-sources-for-patch-baselines&#34;&gt;skip to juicy stuff below&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;aws-primer-cloud&#34;&gt;AWS Primer &amp;#x2601;&amp;#xfe0f;&lt;/h2&gt;
&lt;h3 id=&#34;ec2&#34;&gt;EC2&lt;/h3&gt;
&lt;p&gt;Amazon Web Services (AWS) provides Cloud resources to those that require it, &lt;a href=&#34;https://www.lastweekinaws.com/blog/4-reasons-lyft-is-smart-to-pay-aws-300m/&#34;&gt;for a cost&lt;/a&gt; mind you. One resource in particular is a box-standard server known on AWS as &lt;a href=&#34;https://aws.amazon.com/ec2/&#34;&gt;EC2 Instances&lt;/a&gt; which you can secure shell (&lt;a href=&#34;https://www.ssh.com/ssh/&#34;&gt;SSH&lt;/a&gt;) into and do whatever you like with it - the world is now officially your oyster! &amp;#x1f9aa;&lt;/p&gt;
&lt;p&gt;Just like with your personal laptop, computer, mobile phone, etc., you need to patch your servers with the latest security updates to ensure that you are protected from any adversary gaining access to your devices.&lt;/p&gt;
&lt;p&gt;If they were to gain unauthorised access, they can execute whatever they like on there; whether that be sniffing around your network for some sensitive files, or &lt;a href=&#34;https://www.cybersecurity-insiders.com/hackers-cyber-attack-amazon-cloud-to-mine-bitcoins/&#34;&gt;mining cryptocurrency&lt;/a&gt; on your paid-for resources. Just like you earlier - your world is now officially &lt;em&gt;their&lt;/em&gt; oyster!&lt;/p&gt;
&lt;p&gt;EC2 instances are operated by you, and as per the &lt;a href=&#34;https://aws.amazon.com/compliance/shared-responsibility-model/&#34;&gt;AWS Shared Responsibility Model&lt;/a&gt;, &lt;em&gt;you&lt;/em&gt; are responsible for ensuring the software is kept secured.&lt;/p&gt;
&lt;h3 id=&#34;ssm--patch-manager&#34;&gt;SSM &amp;amp; Patch Manager&lt;/h3&gt;
&lt;p&gt;AWS have a service used to help with the administration of EC2 instances called &lt;a href=&#34;https://aws.amazon.com/systems-manager/&#34;&gt;Systems Manager (SSM*)&lt;/a&gt;. SSM is a bit of a beast and covers a lot of different functionalitie, going deep on SSM is beyond the scope of this article - so you can read more about it on the &lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/what-is-systems-manager.html&#34;&gt;AWS documentation&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;* SSM formerly was an acronym for Simple Systems Manager - I guess it got more complex than they thought!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To help you keep your instances patched, AWS provided the &lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-patch.html&#34;&gt;Patch Manager&lt;/a&gt; - within there it contains a number of tools to help with this. These are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/about-patch-baselines.html&#34;&gt;&lt;strong&gt;Patch Baselines&lt;/strong&gt;&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;a policy which filters available patches for your instances to what should be installed on it&lt;/li&gt;
&lt;li&gt;filters which you can apply to a baseline include
&lt;ul&gt;
&lt;li&gt;how many days since the patch was released&lt;/li&gt;
&lt;li&gt;the severity and classification of the patch&lt;/li&gt;
&lt;li&gt;or even explicitly deny individual patches if you know they introduce a bug&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;e.g. only install security classified patches marked as critical severity which have been released more than 7 days ago&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-patch-patchgroups.html&#34;&gt;&lt;strong&gt;Patch Groups&lt;/strong&gt;&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;a label which joins together patch baselines, to what instances they should be applied to&lt;/li&gt;
&lt;li&gt;they apply to instances as a tag at a key named &lt;code&gt;Patch Group&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To then perform a patch event on an instance, you will need to execute an SSM Document known as &lt;strong&gt;AWS-RunPatchBaseline&lt;/strong&gt; on your target instances.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;An &lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-ssm-docs.html&#34;&gt;SSM Document&lt;/a&gt; is essentially an automation script that you can perform on one or more instances at a time, with conditions to apply different sets of scripts depending on the operating system (OS) platform (i.e. Windows / Linux).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;AWS-RunPatchBaseline&lt;/strong&gt; is one such example of a document that has both Windows and Linux stages, and it will check and install patches against the patch baseline that has been applied to your instance, via a patch group.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;However if an EC2 instance is not assigned to a patch group, then AWS will pick the &lt;strong&gt;default baseline&lt;/strong&gt; for that instances operating system&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Say you had a security policy of ensuring instances must check for patches once a week - there&amp;rsquo;s no need to manually execute the SSM Document yourself. &lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-maintenance.html&#34;&gt;Maintenance Windows&lt;/a&gt; can help with that. They are essentially a &lt;a href=&#34;https://en.wikipedia.org/wiki/Cron#CRON_expression&#34;&gt;cron expression&lt;/a&gt; that you define to then execute a task. In this case we can create a maintenance window to execute the AWS-RunPatchBaseline document on a weekly basis.&lt;/p&gt;
&lt;p&gt;The resulting relationship of all the above looks like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Maintenance Windows ──┬── invokes ───&amp;gt; SSM Document ─── queries ───&amp;gt; Patch Baseline
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                      |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                      |
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                      └── targets ───&amp;gt; Patch Groups ─── assigned to ───&amp;gt; EC2 Instances
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;infrastructure-as-code-primer&#34;&gt;Infrastructure as Code Primer&lt;/h2&gt;
&lt;p&gt;Creating your resources through a web UI such as AWS Console is okay for learning in. But how do you ensure your infrastructure is repeatable across several environments? Not only that, how do you easily keep a history of the state of your application infrastructure?&lt;/p&gt;
&lt;p&gt;This is where &lt;a href=&#34;https://en.wikipedia.org/wiki/Infrastructure_as_code&#34;&gt;infrastructure as code&lt;/a&gt; (IAC) comes in. IAC is where all your infrastructure resources are defined in code, and published onto a source code repository of your choosing (GitHub, GitLab, etc.).&lt;/p&gt;
&lt;p&gt;That way you can ensure the code that defines your infrastructure can be repeated across your different application environments, and be able to view history of code changes.&lt;/p&gt;
&lt;p&gt;It also acts as a single source of truth that everyone in your team can depend on for what the application state looks like.&lt;/p&gt;
&lt;p&gt;There are several IAC tools that you can use:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://aws.amazon.com/cloudformation/&#34;&gt;CloudFormation&lt;/a&gt; is AWS’s home-grown solution exclusively for AWS services&lt;/li&gt;
&lt;li&gt;There are platform-agnostic tools such as &lt;a href=&#34;https://www.terraform.io/&#34;&gt;Terraform&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;written in its own proprietary language HashiCorp Configuration Language (HCL)&lt;/li&gt;
&lt;li&gt;I’ve talked about Terraform before in a &lt;a href=&#34;https://jdheyburn.co.uk/blog/on-becoming-an-open-source-software-contributor/#background&#34;&gt;previous post&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Or for a language-agnostic, platform-agnostic tool - &lt;a href=&#34;https://www.pulumi.com/why-pulumi/&#34;&gt;Pulumi&lt;/a&gt; is the one for you.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the case of Terraform, it keeps the current state of your infrastructure stack in a file stored in a shared remote location. This is known as the &lt;a href=&#34;https://www.terraform.io/docs/state/index.html&#34;&gt;state file&lt;/a&gt;, and it is used by Terraform to understand what resources Terraform is aware of in your platform.&lt;/p&gt;
&lt;h2 id=&#34;patch-baselines-in-an-enterprise-environment&#34;&gt;Patch Baselines in an Enterprise Environment&lt;/h2&gt;
&lt;p&gt;When managing Terraform in an enterprise environment, it is a best practice to split up the infrastructure on the structure of the teams working on them, known as workspaces. This is something advised by &lt;a href=&#34;https://www.terraform.io/docs/cloud/guides/recommended-practices/part1.html#the-recommended-terraform-workspace-structure&#34;&gt;Terraform themselves&lt;/a&gt;. For example, you would have a workspace for billing, and one for networking. That way you can ensure teams can work effectively without treading on each others&amp;rsquo; toes.&lt;/p&gt;
&lt;p&gt;The same principle can be applied to a security team that defines security policies, who will also write up patching policies. These policies will be followed by teams working in different workspaces to understand at minimum what patches should be installed on a server, and how quickly to install them.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Check out the University of Exeter’s own &lt;a href=&#34;https://www.exeter.ac.uk/media/level1/academicserviceswebsite/it/recordsmanagementservice/policydocuments/Patch_Management_Policy_FINAL.pdf&#34;&gt;patching policy&lt;/a&gt; for an example.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For example, an enterprise may have a patching policy that states all patches with a severity marked as ‘Critical’ or ‘Important’ must be installed within 14 days of its release. If an out-of-band (OOB) patch is released, then it must be installed within 3 days.&lt;/p&gt;
&lt;p&gt;These folk are the same lot who will also conjure up the &lt;strong&gt;Patch Baselines&lt;/strong&gt; we learned about earlier.&lt;/p&gt;
&lt;p&gt;They may deploy these patch baselines in a different Terraform workspace from yours. If that were the case, then if you wanted to refer back to these in your Terraform workspace, then the &lt;a href=&#34;https://www.terraform.io/docs/providers/terraform/d/remote_state.html&#34;&gt;&lt;code&gt;remote_state&lt;/code&gt;&lt;/a&gt; resource is something you might need.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# This example references a subnet_id created in another workspace
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;data&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;terraform_remote_state&amp;#34; &amp;#34;vpc&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  backend &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;remote&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  config &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    organization &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;hashicorp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    workspaces &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;vpc-prod&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_instance&amp;#34; &amp;#34;foo&amp;#34;&lt;/span&gt; {&lt;span style=&#34;color:#6272a4&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;  # ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;  subnet_id &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;data&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;terraform_remote_state&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;vpc&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;outputs&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;subnet_id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So the above works if all parties are using Terraform workspaces. But what if you&amp;rsquo;re using Terraform, and the security team (who are managing patch baselines) are using something different like the mentioned CloudFormation or Pulumi.&lt;/p&gt;
&lt;p&gt;If you are creating &lt;strong&gt;patch groups&lt;/strong&gt; in Terraform - you won’t be able to reference the &lt;strong&gt;patch baselines&lt;/strong&gt; they’ve created, because they are in a different state file.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_ssm_patch_group&amp;#34; &amp;#34;front_end_servers&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  baseline_id &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_patch_baseline&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;front_end_servers&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt; # Terraform does not know about this resource!
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;  patch_group &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;front_end_servers&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You could of course replicate the patch baseline they’ve created in your Terraform code, but then that does not scale in an enterprise.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_ssm_patch_baseline&amp;#34; &amp;#34;front_end_servers&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;front-end-servers&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ...&lt;span style=&#34;color:#6272a4&#34;&gt; # variables removed for brevity
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_ssm_patch_group&amp;#34; &amp;#34;front_end_servers&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  baseline_id &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_patch_baseline&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;front_end_servers&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt; # Not good!
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;  patch_group &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;front_end_servers&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is what we want to avoid.&lt;/p&gt;
&lt;h3 id=&#34;data-sources-for-patch-baselines&#34;&gt;Data Sources For Patch Baselines&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://www.terraform.io/docs/configuration/data-sources.html&#34;&gt;Data sources&lt;/a&gt; in Terraform allow you to pull resources in your platform that may exist in a separate state file to yours, or even a completely different IAC tool to Terraform, such as CloudFormation. What we need is a data source component for the &lt;code&gt;aws_ssm_patch_baseline&lt;/code&gt; resource.&lt;/p&gt;
&lt;p&gt;With a &lt;a href=&#34;https://github.com/terraform-providers/terraform-provider-aws/pull/9486&#34;&gt;pull request&lt;/a&gt; I made to the &lt;a href=&#34;https://github.com/terraform-providers/terraform-provider-aws&#34;&gt;terraform-aws-provider&lt;/a&gt; project - I added the ability to pull in &lt;strong&gt;patch baseline&lt;/strong&gt; resources that exist in the AWS account you are targeting, meaning if the enterprise security team had deployed a patch baseline via a different stack, then you can reference that in your &lt;strong&gt;patch group&lt;/strong&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# This resource is defined outside of the current working Terraform state
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# So we are making a call to retrieve the ID of the resource in AWS
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;data&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_ssm_patch_baseline&amp;#34; &amp;#34;front_end_servers&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  owner            &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Self&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name_prefix      &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;FrontEndServers&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  operating_system &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;CENTOS&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_ssm_patch_group&amp;#34; &amp;#34;front_end_servers&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  baseline_id &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;data&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_patch_baseline&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;front_end_servers&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  patch_group &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;front_end_servers&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_instance&amp;#34; &amp;#34;front_end_server&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ...&lt;span style=&#34;color:#6272a4&#34;&gt; # variables removed for brevity
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  tags &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;#34;Patch Group&amp;#34; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_patch_group&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;front_end_servers&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The documentation for its usage can be found &lt;a href=&#34;https://www.terraform.io/docs/providers/aws/d/ssm_patch_baseline.html&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Another scenario might be where you want to reuse the &lt;strong&gt;patch baselines&lt;/strong&gt; that AWS has created in your account. One such baseline is &lt;code&gt;AWS-WindowsPredefinedPatchBaseline-OS-Applications&lt;/code&gt;, which patches both the Windows OS, and selected Microsoft applications installed on the Windows server.&lt;/p&gt;
&lt;p&gt;Currently this is not the default baseline for Windows. So if you want to have this baseline assigned to a patch group, you could do something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-hcl&#34; data-lang=&#34;hcl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;data&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_ssm_patch_baseline&amp;#34; &amp;#34;windows_predefined_os_and_apps&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  owner            &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;AWS&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name_prefix      &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;AWS-WindowsPredefinedPatchBaseline-OS-Applications&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  operating_system &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;WINDOWS&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_ssm_patch_group&amp;#34; &amp;#34;active_directory&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  baseline_id &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;data&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_patch_baseline&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;windows_predefined_os_and_apps&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  patch_group &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;active_directory&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;resource&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;aws_instance&amp;#34; &amp;#34;active_directory&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ...&lt;span style=&#34;color:#6272a4&#34;&gt; # variables removed for brevity
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  tags &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;#34;Patch Group&amp;#34; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;aws_ssm_patch_group&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;active_directory&lt;/span&gt;.&lt;span style=&#34;color:#ff79c6&#34;&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This is a relatively short post - but it was just a quick introduction to AWS Patch Manager. I hope you find it helpful in applying your enterprise patching strategy to your product teams!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Reverse Proxy Multiple Domains Using Caddy 2</title><enclosure url="https://jdheyburn.co.uk/blog/reverse-proxy-multiple-domains-using-caddy-2/cover.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/blog/reverse-proxy-multiple-domains-using-caddy-2/</link>
      <pubDate>Tue, 09 Jun 2020 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/reverse-proxy-multiple-domains-using-caddy-2/</guid>-->
      <description>&lt;p&gt;During lockdown, I&amp;rsquo;ve spent a bit of time improving our home network. The bigger picture of which I&amp;rsquo;ll write about in a future post. But for now, I came across some challenges with running &lt;a href=&#34;https://caddyserver.com/&#34;&gt;Caddy 2&lt;/a&gt; as a reverse proxy for multiple domains used internally.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve stumbled across this looking for the end config file for Caddy, then you can &lt;a href=&#34;https://jdheyburn.co.uk/blog/reverse-proxy-multiple-domains-using-caddy-2/#caddy-configuration&#34;&gt;skip there&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;A few months back I kitted out my home with some &lt;a href=&#34;https://www.ui.com/products/#unifi&#34;&gt;Ubiquiti UniFi&lt;/a&gt; gear to fix our crappy Wifi at home, following inspiration from &lt;a href=&#34;https://www.troyhunt.com/ubiquiti-all-the-things-how-i-finally-fixed-my-dodgy-wifi/&#34;&gt;Troy Hunt&lt;/a&gt; and &lt;a href=&#34;https://scotthelme.co.uk/my-ubiquiti-home-network/&#34;&gt;Scott Helme&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In order to administrate UniFi devices, you&amp;rsquo;ll need the &lt;a href=&#34;https://www.ui.com/unifi/unifi-cloud-key/&#34;&gt;UniFi Cloud Key&lt;/a&gt; which runs the Controller software to do just that. Although if you have a spare Raspberry Pi lying around, you can download the &lt;a href=&#34;https://www.ui.com/download/unifi/&#34;&gt;software&lt;/a&gt; for free and run it on there - this is what I did.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve also wanted to protect my home network with a self-hosted DNS server, such as &lt;a href=&#34;https://pi-hole.net/&#34;&gt;PiHole&lt;/a&gt;. I won&amp;rsquo;t go into depth about how that was done, but you can follow &lt;a href=&#34;https://scotthelme.co.uk/securing-dns-across-all-of-my-devices-with-pihole-dns-over-https-1-1-1-1/&#34;&gt;Scott Helme&amp;rsquo;s guide&lt;/a&gt; on how you can set the same up.&lt;/p&gt;
&lt;p&gt;Both of these services can be accessed through web browsers at the IP address and ports where they are being hosted, such as &lt;code&gt;http://192.168.1.10:8093/admin/&lt;/code&gt; in the case of PiHole. Having to remember the IP address and the port can be a pain. We can front these services with a rememberable domain name which points to these services - of which I&amp;rsquo;ve written about in a &lt;a href=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-2-custom-domain/&#34;&gt;previous post&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;securing-with-https&#34;&gt;Securing with HTTPS&lt;/h3&gt;
&lt;p&gt;The web is evolving, and there is no reason why we should access services via insecure HTTP, that includes services that are only running on an internal network such as a home network. Web browsers nowadays give you a warning when you are connecting to website over an unencrypted connection.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;insecure-pihole.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/reverse-proxy-multiple-domains-using-caddy-2//blog/reverse-proxy-multiple-domains-using-caddy-2/insecure-pihole.png&#34;
    alt=&#34;Insecure PiHole connection&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Simply accessing over HTTP is not an option, when browsers present us with a huge warning message&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href=&#34;https://caddyserver.com/&#34;&gt;Caddy&lt;/a&gt; is a web server similar to Apache, nginx, et al., but it is different in that it enables HTTPS by default and upgrades requests from HTTP to HTTPS. Managing certificates for HTTPS is a pain - so Caddy does that too, so long as you can prove you own the domain you are hosting requests at. We can use Caddy in a reverse proxy mode, allowing us to access services at endpoints such as &lt;code&gt;https://pihole.domain.local&lt;/code&gt; in our browsers and forward them to the corresponding IP address hosting the service.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A &lt;a href=&#34;https://www.cloudflare.com/learning/cdn/glossary/reverse-proxy/&#34;&gt;reverse proxy&lt;/a&gt; is a service that simply forwards client requests onto the server on the clients behalf.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;proving-domain-ownership&#34;&gt;Proving Domain Ownership&lt;/h2&gt;
&lt;p&gt;Caddy uses &lt;a href=&#34;https://letsencrypt.org/&#34;&gt;Let&amp;rsquo;s Encrypt&lt;/a&gt; (LE) to provide certificates for domains. Since domains can be exposed publicly, we will have to prove ownership of the domain to have LE issue certificates on our behalf - so we&amp;rsquo;ll have to purchase the domain from a registrar. I talked about how to do this for this website &lt;a href=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-2-custom-domain/#acquire-a-domain&#34;&gt;in the past&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;LE supports several &lt;a href=&#34;https://letsencrypt.org/docs/challenge-types/&#34;&gt;challenge methods&lt;/a&gt; in order to prove you own the domain. This helps mitigates attacks by adversaries by claiming they own a domain such as &lt;code&gt;natwest.co.uk&lt;/code&gt; - allowing them to create phishing attacks and steal banking information.&lt;/p&gt;
&lt;p&gt;Since my network is only visible internally for the moment (i.e. the domain will only resolve to an IP address on my network) - I cannot use HTTP or TLS since these require the domain to resolve to a public IP address to a web server hosting a challenge file requested by LE. Therefore the only option I have is DNS challenge, where a randomly string generated by LE is placed into the &lt;a href=&#34;https://www.cloudflare.com/learning/dns/dns-records/dns-txt-record/&#34;&gt;TXT record&lt;/a&gt; of a DNS record to confirm ownership.&lt;/p&gt;
&lt;h2 id=&#34;building-our-caddy&#34;&gt;Building Our Caddy&lt;/h2&gt;
&lt;p&gt;For this exercise I&amp;rsquo;ll be using the latest version, Caddy 2, which allows for plugins to be built into the binary depending on your use case - including &lt;a href=&#34;https://caddyserver.com/docs/automatic-https#dns-challenge&#34;&gt;DNS challenge&lt;/a&gt;. This plugin isn&amp;rsquo;t included by default, so we&amp;rsquo;ll need to build our own Caddy binary. The tool to do this is called &lt;a href=&#34;https://github.com/caddyserver/xcaddy&#34;&gt;xcaddy&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;UPDATE 2020-09-30&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Looks like Caddy now comes with a &lt;a href=&#34;https://caddyserver.com/download&#34;&gt;nice web interface&lt;/a&gt; for downloading a Caddy binary with whatever plugins you desire. I just tested out the &lt;code&gt;Linux arm 7&lt;/code&gt; platform with just the &lt;code&gt;github.com/caddy-dns/cloudflare&lt;/code&gt; plugin, and it was able to run my Caddy configuration below perfectly!&lt;/p&gt;
&lt;p&gt;Once you&amp;rsquo;ve got the binary downloaded, copy it to the Pi then skip to &lt;a href=&#34;https://jdheyburn.co.uk/blog/reverse-proxy-multiple-domains-using-caddy-2/#caddy-configuration&#34;&gt;Caddy Configuration&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To build using xcaddy, you need to make sure you have &lt;a href=&#34;https://golang.org/doc/install&#34;&gt;Go installed&lt;/a&gt; on your machine.&lt;/p&gt;
&lt;p&gt;Note that I am building Caddy on my laptop, but running it on a Pi, so I will have to specify the architecture that Pi is running on so that Go can correctly build it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# Download xcaddy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;go get -u github.com/caddyserver/xcaddy/cmd/xcaddy
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# Build custom Caddy binary for Raspberry Pi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;GOOS&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;linux &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;GOARCH&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;arm &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;GOARM&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#bd93f9&#34;&gt;7&lt;/span&gt; xcaddy build --with github.com/caddy-dns/cloudflare
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# Copy the new binary across to the Pi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;scp caddy pi:/home/pi/caddy/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;caddy-configuration&#34;&gt;Caddy Configuration&lt;/h2&gt;
&lt;p&gt;The configuration I&amp;rsquo;m using can be seen below. Some things to note:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I&amp;rsquo;m using Cloudflare as the DNS name servers for the domain, even though I purchased my domain from namecheap
&lt;ul&gt;
&lt;li&gt;This repeats an &lt;a href=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-2-custom-domain/#adding-our-cdn-layer&#34;&gt;exercise I&amp;rsquo;ve done previously&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;I&amp;rsquo;ve done this for two reasons:
&lt;ul&gt;
&lt;li&gt;Caddy at the time of writing does not have a namecheap DNS challenge plugin&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s a proven method I know already&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;CLOUDFLARE_API_TOKEN&lt;/code&gt; is required to have Caddy set the TXT record DNS challenge received from LE
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://support.cloudflare.com/hc/en-us/articles/200167836-Managing-API-Tokens-and-Keys&#34;&gt;Guide for the same&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Caddy is reverse proxying traffic to services running locally on the Pi&lt;/li&gt;
&lt;li&gt;Caddy is not verifying the certificate being hosted by the UniFi Controller (&lt;code&gt;insecure_skip_verify = true&lt;/code&gt;)
&lt;ul&gt;
&lt;li&gt;The controller self-signs a certificate, and the reverse proxy has no means of establishing a chain of trust to verify the certificate&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s not a best practice to not verify the chain of trust, however I&amp;rsquo;m happy to accept the risk for now&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&#34;https://caddyserver.com/docs/json/&#34;&gt;Click here&lt;/a&gt; to see documentation on Caddy JSON config files.&lt;/p&gt;
&lt;script src=&#34;https://gist.github.com/jdheyburn/ca668a9d162535ab92db2cfa6f4e4e54.js&#34;&gt;&lt;/script&gt;

&lt;h2 id=&#34;updating-dns-records&#34;&gt;Updating DNS Records&lt;/h2&gt;
&lt;p&gt;Remember that the domain names aren&amp;rsquo;t actually publicly accessible. At a basic level we can update the &lt;code&gt;/etc/hosts&lt;/code&gt; file of the machine we&amp;rsquo;re running on to add a record telling our machine how to resolve the domain.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo sh -c &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;echo \&amp;#34;192.168.1.10 pihole.joannet.casa\n192.168.1.10 unifi.joannet.casa\&amp;#34; &amp;gt;&amp;gt; /etc/hosts&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;However, we&amp;rsquo;re already using PiHole as our own DNS server right? We can add the records there instead.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;pihole-dns-records.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/reverse-proxy-multiple-domains-using-caddy-2//blog/reverse-proxy-multiple-domains-using-caddy-2/pihole-dns-records.png&#34;
    alt=&#34;Adding domain records to DNS server&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;PiHole let&amp;rsquo;s you specify where local domain names should resolve to&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The IP addresses you see above are pointing to the host running Caddy, the Raspberry Pi.&lt;/p&gt;
&lt;h2 id=&#34;verifying-caddy&#34;&gt;Verifying Caddy&lt;/h2&gt;
&lt;p&gt;Once the config file is built, you can perform a test run to confirm everything is working by executing this command.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo ./caddy run --config config.json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;We need to execute using &lt;code&gt;sudo&lt;/code&gt; so that we can expose the service to restricted ports 80 and 443 (HTTP and HTTPS respectively).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;proxied-pihole.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/reverse-proxy-multiple-domains-using-caddy-2//blog/reverse-proxy-multiple-domains-using-caddy-2/proxied-pihole.png&#34;
    alt=&#34;PiHole appearing in browser through a domain name&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;proxied-unifi.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/reverse-proxy-multiple-domains-using-caddy-2//blog/reverse-proxy-multiple-domains-using-caddy-2/proxied-unifi.png&#34;
    alt=&#34;UniFi Controller appearing in browser through a domain name&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Now we have a memorable domain name fronting the service, and Firefox is happy that we&amp;rsquo;re encrypting the connection too. The certificate being produced in seen below.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;pihole-certificate.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/reverse-proxy-multiple-domains-using-caddy-2//blog/reverse-proxy-multiple-domains-using-caddy-2/pihole-certificate.png&#34;
    alt=&#34;Certificate used by PiHole&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;enabling-caddy-service&#34;&gt;Enabling Caddy Service&lt;/h2&gt;
&lt;p&gt;Since we&amp;rsquo;re not using the standard Caddy installation method, we will need to specify a service unit file so that Caddy starts up at the same time as the host - which is what PiHole and UniFi are doing currently.&lt;/p&gt;
&lt;p&gt;First check to see if there is a stale service there already.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ls -la /etc/systemd/system/caddy.service
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;lrwxrwxrwx &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt; root root &lt;span style=&#34;color:#bd93f9&#34;&gt;9&lt;/span&gt; Jun  &lt;span style=&#34;color:#bd93f9&#34;&gt;4&lt;/span&gt; 09:14 /etc/systemd/system/caddy.service -&amp;gt; /dev/null
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you get the above then remove the symlink so that we can create a file there.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;rm /etc/systemd/system/caddy.service
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then populate the same file with the below, remembering the change the location of the Caddy config file to where it exists on your machine.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;[&lt;/span&gt;Unit&lt;span style=&#34;color:#ff79c6&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;Description&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;Caddy Reverse Proxy
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;Wants&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;network-online.target
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;After&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;network.target network-online.target
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;[&lt;/span&gt;Service&lt;span style=&#34;color:#ff79c6&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;ExecStart&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;/usr/local/bin/caddy run --config /home/jdheyburn/homelab/caddy/config.json
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;Restart&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;on-abort
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;[&lt;/span&gt;Install&lt;span style=&#34;color:#ff79c6&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;WantedBy&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;multi-user.target
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finalise the new service with the two commands, enabling it on host startup and starting the service right now.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo systemctl &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;enable&lt;/span&gt; caddy.service
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo systemctl start caddy.service
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;For now I have all the above running bare-metal on one Pi instance, which produces a huge single point of failure in my network. In the future I&amp;rsquo;d like to see how converting these to Docker containers and having them distributed on multiple Pis would increase the resiliency of these services.&lt;/p&gt;
&lt;p&gt;Until then, these basic but essential services are being hosted at easy to remember domains, transported over an encrypted connection,  for me to easily administer the network for when it gets more complex over time.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Who Goes Blogging 6: Three Steps to Improve Hugo&#39;s RSS Feeds</title><enclosure url="https://jdheyburn.co.uk/blog/who-goes-blogging-6-three-steps-to-improve-hugos-rss-feeds/cover.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/blog/who-goes-blogging-6-three-steps-to-improve-hugos-rss-feeds/</link>
      <pubDate>Thu, 14 May 2020 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/who-goes-blogging-6-three-steps-to-improve-hugos-rss-feeds/</guid>-->
      <description>&lt;p&gt;I &amp;ldquo;fixed&amp;rdquo; the default RSS template used by Hugo to show the full article content, along with images, and also talk about how to have social media cards appear in the RSS items too.&lt;/p&gt;
&lt;p&gt;I uploaded my completed RSS file to &lt;a href=&#34;https://gist.github.com/jdheyburn/a0a2c678f8f9795088b2779ec6af9920&#34;&gt;GitHub Gist&lt;/a&gt; &amp;#x1f680;&lt;/p&gt;
&lt;h2 id=&#34;what-is-rss&#34;&gt;What is RSS?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://en.wikipedia.org/wiki/RSS&#34;&gt;RSS&lt;/a&gt; is a great way to &amp;ldquo;subscribe&amp;rdquo; to websites to ensure that you don&amp;rsquo;t miss content from them. The standard for how it is generated has been the same for decades now - however its still the best supported and most accepted way to receive updates. It is simply a standardised XML file that allows consumers such as RSS aggregators to parse the content of the post - for which there are many to choose from (RIP &lt;a href=&#34;https://en.wikipedia.org/wiki/Google_Reader&#34;&gt;Google Reader&lt;/a&gt;).&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;inoreader-complete.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-6-three-steps-to-improve-hugos-rss-feeds//blog/who-goes-blogging-6-three-steps-to-improve-hugos-rss-feeds/inoreader-complete.png&#34;
    alt=&#34;inoreader as an RSS feed aggregator&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;My RSS aggregator of choice currently is &lt;a href=&#34;https://www.inoreader.com/&#34;&gt;inoreader&lt;/a&gt; - I&amp;rsquo;ll be using this in my examples&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Hugo &lt;a href=&#34;https://gohugo.io/templates/rss/&#34;&gt;generates RSS XML&lt;/a&gt; files from a template that will loop over your content and expose this at an endpoint for RSS aggregators to subscribe to and periodically check for updates against. Hugo generates a bunch of RSS XML files at different sections of the website, allowing consumers to subscribe to the section that interests them most - you can even subscribe to tags if that XML is being generated!&lt;/p&gt;
&lt;p&gt;Usually there is site top-level available at &lt;code&gt;/index.xml&lt;/code&gt; - in the case of my site that would be &lt;a href=&#34;https://jdheyburn.co.uk/index.xml&#34;&gt;https://jdheyburn.co.uk/index.xml&lt;/a&gt;. The source code for the template file Hugo uses to generate this is embedded in Hugo at &lt;a href=&#34;https://github.com/gohugoio/hugo/blob/master/tpl/tplimpl/embedded/templates/_default/rss.xml&#34;&gt;this location&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;the-problem&#34;&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Some (but not all) websites only include the first paragraph of their post in an RSS update (aka summary), along with a message after that paragraph requesting the reader to visit the site in a browser to view the rest of the content.&lt;/p&gt;
&lt;p&gt;This is a common feature of WordPress blogs - and it is done to have you load the full website and along with it, all the code for providing the owner with user analytics, and advertising - if they have it. So really they&amp;rsquo;re getting the benefit of being able to publish updates via a standardised approach (RSS), to then lure you onto the website.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;rss-post-show-more-content.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-6-three-steps-to-improve-hugos-rss-feeds//blog/who-goes-blogging-6-three-steps-to-improve-hugos-rss-feeds/rss-post-show-more-content.png&#34;
    alt=&#34;An RSS post loading incompletely&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Hugo &lt;em&gt;kind of&lt;/em&gt; does this through the highlighted line in the &lt;a href=&#34;https://github.com/gohugoio/hugo/blob/master/tpl/tplimpl/embedded/templates/_default/rss.xml&#34;&gt;templated RSS XML file&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;description&amp;gt;&lt;/span&gt;{{ .Summary | html }}&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;/description&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;.Summary&lt;/code&gt; will do what was just described, print out the first paragraph of your post. However unlike WordPress, it doesn&amp;rsquo;t actually say if there is more content. So users may not load your full website, instead thinking you&amp;rsquo;ve produced a very short article!&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;hugo-incomplete-post.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-6-three-steps-to-improve-hugos-rss-feeds//blog/who-goes-blogging-6-three-steps-to-improve-hugos-rss-feeds/hugo-incomplete-post.png&#34;
    alt=&#34;Hugo producing a summarised RSS post&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;I&amp;rsquo;m not against the practice of previewing the post, if a blog provides revenue for the author(s) then you will need to force readers to view the full  website for analytics and advertising (businesses gotta make money!).&lt;/p&gt;
&lt;p&gt;Although I&amp;rsquo;m sure the initial purpose of RSS was only meant to be used to provide subscribers a pure text version of your content. At the end of the day, I believe the author should give the reader choice on mediums of consumption - and they accept the limitations that come from RSS.&lt;/p&gt;
&lt;p&gt;So now, I&amp;rsquo;ll be making several changes to the Hugo template RSS XML file to produce the full site content, along with some other enhancements.&lt;/p&gt;
&lt;h2 id=&#34;rendering-full-site-content&#34;&gt;Rendering Full Site Content&lt;/h2&gt;
&lt;p&gt;Before we start editing files, we need to produce our own copy of the template RSS XML file. This is embedded directly within Hugo itself and not packaged in with your theme.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There&amp;rsquo;s every chance your theme is generating one for you. Run the below command to see if there is anything for you to copy from.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;find themes/ -type f -name &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;*.xml&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;We&amp;rsquo;ve discussed template lookup orders before in &lt;a href=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore/#template-lookup-order-primer&#34;&gt;previous posts&lt;/a&gt; - we can overwrite the default of any file used in generating the website by placing it in your local project root.&lt;/p&gt;
&lt;p&gt;We can start to make modifications to the &lt;code&gt;rss.xml&lt;/code&gt; file by copying the &lt;a href=&#34;https://github.com/gohugoio/hugo/blob/master/tpl/tplimpl/embedded/templates/_default/rss.xml&#34;&gt;default&lt;/a&gt; into the directory where Hugo will read it from.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# Assuming you are in project root&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;wget https://raw.githubusercontent.com/gohugoio/hugo/master/tpl/tplimpl/embedded/templates/_default/rss.xml -O layouts/_default/rss.xml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We know that line 35 containing &lt;code&gt;.Summary&lt;/code&gt; is causing the issue here. All we need to do is change it to &lt;code&gt;.Content&lt;/code&gt; - then Hugo will print out the entire content of your post. See the highlighted line below for the change.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;29
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;30
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;31
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;32
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;33
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;34
&lt;/span&gt;&lt;span style=&#34;background-color:#3d3f4a&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;35
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;36
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;37
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;38
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;39
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;item&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;{{ .Title }}&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;link&amp;gt;&lt;/span&gt;{{ .Permalink }}&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;/link&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;pubDate&amp;gt;&lt;/span&gt;{{ .Date.Format &amp;#34;Mon, 02 Jan 2006 15:04:05 -0700&amp;#34; | safeHTML }}&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;/pubDate&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      {{ with .Site.Author.email }}&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;author&amp;gt;&lt;/span&gt;{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;/author&amp;gt;&lt;/span&gt;{{end}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;guid&amp;gt;&lt;/span&gt;{{ .Permalink }}&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;/guid&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;description&amp;gt;&lt;/span&gt;{{ .Content | html }}&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;/description&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;/item&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {{ end }}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;/channel&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;/rss&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s now view the changes in the RSS reader.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;hugo-complete-post.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-6-three-steps-to-improve-hugos-rss-feeds//blog/who-goes-blogging-6-three-steps-to-improve-hugos-rss-feeds/hugo-complete-post.png&#34;
    alt=&#34;Hugo RSS post displaying all content&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;We can now see much more content is being produced - and with proper HTML this time!&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;UPDATE 2020-12-14&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I came across an issue whereby code indentation wasn&amp;rsquo;t rendering properly in RSS feeds if you used the &lt;code&gt;--minify&lt;/code&gt; parameter - &lt;a href=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-7-hugo-minify-rss-code-indentation-fix/&#34;&gt;this post&lt;/a&gt; resolves it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;rendering-images-in-rss-posts&#34;&gt;Rendering images in RSS posts&lt;/h2&gt;
&lt;p&gt;Depending on how you are retrieving images - you may find that they are not displaying, and that the image alterative (&lt;code&gt;alt&lt;/code&gt;) text is being displayed instead.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;hugo-missing-images-post.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-6-three-steps-to-improve-hugos-rss-feeds//blog/who-goes-blogging-6-three-steps-to-improve-hugos-rss-feeds/hugo-missing-images-post.png&#34;
    alt=&#34;Hugo RSS post with missing images, alt text displayed instead&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Note the highlighted alt texts of images&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;blockquote&gt;
&lt;p&gt;If you&amp;rsquo;re not already providing an &lt;code&gt;alt&lt;/code&gt; field to your &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; blocks then &lt;strong&gt;you absolutely need to&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;They help boost accessibility to your website should the user have a text-to-voice application reading your website.&lt;/p&gt;
&lt;p&gt;They also provide a helpful description to your image in case it cannot load - which happens more often than you think!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;why-it-is-breaking-triumph&#34;&gt;Why it is breaking &amp;#x1f624;&lt;/h3&gt;
&lt;p&gt;In &lt;a href=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-4-content-structure-and-refactoring/&#34;&gt;part 4&lt;/a&gt; of this series, we looked at placing resources for a post in the same directory as the content markdown file (also known as &lt;a href=&#34;https://gohugo.io/content-management/page-bundles/#leaf-bundles&#34;&gt;leaf bundles&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;That means for a given directory tree structure:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ tree content/blog
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;content/blog
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;└── some-blog-post
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ├── image-file-name.png
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    └── index.md
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We would use the following shortcode to generate the &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; block in &lt;code&gt;index.md&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{&amp;lt; figure src=&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;image-file-name.png&amp;#34;&lt;/span&gt; alt=&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Here is an image!&amp;#34;&lt;/span&gt; &amp;gt;}}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This produces the block:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#ff79c6&#34;&gt;figure&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;&lt;span style=&#34;color:#ff79c6&#34;&gt;img&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;src&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;image-file-name.png&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;alt&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Here is an image!&amp;#34;&lt;/span&gt;&amp;gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#ff79c6&#34;&gt;figure&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Since this contains no preceding forward-slash &lt;code&gt;/&lt;/code&gt; in &lt;code&gt;src&lt;/code&gt;, this becomes a &lt;em&gt;relative path&lt;/em&gt;. This means the webserver will look for the image at the path relative to the current page.&lt;/p&gt;
&lt;p&gt;So when we navigate to &lt;code&gt;blog/some-blog-post/&lt;/code&gt; in our web browser (as dictated from the above tree structure), your browser will request the image at &lt;code&gt;blog/some-blog-post/image-file-name.png&lt;/code&gt; for you.&lt;/p&gt;
&lt;p&gt;Why is this relevant? This same &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; block is being rendered in the description of your RSS XML file, including the relative image location. RSS is very simple and renders only complete URLs (e.g. &lt;code&gt;https://jdheyburn.co.uk/blog/some-blog-post/image-file-name.png&lt;/code&gt;) - so when we just specify the relative path, it cannot find the image and thus prints out the &lt;code&gt;alt&lt;/code&gt; text.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; if you specify images from a complete path such as &lt;code&gt;/images/image-file-name.png&lt;/code&gt; under &lt;code&gt;static/images&lt;/code&gt;, then this is not an issue for you.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;fix-image-rendering&#34;&gt;Fix image rendering&lt;/h3&gt;
&lt;p&gt;The fix for this involves some Go magic. We need to prepend the permalink for the current post that&amp;rsquo;s being printed out to anything that contains a relative path &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; block.&lt;/p&gt;
&lt;p&gt;We need to go back to the same line where we added &lt;code&gt;.Content&lt;/code&gt; and change it to:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;description&amp;gt;&lt;/span&gt;{{ replaceRE &amp;#34;img src=\&amp;#34;(.*?)\&amp;#34;&amp;#34; (printf &amp;#34;%s%s%s&amp;#34; &amp;#34;img src=\&amp;#34;&amp;#34; .Permalink &amp;#34;$1\&amp;#34;&amp;#34;) .Content | html }}&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;/description&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This calls the Hugo function &lt;a href=&#34;https://gohugo.io/functions/replacere/&#34;&gt;replaceRE&lt;/a&gt; which enables us to perform a find-and-replace using &lt;a href=&#34;https://www.regular-expressions.info/tutorial.html&#34;&gt;regular expressions (regex)&lt;/a&gt;, and it takes three inputs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;PATTERN&lt;/code&gt; is the regex pattern used to find the text we want to remove
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;img src=\&amp;quot;(.*?)\&amp;quot;&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;REPLACEMENT&lt;/code&gt; is the text that we want to replace the &lt;code&gt;PATTERN&lt;/code&gt; with
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;(printf &amp;quot;%s%s%s&amp;quot; &amp;quot;img src=\&amp;quot;&amp;quot; .Permalink &amp;quot;$1\&amp;quot;&amp;quot;)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;INPUT&lt;/code&gt; is the string we want to perform the find-and-replace on
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.Content&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the example above, we&amp;rsquo;re placing a &lt;a href=&#34;https://www.regular-expressions.info/brackets.html&#34;&gt;capturing group&lt;/a&gt; in the &lt;code&gt;PATTERN&lt;/code&gt; so that it will capture the contents inside &lt;code&gt;src&lt;/code&gt; and save them so that we can refer to it in the &lt;code&gt;REPLACEMENT&lt;/code&gt;, while removing &lt;code&gt;img src=&amp;quot;&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;REPLACEMENT&lt;/code&gt; is calling another Hugo function called &lt;a href=&#34;https://gohugo.io/functions/printf/&#34;&gt;printf&lt;/a&gt; to construct the replacement text. We&amp;rsquo;re pretty much just injecting the page &lt;code&gt;.Permalink&lt;/code&gt; prior to the captured text from the &lt;code&gt;PATTERN&lt;/code&gt; - so that RSS can then display the complete URL. While adding back the &lt;code&gt;img src=&amp;quot;&amp;quot;&lt;/code&gt; that was removed.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;INPUT&lt;/code&gt; will be the page &lt;code&gt;.Content&lt;/code&gt;, what we were printing out before.&lt;/p&gt;
&lt;p&gt;Once we&amp;rsquo;ve made the change, images are now rendered properly!&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;hugo-images-post.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-6-three-steps-to-improve-hugos-rss-feeds//blog/who-goes-blogging-6-three-steps-to-improve-hugos-rss-feeds/hugo-images-post.png&#34;
    alt=&#34;Hugo RSS post with images being correctly rendered&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;UPDATE 2020-05-16&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Upon posting this, I noticed I needed to do the same thing for hyperlinks referencing headings with the same post. For instance the markdown below which renders a hyperlink to a heading&amp;hellip;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[&lt;span style=&#34;color:#ff79c6&#34;&gt;images to display in RSS content&lt;/span&gt;](&lt;span style=&#34;color:#50fa7b&#34;&gt;#rendering-images-in-rss-posts&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Will render this in RSS:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#ff79c6&#34;&gt;a&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;href&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;https://jdheyburn.co.uk/#rendering-images-in-rss-posts&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Which does not take the reader to the right location at all. What we need to do is repeat the regex used above in &lt;code&gt;rss.xml&lt;/code&gt; to catch these and replace them with the complete URL. See below for the snippet.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-golang&#34; data-lang=&#34;golang&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{&lt;span style=&#34;color:#ff79c6&#34;&gt;-&lt;/span&gt; $content &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; replaceRE &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;a href=\&amp;#34;(#.*?)\&amp;#34;&amp;#34;&lt;/span&gt; (printf &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;%s%s%s&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;a href=\&amp;#34;&amp;#34;&lt;/span&gt; .Permalink &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;$1\&amp;#34;&amp;#34;&lt;/span&gt;) .Content &lt;span style=&#34;color:#ff79c6&#34;&gt;-&lt;/span&gt;}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{&lt;span style=&#34;color:#ff79c6&#34;&gt;-&lt;/span&gt; $content = replaceRE &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;img src=\&amp;#34;(.*?)\&amp;#34;&amp;#34;&lt;/span&gt; (printf &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;%s%s%s&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;img src=\&amp;#34;&amp;#34;&lt;/span&gt; .Permalink &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;$1\&amp;#34;&amp;#34;&lt;/span&gt;) $content &lt;span style=&#34;color:#ff79c6&#34;&gt;-&lt;/span&gt;}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;description&amp;gt;{{ $content | html }}&amp;lt;&lt;span style=&#34;color:#ff79c6&#34;&gt;/&lt;/span&gt;description&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;adding-cards-for-posts&#34;&gt;Adding cards for posts&lt;/h2&gt;
&lt;p&gt;Not dissimilar to &lt;a href=&#34;https://barkersocial.com/social-cards/&#34;&gt;social media cards&lt;/a&gt;, you can also have cards appear for your RSS posts in order to make them more attractive for readers to click on!&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;inoreader-enclosure-card-view.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-6-three-steps-to-improve-hugos-rss-feeds//blog/who-goes-blogging-6-three-steps-to-improve-hugos-rss-feeds/inoreader-enclosure-card-view.png&#34;
    alt=&#34;inoreader displaying images for each post when in card view&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;While in card view for inoreader, we can see featured images being displayed&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;You can do these in RSS via the &lt;a href=&#34;https://www.w3schools.com/xml/rss_tag_enclosure.asp&#34;&gt;enclosure&lt;/a&gt; tag&amp;hellip;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#ff79c6&#34;&gt;enclosure&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;url&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;image-location.png&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;image/jpg&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#ff79c6&#34;&gt;enclosure&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you have a look at our &lt;code&gt;layouts/_default/rss.xml&lt;/code&gt; file, we don&amp;rsquo;t have this defined. We could go and add it in now, but I would like a unified social media image to be used across all platforms. This will enable me to focus my effort on generating a single social media card image that is shared across all methods of consumption.&lt;/p&gt;
&lt;p&gt;Based on the above, my theme (hugo-coder), renders the tags required for social media cards through the &lt;a href=&#34;https://github.com/gohugoio/hugo/blob/master/tpl/tplimpl/embedded/templates/twitter_cards.html&#34;&gt;internal template&lt;/a&gt; for &lt;a href=&#34;https://gohugo.io/templates/internal/#use-the-twitter-cards-template&#34;&gt;twitter_cards&lt;/a&gt;. A snippet of the logic that determines this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 3
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 4
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 5
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 6
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 7
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 8
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 9
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;10
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;11
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;12
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;13
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;14
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;15
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{- with $.Params.images -}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#ff79c6&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;twitter:card&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;content&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;summary_large_image&amp;#34;&lt;/span&gt;/&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#ff79c6&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;twitter:image&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;content&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ index . 0 | absURL }}&amp;#34;&lt;/span&gt;/&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ else -}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{- $images := $.Resources.ByType &amp;#34;image&amp;#34; -}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{- $featured := $images.GetMatch &amp;#34;*feature*&amp;#34; -}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{- if not $featured }}{{ $featured = $images.GetMatch &amp;#34;{*cover*,*thumbnail*}&amp;#34; }}{{ end -}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{- with $featured -}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#ff79c6&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;twitter:card&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;content&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;summary_large_image&amp;#34;&lt;/span&gt;/&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#ff79c6&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;twitter:image&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;content&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ $featured.Permalink }}&amp;#34;&lt;/span&gt;/&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{- else -}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{- with $.Site.Params.images -}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#ff79c6&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;twitter:card&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;content&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;summary_large_image&amp;#34;&lt;/span&gt;/&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#ff79c6&#34;&gt;meta&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;twitter:image&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;content&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ index . 0 | absURL }}&amp;#34;&lt;/span&gt;/&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ else -}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The hierarchy of images used to render the social media card goes like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The first entry in the list of &lt;code&gt;images&lt;/code&gt; found in the post front matter&lt;/li&gt;
&lt;li&gt;Any image in the post leaf bundle containing &lt;code&gt;feature&lt;/code&gt; in its name&lt;/li&gt;
&lt;li&gt;Any image in the post leaf bundle containing either &lt;code&gt;cover&lt;/code&gt; or &lt;code&gt;thumbnail&lt;/code&gt; in its name&lt;/li&gt;
&lt;li&gt;The default image defined at the site level params&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;adding-enclosure-tags&#34;&gt;Adding enclosure tags&lt;/h3&gt;
&lt;p&gt;We can effectively copy and paste the code being used for generating Twitter cards and modify it for enclosure tags. The snippet of code ultimately ends up looking like this  &amp;#x1f447;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 3
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 4
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 5
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 6
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 7
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 8
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 9
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;10
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;11
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;12
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;13
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;14
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;15
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{- $pagePermalink := .Permalink -}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{- with .Params.images -}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{- $img := index . 0 -}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#ff79c6&#34;&gt;enclosure&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;url&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ printf &amp;#34;&lt;/span&gt;%&lt;span style=&#34;color:#50fa7b&#34;&gt;s&lt;/span&gt;%&lt;span style=&#34;color:#50fa7b&#34;&gt;s&lt;/span&gt;&amp;#34; $&lt;span style=&#34;color:#50fa7b&#34;&gt;pagePermalink&lt;/span&gt; $&lt;span style=&#34;color:#50fa7b&#34;&gt;img&lt;/span&gt; }}&amp;#34; &lt;span style=&#34;color:#50fa7b&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;image/jpg&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#ff79c6&#34;&gt;enclosure&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ else -}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{- $images := .Resources.ByType &amp;#34;image&amp;#34; -}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{- $featured := $images.GetMatch &amp;#34;*feature*&amp;#34; -}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{- if not $featured }}{{ $featured = $images.GetMatch &amp;#34;{*cover*,*thumbnail*}&amp;#34; }}{{ end -}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{- with $featured -}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#ff79c6&#34;&gt;enclosure&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;url&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ $featured.Permalink }}&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;image/jpg&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#ff79c6&#34;&gt;enclosure&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{- else -}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{- with .Site.Params.images -}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#ff79c6&#34;&gt;enclosure&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;url&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ index . 0 | absURL }}&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;image/jpg&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#ff79c6&#34;&gt;enclosure&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{- end -}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{- end -}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{- end }}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Walking through what this is doing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lines 1-4 will use the image defined in the &lt;code&gt;images&lt;/code&gt; in the post front matter, if it exists
&lt;ul&gt;
&lt;li&gt;I had to define &lt;code&gt;{{-/* $pagePermalink := .Permalink */-}}&lt;/code&gt; on line 1 to make &lt;code&gt;.Permalink&lt;/code&gt; available inside the &lt;code&gt;with&lt;/code&gt; block at line 4&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Line 7 will find an image containing &lt;code&gt;feature&lt;/code&gt; in its name, at the post leaf bundle section, if an image is not found previously&lt;/li&gt;
&lt;li&gt;Line 8 will revert to an image containing either &lt;code&gt;cover&lt;/code&gt; or &lt;code&gt;thumbnail&lt;/code&gt;, if an image is not found previously&lt;/li&gt;
&lt;li&gt;Lines 12-13 will default to the image defined at the site level params&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With lessons learnt trying to get &lt;a href=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-6-three-steps-to-improve-hugos-rss-feeds/#rendering-images-in-rss-posts&#34;&gt;images to display in RSS content&lt;/a&gt;, I am rendering the full URL of the image to ensure RSS is always able to retrieve it.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m going to follow lines 12-13 above and have a default card defined at the site level parameters. In my &lt;code&gt;config.toml&lt;/code&gt; I&amp;rsquo;ll need to add the following&amp;hellip;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[params]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    images = [&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;images/jdheyburn_co_uk_card.png&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&amp;hellip;while also ensuring that I&amp;rsquo;ve placed the image under &lt;code&gt;static/images/jdheyburn_co_uk_card.png&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Once all that is done and deployed - we will have an RSS post card that looks like this  &amp;#x1f64c;&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;hugo-enclosure-card-view.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-6-three-steps-to-improve-hugos-rss-feeds//blog/who-goes-blogging-6-three-steps-to-improve-hugos-rss-feeds/hugo-enclosure-card-view.png&#34;
    alt=&#34;inoreader displaying an image for a Hugo post&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;We can now advertise our RSS subscription URL to readers. My theme &lt;code&gt;hugo-coder&lt;/code&gt; supports this as a social button on the home page. We just need to add the below block to the &lt;code&gt;config.toml&lt;/code&gt; file.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[[params.social]]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    name = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;RSS&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    icon = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;fas fa-rss&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    weight = &lt;span style=&#34;color:#bd93f9&#34;&gt;6&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    url = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;https://jdheyburn.co.uk/index.xml&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    rel = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;alternate&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    type = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;application/rss+xml&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once done - our homepage will have the button displayed.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;rss-subscription-button.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-6-three-steps-to-improve-hugos-rss-feeds//blog/who-goes-blogging-6-three-steps-to-improve-hugos-rss-feeds/rss-subscription-button.png&#34;
    alt=&#34;RSS subscription button displayed on the homepage&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;summarising-changes-to-rss-xml&#34;&gt;Summarising changes to RSS XML&lt;/h2&gt;
&lt;p&gt;You can view the gist below to see my complete RSS XML file after all the above has been added. If you wish to use it, you&amp;rsquo;ll need to place it in &lt;code&gt;layouts/_default/rss.xml&lt;/code&gt; in your project directory.&lt;/p&gt;
&lt;script src=&#34;https://gist.github.com/jdheyburn/a0a2c678f8f9795088b2779ec6af9920.js&#34;&gt;&lt;/script&gt;

&lt;p&gt;&lt;a href=&#34;https://gist.github.com/jdheyburn/a0a2c678f8f9795088b2779ec6af9920/revisions&#34;&gt;Click here&lt;/a&gt; for a &lt;code&gt;git diff&lt;/code&gt; view on the changes I made from the default RSS XML file.&lt;/p&gt;
&lt;p&gt;Thanks for reading! &amp;#x1f4af;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Who Goes Blogging 5: Updates Galore!</title><enclosure url="https://jdheyburn.co.uk/images/jdheyburn_co_uk_card.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore/</link>
      <pubDate>Sun, 19 Apr 2020 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore/</guid>-->
      <description>&lt;p&gt;In this article we&amp;rsquo;re going to continue making some improvements to our Hugo website. Some of the things going to be covered are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Upgrading our Hugo theme&lt;/li&gt;
&lt;li&gt;Upgrading the Hugo version&lt;/li&gt;
&lt;li&gt;Adding table of contents&lt;/li&gt;
&lt;li&gt;Adding support for title emojis&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So far since this website has launched we haven&amp;rsquo;t upgraded either the Hugo version used to build our website, alongside the theme being used. Hugo is a very active project with new features and bug fixes being added all the time. By not upgrading Hugo, we are missing out on enhancements that we can make to the site.&lt;/p&gt;
&lt;p&gt;As for upgrading the theme, improvements in the styling or even making use of said new features in Hugo can be added as part of the theme. Remember that when you execute a &lt;code&gt;hugo&lt;/code&gt; command, turning the website from markdown to rendered HTML files, that the theme chosen will be used to render those files.&lt;/p&gt;
&lt;h2 id=&#34;upgrading-hugo&#34;&gt;Upgrading Hugo&lt;/h2&gt;
&lt;p&gt;This varies depending on what OS you are running on, but it is pretty much identical as the steps taken to install Hugo in the &lt;a href=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-1-getting-started/#blog-bootstrapping&#34;&gt;first place&lt;/a&gt;. Even the &lt;a href=&#34;https://gohugo.io/getting-started/installing/#upgrade-hugo&#34;&gt;documentation for upgrading&lt;/a&gt; is a one-liner:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Upgrading Hugo is as easy as downloading and replacing the executable you’ve placed in your &lt;code&gt;PATH&lt;/code&gt;, or running &lt;code&gt;brew upgrade hugo&lt;/code&gt; if using Homebrew on macOS.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A quick execute of &lt;code&gt;hugo version&lt;/code&gt; tells us what we&amp;rsquo;re currently running. We can then have a look at the &lt;a href=&#34;https://github.com/gohugoio/hugo/releases/&#34;&gt;Hugo release page&lt;/a&gt; to find the latest one - which at the time of writing is &lt;a href=&#34;https://github.com/gohugoio/hugo/releases/tag/v0.68.3&#34;&gt;0.68.3&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ hugo version
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hugo Static Site Generator v0.58.3-4AAC02D4/extended linux/amd64 BuildDate: 2019-09-19T15:34:54Z
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I run Linux so I would need to replace the executable on my &lt;code&gt;PATH&lt;/code&gt;. This can be done with a handy script to pull the archive and install it for us.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;HUGO_VERSION&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;0.68.3&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;wget &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;https://github.com/gohugoio/hugo/releases/download/v&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;HUGO_VERSION&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;/hugo_extended_&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;HUGO_VERSION&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;_Linux-64bit.tar.gz&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;tar -zxvf &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;hugo_extended_&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;HUGO_VERSION&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;_Linux-64bit.tar.gz&amp;#34;&lt;/span&gt; hugo
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo mv -v hugo /usr/local/bin
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;hugo version
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;rm &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;hugo_extended_&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;HUGO_VERSION&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;_Linux-64bit.tar.gz&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Remember that the hugo-coder theme requires the extended version of Hugo in order for it to render files correctly.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can see where &lt;code&gt;hugo&lt;/code&gt; is currently installed on your system with the command &lt;code&gt;which hugo&lt;/code&gt;, given it is in your &lt;code&gt;PATH&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Executing &lt;code&gt;hugo version&lt;/code&gt; one more time confirms if the upgrade took place.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ hugo version
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Hugo Static Site Generator v0.68.3-157669A0/extended linux/amd64 BuildDate: 2020-03-24T12:13:38Z
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;testing-for-any-issues&#34;&gt;Testing For Any Issues&lt;/h3&gt;
&lt;p&gt;At this point I would advise you do some testing with the new version to determine if your site is not only rendering as expected but functioning too. You can do this with the good old &lt;code&gt;hugo serve&lt;/code&gt; command as you would have used before when writing out articles, and fixing anything that looks out of place.&lt;/p&gt;
&lt;p&gt;However, since I&amp;rsquo;m going to be upgrading the hugo theme too, I&amp;rsquo;ll do my testing in one hit &lt;a href=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore/#validation-and-testing&#34;&gt;later in this post&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;updating-hugo-in-cicd&#34;&gt;Updating Hugo in CI/CD&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;ve only upgrading Hugo on our machines, but we also need to upgrade it in our CI/CD pipeline to ensure it deploys with the correct rendering too. We set this up in parts &lt;a href=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-3-1-deployment-methods-travisci/&#34;&gt;3.1&lt;/a&gt; and &lt;a href=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-3-2-deployment-methods-github-actions/&#34;&gt;3.2&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m using GitHub Actions for my CI/CD pipeline, so I need to head to &lt;code&gt;.github/workflows/deploy.yml&lt;/code&gt; and change &lt;code&gt;hugo-version: &amp;quot;0.58.3&amp;quot;&lt;/code&gt; to &lt;code&gt;hugo-version: &amp;quot;0.68.3&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s as easy as that thanks to the pre-made GitHub Action!&lt;/p&gt;
&lt;h2 id=&#34;upgrading-hugo-theme&#34;&gt;Upgrading Hugo Theme&lt;/h2&gt;
&lt;h3 id=&#34;a-git-submodules-recap&#34;&gt;A git submodules recap&lt;/h3&gt;
&lt;p&gt;As mentioned before, I&amp;rsquo;m using hugo-coder as the theme for my Hugo website which is being pulled as a submodule in my git repository. I can interface with it via the command &lt;code&gt;git submodules&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;One of the cool things about &lt;code&gt;git submodules&lt;/code&gt; is I don&amp;rsquo;t need to have a copy of the source code for the theme in my project. I can just reference the codebase at a particular point in time - or a commit, and git will pull in the resources for which then my Hugo website can utilise it.&lt;/p&gt;
&lt;p&gt;We already defined this back in the &lt;a href=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-1-getting-started/&#34;&gt;first post of this series&lt;/a&gt;, in our root directory we created a file called &lt;code&gt;.gitmodules&lt;/code&gt; with the following contents:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;[&lt;/span&gt;submodule &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;themes/hugo-coder&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;path&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; themes/hugo-coder
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;url&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; https://github.com/luizdepra/hugo-coder.git
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Line by line, we are giving the submodule a name, telling git where to check out the module to, and lastly where git can find the repository for the module when we checkout our project.&lt;/p&gt;
&lt;p&gt;When we pull in the submodule for the first time, it creates a tree in our projects &lt;code&gt;.git/modules&lt;/code&gt; filled with metadata about the submodule.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ tree .git/modules/themes/hugo-coder -L &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.git/modules/themes/hugo-coder
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── branches
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── config
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── description
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── HEAD
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── hooks
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── index
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── info
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── logs
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── objects
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── packed-refs
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;└── refs
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;The &lt;code&gt;.git/&lt;/code&gt; directory contains all the metadata about your repository so that &lt;code&gt;git&lt;/code&gt; knows where to pull/push to/from the repository, &lt;a href=&#34;https://githowto.com/git_internals_git_directory&#34;&gt;amongst other things&lt;/a&gt;. It has a near identical directory structure to the one shown above.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of the files in this directory is called &lt;code&gt;HEAD&lt;/code&gt;, and it contains the commit of which &lt;code&gt;git&lt;/code&gt; should checkout the submodule&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ cat .git/modules/themes/hugo-coder/HEAD
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;e66c1740611b15eee0069811d10b5a22b0dc4332
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can even view this commit in the repository at &lt;a href=&#34;https://github.com/luizdepra/hugo-coder/commit/e66c1740611b15eee0069811d10b5a22b0dc4332&#34;&gt;https://github.com/luizdepra/hugo-coder/commit/e66c1740611b15eee0069811d10b5a22b0dc4332&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;upgrading-submodules&#34;&gt;Upgrading Submodules&lt;/h3&gt;
&lt;p&gt;To upgrade submodules in your project, you simply need to execute the command below:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git submodule update --recursive --remote
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can then check the value in &lt;code&gt;.git/modules/themes/hugo-coder/HEAD&lt;/code&gt; and see what this is updated to.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ cat .git/modules/themes/hugo-coder/HEAD
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;acb4bf6f2dcce51a27dc0e1f1008afb369d378d8
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Nice - this has changed from the previous value. We can jump over to GitHub to verify this is indeed the latest version.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;hugo-coder-commit.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore//blog/who-goes-blogging-5-updates-galore/hugo-coder-commit.png&#34;
    alt=&#34;Screenshot showing latest hugo-coder version in GitHub&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Verify at the &lt;a href=&#34;https://github.com/luizdepra/hugo-coder/commit/master&#34;&gt;GitHub repo page&lt;/a&gt;&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;validation-and-testing&#34;&gt;Validation and Testing&lt;/h2&gt;
&lt;p&gt;Now comes the testing part, ensuring that everything is working as intended with both the new Hugo version and the latest hugo-coder submodule. Some of the issues I came across were:&lt;/p&gt;
&lt;h3 id=&#34;center-tags-not-rending-correctly&#34;&gt;&lt;code&gt;&amp;lt;center&amp;gt;&lt;/code&gt; tags not rending correctly&lt;/h3&gt;
&lt;p&gt;I believe the new Hugo version broke this one, where images that were wrapped in &lt;code&gt;&amp;lt;center&amp;gt;&lt;/code&gt; tags were simply disappearing. I had been wrapping &lt;code&gt;{{&amp;lt; figure ... &amp;gt;}}&lt;/code&gt; shortcodes in &lt;code&gt;&amp;lt;center&amp;gt;&lt;/code&gt; tags in order to horizontally centre images - but it looks like the new version causes these images to appear completely.&lt;/p&gt;
&lt;p&gt;It looks like the &lt;code&gt;figure&lt;/code&gt; shortcodes now support &lt;code&gt;center&lt;/code&gt; as a &lt;code&gt;class&lt;/code&gt; property. Therefore we go from&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;center&amp;gt;{{&amp;lt; figure src=&amp;quot;image-name.png&amp;quot; &amp;gt;}}&amp;lt;/center&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;To&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;{{&amp;lt; figure src=&amp;quot;image-name.png&amp;quot; class=&amp;quot;center&amp;quot; &amp;gt;}}&lt;/code&gt;&lt;/p&gt;
&lt;h3 id=&#34;lists-not-rendering-as-before&#34;&gt;Lists not rendering as before&lt;/h3&gt;
&lt;p&gt;It turns out that my markdown for articles wasn&amp;rsquo;t as watertight as I thought; Hugo is now a whole lot more strict in ensuring your syntax is correct. Check these two before and after for comparison.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore/old-render-list.png&#34; class=&#34;screenshotimg&#34;
         alt=&#34;Screenshot showing old, proper list render&#34;/&gt; 
&lt;/figure&gt;

&lt;figure&gt;
    &lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore/new-render-list.png&#34; class=&#34;screenshotimg&#34;
         alt=&#34;Screenshot showing new, incorrect list render&#34;/&gt; 
&lt;/figure&gt;

&lt;p&gt;For this I just needed to fix the markdown by adding another indent to the list item.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;git-diff-list.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore//blog/who-goes-blogging-5-updates-galore/git-diff-list.png&#34;
    alt=&#34;Screenshot showing git diff between old and new list&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;I saw several cases of this, and including a &lt;a href=&#34;https://github.com/igorshubovych/markdownlint-cli&#34;&gt;markdown linter&lt;/a&gt; in the CI/CD pipeline would help to prevent this. This is something I&amp;rsquo;ll look to include in the near future.&lt;/p&gt;
&lt;h2 id=&#34;adding-new-features&#34;&gt;Adding New Features&lt;/h2&gt;
&lt;p&gt;The whole reason to upgrade both Hugo and the theme was to explore some of the new features that come in both of them, so let&amp;rsquo;s get adding them.&lt;/p&gt;
&lt;h2 id=&#34;adding-series-links-in-content-footer&#34;&gt;Adding Series Links in Content Footer&lt;/h2&gt;
&lt;p&gt;The hugo-coder theme now has better support for series - these are articles that directly follow on from one another, such as this Who Goes Blogging one. Series are nothing new to Hugo and we could have done them with the version we upgraded from, only in &lt;a href=&#34;https://github.com/luizdepra/hugo-coder/commit/27e83b1e5a9d8b7bbd42d202bd5ef57adcce659b&#34;&gt;this commit for hugo-coder&lt;/a&gt; has it added a layout for it to be rendered.&lt;/p&gt;
&lt;p&gt;To create a series, we use the &lt;code&gt;series&lt;/code&gt; front matter setting for articles. For the Who Goes Blogging articles, adding the front matter to them looks like this.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;date&lt;/span&gt;: 2020-03-10
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;title&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Who Goes Blogging 3.2: Deployment Methods - GitHub Actions&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;#...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;series&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;  - Who Goes Blogging
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;tags&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;#...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once this is done for all pages, hugo-coder will render the &lt;a href=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore/#conclusion&#34;&gt;&lt;strong&gt;See also in &amp;hellip;&lt;/strong&gt;&lt;/a&gt; section at the article footer.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore/series-footer-section.png&#34; class=&#34;screenshotimg&#34;
         alt=&#34;Screenshot showing series rendered in the page footer&#34;/&gt; 
&lt;/figure&gt;

&lt;h2 id=&#34;adding-table-of-contents&#34;&gt;Adding Table of Contents&lt;/h2&gt;
&lt;p&gt;Okay this one isn&amp;rsquo;t exactly a new feature of the theme, but since we&amp;rsquo;re playing around we may as well make use of this opportunity. Table of contents (TOC) aren&amp;rsquo;t included by default in the &lt;code&gt;hugo-coder&lt;/code&gt; theme, so we need to add it in ourselves.&lt;/p&gt;
&lt;p&gt;There is some &lt;a href=&#34;https://gohugo.io/content-management/toc/&#34;&gt;documentation&lt;/a&gt; on how to add TOC to your articles. Before we do that we need to revisit how layouts are organised as discussed briefly in &lt;a href=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-4-content-structure-and-refactoring/#fixing-article-rendering&#34;&gt;a previous post&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;template-lookup-order-primer&#34;&gt;Template Lookup Order Primer&lt;/h3&gt;
&lt;p&gt;The type of a markdown file (defined by the front matter setting &lt;code&gt;type&lt;/code&gt;) tells Hugo where to look for in the &lt;code&gt;layouts/&lt;/code&gt; directory of your project. The Hugo theme you&amp;rsquo;re using will already have these all defined, hence why they are predefined themes! Before Hugo looks for these layout HTML files in your theme, it will check for them in your projects &lt;code&gt;layouts/&lt;/code&gt; directory first.&lt;/p&gt;
&lt;p&gt;In other words, the order of precedence (or hierarchy) for HTML files is anything in the project &lt;code&gt;layouts/&lt;/code&gt; directory takes a higher priority than that in your theme - as described in Hugo&amp;rsquo;s &lt;a href=&#34;https://gohugo.io/templates/lookup-order/&#34;&gt;documentation for template lookup order&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See below for a directory tree explaning this - a lot of content has been removed for brevity.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ tree layouts
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;layouts
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── posts
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   └── single.html					&lt;span style=&#34;color:#6272a4&#34;&gt;# hugo will use this file to render your HTML&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;└── themes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    └── hugo-coder
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	    └── layouts
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			└── posts
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			    └── single.html     &lt;span style=&#34;color:#6272a4&#34;&gt;# hugo will ignore this one in the themes directory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;implementation&#34;&gt;Implementation&lt;/h3&gt;
&lt;p&gt;The primer above gives a hint as to what file we need to modify. The &lt;a href=&#34;https://github.com/luizdepra/hugo-coder/blob/master/layouts/posts/single.html&#34;&gt;&lt;code&gt;single.html&lt;/code&gt;&lt;/a&gt; file is in charge of how articles are rendered. Since we want everything else to remain the same about this file, we can take a copy of the file as it currently stands and place it into our project root.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mkdir layouts/posts
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cp -v themes/hugo-coder/layouts/posts/single.html layouts/posts
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;I won&amp;rsquo;t go into the details here, I&amp;rsquo;ll leave that to you to make changes to see how they affect the rendering of the page.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I want the TOC to appear before the main content, but after the featured image for the article (if there is any). Based on this the files is going to need the highlighted change below.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;29
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;30
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;31
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;32
&lt;/span&gt;&lt;span style=&#34;background-color:#3d3f4a&#34;&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;33
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;34
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;35
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#ff79c6&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {{ if .Params.featured_image }}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#ff79c6&#34;&gt;img&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;src&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;{{ .Params.featured_image }}&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;alt&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Featured image&amp;#34;&lt;/span&gt; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {{ end }}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;  {{ partial &amp;#34;toc.html&amp;#34; . }}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {{ .Content }}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#ff79c6&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;We&amp;rsquo;re not done there - we&amp;rsquo;ve referenced a &lt;a href=&#34;https://gohugo.io/templates/partials/&#34;&gt;partial template file&lt;/a&gt; here. These allow us to reuse HTML files in multiple places, or to break up a large HTML file into smaller, more easier to manage chunks.&lt;/p&gt;
&lt;p&gt;Partial templates are found under the &lt;code&gt;layouts/partials&lt;/code&gt; directory and follow the same principle as defined in the primer for template lookups, and are referenced back as &lt;code&gt;{{ partial &amp;quot;&amp;lt;partial_name&amp;gt;.html&amp;quot; . }}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s go ahead and create the &lt;code&gt;layouts/partials/toc.html&lt;/code&gt; file and populate it as below.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ if and (gt .WordCount 400 ) (.Site.Params.toc) }}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#ff79c6&#34;&gt;aside&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  {{.TableOfContents}}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#ff79c6&#34;&gt;aside&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ end }}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is similar to &lt;a href=&#34;https://gohugo.io/content-management/toc/#template-example-toc-partial&#34;&gt;Hugo&amp;rsquo;s example&lt;/a&gt; but just tweaked a bit. I&amp;rsquo;m not looking to do anything too fancy with the styling just yet, but maybe later on.&lt;/p&gt;
&lt;p&gt;Previewing it by &lt;code&gt;hugo serve&lt;/code&gt; we should get something that appears as below.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore/completed-toc.png&#34; class=&#34;screenshotimg&#34;
         alt=&#34;Screenshot showing rendered Table of Contents&#34;/&gt; 
&lt;/figure&gt;

&lt;h3 id=&#34;bugs-along-the-way&#34;&gt;Bugs Along The Way&lt;/h3&gt;
&lt;p&gt;As the heading suggests, there were a few extra things I needed to change before releasing it into the wild.&lt;/p&gt;
&lt;h4 id=&#34;fix-improper-headings&#34;&gt;Fix Improper Headings&lt;/h4&gt;
&lt;p&gt;Per &lt;a href=&#34;https://gohugo.io/content-management/toc/#usage&#34;&gt;Table of Contents Usage documentation&lt;/a&gt;, Hugo will render from &lt;code&gt;&amp;lt;h2&amp;gt;&lt;/code&gt; headings, which are defined as &lt;code&gt;##&lt;/code&gt; in markdown. So &lt;code&gt;# Introduction&lt;/code&gt; would not be included in the TOC, but &lt;code&gt;## Introduction&lt;/code&gt; will.&lt;/p&gt;
&lt;p&gt;This is a problem for some of my articles since I&amp;rsquo;ve been writing headings liberally at the wrong level.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;
    &lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore/improper-toc-headings.png&#34; class=&#34;screenshotimg&#34;
         alt=&#34;Screenshot showing table of contents not including all headings for a post&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Notice the &lt;strong&gt;Applying Cartography&lt;/strong&gt; heading doesn&amp;rsquo;t appear in the TOC&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;The given example above shows what happens when we use &lt;code&gt;# Heading Name&lt;/code&gt; instead of &lt;code&gt;## Heading Name&lt;/code&gt;. To fix this I just need to go through each heading and make sure top-level headings begin with &lt;code&gt;##&lt;/code&gt; and any subheadings have an additional &lt;code&gt;#&lt;/code&gt;.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;
    &lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore/proper-toc-headings.png&#34; class=&#34;screenshotimg&#34;
         alt=&#34;Screenshot showing table of contents fixed with correct heading indentation&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Much better!&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id=&#34;fix-emojis&#34;&gt;Fix Emojis&lt;/h4&gt;
&lt;p&gt;Who doesn&amp;rsquo;t love the use of emojis &amp;#x2049;&amp;#xfe0f; Well I don&amp;rsquo;t when they render badly. Before this exercise I was using a custom shortcode to render them.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;# layouts/shortcodes/emoji.html
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{{ .Get 0 | emojify }}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://gohugo.io/content-management/shortcodes/&#34;&gt;Shortcodes&lt;/a&gt; are similar to partial templates in that they contain HTML, but they are usually for referring to one HTML element as opposed to multiple.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then in my markdown I would refer back to them such as &lt;code&gt;{{&amp;lt; emoji &amp;quot;:wave:&amp;quot; &amp;gt;}}&lt;/code&gt;. However in headings they rendered as below&amp;hellip;&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;
    &lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore/bad-emojis-headings.png&#34; class=&#34;screenshotimg&#34;
         alt=&#34;Screenshot showing poorly rendered emojis in Table of Contents&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Hugo clearly finds this hysterical&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;It&amp;rsquo;s a weird one for sure - especially since they render fine in the headings themselves already&amp;hellip;&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;
    &lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore/good-emoji-heading-1.png&#34; class=&#34;screenshotimg&#34;
         alt=&#34;Screenshot with heading example with emoji 1&#34;/&gt; 
&lt;/figure&gt;

&lt;figure class=&#34;center&#34;&gt;
    &lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore/good-emoji-heading-2.png&#34; class=&#34;screenshotimg&#34;
         alt=&#34;Screenshot with heading example with emoji 2&#34;/&gt; 
&lt;/figure&gt;

&lt;p&gt;There is a fix for these, and it&amp;rsquo;s called &lt;em&gt;rendering emojis the correct way!&lt;/em&gt; &amp;#x1f605;&lt;/p&gt;
&lt;p&gt;Firstly in the site &lt;code&gt;config.toml&lt;/code&gt; I added &lt;code&gt;enableEmoji = true&lt;/code&gt; at the root level and then proceeded to change all occurrence of &lt;code&gt;{{&amp;lt; emoji &amp;quot;:emoji_name:&amp;quot; &amp;gt;}}&lt;/code&gt; to &lt;code&gt;:emoji_name:&lt;/code&gt;. With that I can then remove &lt;code&gt;layouts/shortcodes/emoji.html&lt;/code&gt; since it&amp;rsquo;s not being referred back to anymore.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;
    &lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore/correct-emoji-headings.png&#34; class=&#34;screenshotimg&#34;
         alt=&#34;Screenshot with correct emoji rendering in table of contents&#34;/&gt; &lt;figcaption&gt;
            &lt;p&gt;Nicely done&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id=&#34;markdown-not-rendering&#34;&gt;Markdown Not Rendering&lt;/h4&gt;
&lt;p&gt;I also noticed that markdown wasn&amp;rsquo;t rendering properly too. Given the markdown &lt;code&gt;## Spice Up Your ~~Life~~ Python :snake:&lt;/code&gt;, which renders as a heading in the screenshot below&amp;hellip;&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;
    &lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore/markdown-heading-render.png&#34; class=&#34;screenshotimg&#34;
         alt=&#34;Screenshot showing a heading formatted correctly with markdown&#34;/&gt; 
&lt;/figure&gt;

&lt;p&gt;But in the table of contents it appears without the tilde strikethrough (&lt;code&gt;~&lt;/code&gt;) rendering.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;
    &lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore/incorrect-markdown-rendering-toc.png&#34; class=&#34;screenshotimg&#34;
         alt=&#34;Screenshot showing tilde strikethrough not appearing in table of content entry&#34;/&gt; 
&lt;/figure&gt;

&lt;p&gt;This looks to be a bug within Hugo itself, since I was able to get &lt;strong&gt;strong&lt;/strong&gt; and &lt;em&gt;emphasise&lt;/em&gt; to render fine - I &lt;a href=&#34;https://github.com/gohugoio/hugo/issues/7169&#34;&gt;raised an issue&lt;/a&gt; on the project - I can live with it for now.&lt;/p&gt;
&lt;h2 id=&#34;emojify-everything&#34;&gt;Emojify Everything!&lt;/h2&gt;
&lt;p&gt;Continuing on the theme of emojis; we were able to get them to appear in the Table of Contents fine. But what about in the titles of the articles themselves? We can add them in the &lt;code&gt;title&lt;/code&gt; front matter setting like below:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;date&lt;/span&gt;: 2019-07-05
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;title&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;:​​​snake: Three Ways To Spice Up Your Python Code&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Having a look around the site we see these aren&amp;rsquo;t getting rendered correctly in a number of places&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(1)&lt;/strong&gt; Post title&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;
    &lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore/broken-emoji-post-title.png&#34; class=&#34;screenshotimg&#34;
         alt=&#34;Screenshot of broken emoji on post title&#34;/&gt; 
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;(2)&lt;/strong&gt; Browser title&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;broken-emoji-browser-title.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore//blog/who-goes-blogging-5-updates-galore/broken-emoji-browser-title.png&#34;
    alt=&#34;Screenshot of broken emoji on browser title&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;(3)&lt;/strong&gt; Type list entry&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;
    &lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore/broken-emoji-list.png&#34; class=&#34;screenshotimg&#34;
         alt=&#34;Screenshot of broken emoji rendering on lists&#34;/&gt; 
&lt;/figure&gt;

&lt;p&gt;This is all to do with how the theme is rendering these, they are not &amp;ldquo;&lt;a href=&#34;https://gohugo.io/functions/emojify/&#34;&gt;emojifying&lt;/a&gt;&amp;rdquo; the text it is receiving. This is easy to fix but it involves us copying over the respective files where they are being rendered from in hugo-coder and overwriting thme in our project root (back to &lt;a href=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore/#template-lookup-order-primer&#34;&gt;template ordering&lt;/a&gt; again!).&lt;/p&gt;
&lt;h3 id=&#34;fix-post--browser-title&#34;&gt;Fix Post &amp;amp; Browser Title&lt;/h3&gt;
&lt;p&gt;We can kill two birds with one stone since these are both found in the same file, the &lt;code&gt;layouts/posts/single.html&lt;/code&gt; file - this is the main file used to render the article page. If you followed &lt;a href=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore/#implementation&#34;&gt;above&lt;/a&gt;, you&amp;rsquo;ll notice we already copied across that file to our project root in order to add a TOC.&lt;/p&gt;
&lt;p&gt;We need to go back to that file and &lt;em&gt;emojify&lt;/em&gt; both the main article heading, alongside the browser title to change any occurrence of &lt;code&gt;{{ .Title }}&lt;/code&gt; to &lt;code&gt;{{ .Title | emojify }}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;By doing this we will &lt;em&gt;always&lt;/em&gt; emojify the titles even if in our &lt;code&gt;config.toml&lt;/code&gt; we specified &lt;code&gt;enableEmoji = false&lt;/code&gt;, or didn&amp;rsquo;t specify it at all. This will conflict with the default state of the setting which is &lt;code&gt;false&lt;/code&gt;. We could do some conditional logic on the &lt;code&gt;enableEmoji&lt;/code&gt; setting, but unfortunately this isn&amp;rsquo;t exposed to us to be able to perform something like &lt;code&gt;{{ if .Site.enableEmoji }}&lt;/code&gt;. Because of this we cannot create a pull request (PR) to merge it in with the &lt;code&gt;hugo-coder&lt;/code&gt; theme. I&amp;rsquo;ve raised a &lt;a href=&#34;https://github.com/gohugoio/hugo/issues/7171&#34;&gt;couple&lt;/a&gt; of &lt;a href=&#34;https://github.com/gohugoio/hugo/issues/7170&#34;&gt;issues&lt;/a&gt; against Hugo requesting these features.&lt;/p&gt;
&lt;p&gt;Since this is just for my website I am okay with the overriding behaviour of emojiying the title.&lt;/p&gt;
&lt;p&gt;See the Gist below for my completed &lt;code&gt;single.html&lt;/code&gt; file, including the TOC addition earlier.&lt;/p&gt;
&lt;script src=&#34;https://gist.github.com/jdheyburn/35a10e346848379ec5f6f87cb48454cd/3a30b153aae5a761f62293add95187e280d8c3d3.js&#34;&gt;&lt;/script&gt;

&lt;p&gt;You&amp;rsquo;ll notice I&amp;rsquo;m emojifying (surely I&amp;rsquo;ve said this enough times to make it into the Oxford Dictionary?!) the site title too. This is incase myself (or even yourselves) wish to add emojis there too.&lt;/p&gt;
&lt;p&gt;The end result for the title will look like this:&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;
    &lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore/fixed-emoji-post-title.png&#34; class=&#34;screenshotimg&#34;
         alt=&#34;Screenshot of fixed emoji on post title&#34;/&gt; 
&lt;/figure&gt;

&lt;p&gt;And the browser title is fixed accordingly:&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;fixed-emoji-browser-title.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore//blog/who-goes-blogging-5-updates-galore/fixed-emoji-browser-title.png&#34;
    alt=&#34;Screenshot of fixed emoji on browser title&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;fix-type-list-entry&#34;&gt;Fix Type List Entry&lt;/h3&gt;
&lt;p&gt;We need to find the file in the theme that determines how to render a list entry for a page of type &lt;code&gt;posts&lt;/code&gt; and make the modification there - &lt;code&gt;layouts/posts/li.html&lt;/code&gt;. We&amp;rsquo;ll need to do the same here as we did for &lt;code&gt;single.html&lt;/code&gt; and copy it to our project root.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cp -v themes/hugo-coder/layouts/posts/li.html layouts/posts
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Likewise for the title in the previous section, we need to emojify the title of the article as it&amp;rsquo;s being listed out.&lt;/p&gt;
&lt;script src=&#34;https://gist.github.com/jdheyburn/177fb9f74a38306d9257fb7302ba221c.js&#34;&gt;&lt;/script&gt;

&lt;p&gt;Once done, we can see it reflect nicely in the list:&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;
    &lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-5-updates-galore/fixed-emoji-list.png&#34; class=&#34;screenshotimg&#34;
         alt=&#34;Screenshot of fixed emoji rendering on lists&#34;/&gt; 
&lt;/figure&gt;

&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;There was a lot covered in this post, to recap we&amp;hellip;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Upgraded the Hugo version used to render the website locally, as well as in the CI/CD pipeline&lt;/li&gt;
&lt;li&gt;Also updated the version of the theme we&amp;rsquo;re using
&lt;ul&gt;
&lt;li&gt;Made use of new features that came with it such as series links&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Added a table of contents to our longer articles&lt;/li&gt;
&lt;li&gt;Added support for emojis in post titles&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can view all the changes made in this post at this &lt;a href=&#34;https://github.com/jdheyburn/jdheyburn.co.uk/pull/2&#34;&gt;pull request&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks for reading! &amp;#x1f31d;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Who Goes Blogging 4: Content Structure &amp; Refactoring</title><enclosure url="https://jdheyburn.co.uk/images/jdheyburn_co_uk_card.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/blog/who-goes-blogging-4-content-structure-and-refactoring/</link>
      <pubDate>Tue, 31 Mar 2020 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/who-goes-blogging-4-content-structure-and-refactoring/</guid>-->
      <description>&lt;p&gt;So far in this series we&amp;rsquo;ve been looking at the infrastructure behind the Hugo website, but I haven&amp;rsquo;t looked into improving the content layout of the site. Some of the improvements I want to make are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Moving content articles from a flat structure to a leaf bundle&lt;/li&gt;
&lt;li&gt;Rename content section to &lt;strong&gt;blog&lt;/strong&gt; from &lt;strong&gt;posts&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Upgrading &lt;code&gt;hugo-coder&lt;/code&gt; theme and &lt;code&gt;hugo&lt;/code&gt; version&lt;/li&gt;
&lt;li&gt;Adding a table of contents to the pages&lt;/li&gt;
&lt;li&gt;&amp;hellip; and further improvements as I find them&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This post will cover items 1-2, the others will be in a follow up post.&lt;/p&gt;
&lt;h2 id=&#34;content-article-reorganising&#34;&gt;Content Article Reorganising&lt;/h2&gt;
&lt;p&gt;Hugo has a few different ways you can organise your content, known as &lt;a href=&#34;https://gohugo.io/content-management/page-bundles/&#34;&gt;Page Bundles&lt;/a&gt;. Up to now  I&amp;rsquo;ve adopted &lt;a href=&#34;https://gohugo.io/content-management/page-bundles/#branch-bundles&#34;&gt;Branch Bundles&lt;/a&gt; where I have all my articles in named markdown files under the &lt;code&gt;content/posts&lt;/code&gt; directory - acting as the branch bundle, and storing all images under &lt;code&gt;static/images&lt;/code&gt; - so the article at &lt;code&gt;content/posts/blog-bootstrap.md&lt;/code&gt; will be available at &lt;code&gt;/posts/blog-bootstrap/&lt;/code&gt;. As time has gone on, the images directory has gotten rather large and difficult to understand what images apply to what posts.&lt;/p&gt;
&lt;p&gt;The alternative way to organise content is the &lt;a href=&#34;https://gohugo.io/content-management/page-bundles/#leaf-bundles&#34;&gt;Leaf Bundle&lt;/a&gt;. This is where at a branch level you have a directory with the name of the article you wish to publish, followed by an &lt;code&gt;index.md&lt;/code&gt; file with the article content - such as &lt;code&gt;content/posts/blog-bootstrap/index.md&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Any page resources defined in this directory can be referenced relatively in the markdown files within it, such that any images can be stored within &lt;code&gt;content/posts/blog-bootstrap/&lt;/code&gt; and referred to in the markdown as &lt;code&gt;figure src=&amp;quot;my-relative-img.png&amp;quot; ...&lt;/code&gt; as opposed to &lt;code&gt;src=&amp;quot;/images/my-not-relative-img.png&amp;quot; ...&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This allows me to organise content much more effectively such that we will be going from this structure:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ tree content/posts 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;content/posts
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── blog-bootstrap.md
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── extending-gotests-for-strict-error-tests.md
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── on-becoming-an-open-source-software-contributor.md
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── three-ways-to-spice-up-your-python-code.md
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── who-goes-blogging-0-applying-cartography.md
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── who-goes-blogging-1-getting-started.md
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── who-goes-blogging-2-custom-domain.md
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── who-goes-blogging-3-1-deployment-methods-travisci.md
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;└── who-goes-blogging-3-2-deployment-methods-github-actions.md
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt; directories, &lt;span style=&#34;color:#bd93f9&#34;&gt;9&lt;/span&gt; files
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ tree static/images 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;static/images
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── analytics_example.png
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── ...                     &lt;span style=&#34;color:#6272a4&#34;&gt;# Everything stored in a flat hierarchy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;└── type_hinting.png
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt; directories, &lt;span style=&#34;color:#bd93f9&#34;&gt;57&lt;/span&gt; files
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ tree content/posts
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;content/posts
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── blog-bootstrap
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   └── index.md
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── extending-gotests-for-strict-error-tests
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   └── index.md
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── on-becoming-an-open-source-software-contributor
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   ├── card.png
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   ├── elephant-cartoon.png
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   └── index.md
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── ...                     
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;└── who-goes-blogging-3-2-deployment-methods-github-actions
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ├── gha-deploy-key.png
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ├── gha-secrets-key.png
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ├── github-actions-build.png
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ├── index.md
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    └── travis-disable-build.png
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bd93f9&#34;&gt;9&lt;/span&gt; directories, &lt;span style=&#34;color:#bd93f9&#34;&gt;45&lt;/span&gt; files
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;One other benefit of grouping all content together in a bundle is it allows you to use just one command to add your article content to the repository - no more will images in another directory be forgotten about!&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git add content/blog/&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$articleName&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;transformation-script&#34;&gt;Transformation Script&lt;/h3&gt;
&lt;p&gt;As opposed to doing the transformation manually, I&amp;rsquo;ve written a script which will move and alter the files for us.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 3
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 4
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 5
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 6
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 7
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 8
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt; 9
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;10
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;11
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;12
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;13
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;14
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;15
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;16
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;17
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;18
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;19
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;20
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;21
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;22
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;23
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;24
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;25
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;26
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;27
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;28
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34;&gt;29
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# in project root dir&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;posts&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;$(&lt;/span&gt;find content/posts -type f -name &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;*.md&amp;#39;&lt;/span&gt; -not -path &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;**/index.md&amp;#39;&lt;/span&gt; -not -path &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;**/_index.md&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$posts&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt; | &lt;span style=&#34;color:#ff79c6&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;read&lt;/span&gt; p; &lt;span style=&#34;color:#ff79c6&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#6272a4&#34;&gt;# Move to leaf bundle&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;postFname&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;$(&lt;/span&gt;basename -- &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$p&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;title&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;postFname&lt;/span&gt;%.*&lt;span style=&#34;color:#f1fa8c&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;leafBundleDir&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;content/posts/&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$title&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    mkdir &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$leafBundleDir&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;newPostLocation&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$leafBundleDir&lt;/span&gt;/index.md
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    git mv -v &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$p&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$newPostLocation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#6272a4&#34;&gt;# Move images referenced in the articles to new leaf bundle dir&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;images&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;$(&lt;/span&gt;grep -oP &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;images\/.*.(png|jpg|jpeg)&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$newPostLocation&lt;/span&gt; | xargs -n1 | sort -u&lt;span style=&#34;color:#ff79c6&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$images&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt; | &lt;span style=&#34;color:#ff79c6&#34;&gt;while&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;read&lt;/span&gt; i; &lt;span style=&#34;color:#ff79c6&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;[&lt;/span&gt; -z &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$i&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;]&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;imgFname&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;$(&lt;/span&gt;basename -- &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$i&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        git mv -v static/&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$i&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$leafBundleDir&lt;/span&gt;/&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$imgFname&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#6272a4&#34;&gt;# Change references to images in the article&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#6272a4&#34;&gt;## figure src=&amp;#34;/images/namecheap-domain-purchase.png&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sed -i &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;s/src=&amp;#34;\/images\//src=&amp;#34;/g&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$newPostLocation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#6272a4&#34;&gt;## images:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#6272a4&#34;&gt;##   - images/namecheap_landing.png&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sed -i &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;s/- images\//- /g&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$newPostLocation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#6272a4&#34;&gt;## [blog_arch]: /images/blog-arch-cover.png&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    sed -i &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;s/: \/images\//: /g&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$newPostLocation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    git add &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;$newPostLocation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;done&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Breaking down the script, firstly we are retrieving all posts that aren&amp;rsquo;t already converted to a leaf bundle and looping over them, where lines 4-9 performs the conversion itself.&lt;/p&gt;
&lt;p&gt;Once that is done then we need to get all images that are referenced in that post and move them to the new leaf bundle for the post. This is done in lines 12-17.&lt;/p&gt;
&lt;p&gt;Lastly, we then need to change the post content itself so that it references images in the location relative to the leaf bundle instead of under &lt;code&gt;static/images&lt;/code&gt; - lines 21-26 accomplish this.&lt;/p&gt;
&lt;p&gt;Hugo allows you to define &lt;code&gt;img&lt;/code&gt; blocks in several ways, where I&amp;rsquo;m currently using 3 of these methods. I &lt;del&gt;probably&lt;/del&gt; should replace these with one approach - for the time being 3 &lt;code&gt;sed&lt;/code&gt; commands will do the trick for me.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I&amp;rsquo;m sure that a few lines could be cut out with some better regex expressions - but I&amp;rsquo;m not an expert at them so I&amp;rsquo;d rather crack on with what I know &amp;#x1f603;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;renaming-content-section&#34;&gt;Renaming Content Section&lt;/h2&gt;
&lt;p&gt;A gripe I&amp;rsquo;ve had with my site is when you click on &lt;strong&gt;Blog&lt;/strong&gt; in the site header it takes you to &lt;strong&gt;Posts&lt;/strong&gt;. Similar to the previous section this is because of &lt;a href=&#34;https://gohugo.io/content-management/page-bundles/#branch-bundles&#34;&gt;Branch Bundles&lt;/a&gt; which are any directory that sits underneath the &lt;code&gt;content&lt;/code&gt; directory. The bundle name here is what forms the URL and the parent page for those articles &lt;a href=&#34;https://gohugo.io/content-management/sections/&#34;&gt;as defined&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Based on this information, migrating should be as easy as&amp;hellip;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ git mv content/posts/ content/blog/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Despite doing this, we come across some problems&amp;hellip;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We lose the correct rendering for the article
&lt;ul&gt;
&lt;li&gt;Hugo doesn&amp;rsquo;t know how to render content with type &lt;code&gt;blog&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Existing URL links are broken
&lt;ul&gt;
&lt;li&gt;All hyperlinks both internal and external are referring to articles at &lt;code&gt;/posts/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Blog landing page is now &lt;strong&gt;Blogs&lt;/strong&gt;?? (see below)&lt;/li&gt;
&lt;/ol&gt;
&lt;figure&gt;&lt;a href=&#34;blogs-landing-page.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-4-content-structure-and-refactoring//blog/who-goes-blogging-4-content-structure-and-refactoring/blogs-landing-page.png&#34;
    alt=&#34;Screenshot of Blog landing page with incorrect Blogs title&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Let&amp;rsquo;s walk through them one by one.&lt;/p&gt;
&lt;h3 id=&#34;fixing-article-rendering&#34;&gt;Fixing Article Rendering&lt;/h3&gt;
&lt;p&gt;We just made our existing articles leaf bundles that sat under &lt;code&gt;content/posts&lt;/code&gt;, but now this is renamed to &lt;code&gt;content/blog&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ tree contents/blog -L &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;content/blog
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── blog-bootstrap
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── extending-gotests-for-strict-error-tests
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── on-becoming-an-open-source-software-contributor
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── three-ways-to-spice-up-your-python-code
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── who-goes-blogging-0-applying-cartography
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── who-goes-blogging-1-getting-started
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── who-goes-blogging-2-custom-domain
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── who-goes-blogging-3-1-deployment-methods-travisci
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;└── who-goes-blogging-3-2-deployment-methods-github-actions
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bd93f9&#34;&gt;9&lt;/span&gt; directories, &lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt; files
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The section of which your article sits underneath also tells Hugo how it should render the page - known as &lt;a href=&#34;https://gohugo.io/content-management/types/#defining-a-content-type&#34;&gt;Content Types&lt;/a&gt;. Take the article blog-bootstrap which is under &lt;code&gt;posts&lt;/code&gt;; Hugo will look in the theme for &lt;code&gt;layouts/posts&lt;/code&gt; for &lt;code&gt;html&lt;/code&gt; files to render it against* - this is how the articles go from a markdown file to rendered HTML.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;* Hugo will &lt;a href=&#34;https://gohugo.io/templates/lookup-order/&#34;&gt;first look&lt;/a&gt; in your local project for any overriding files.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ tree themes/hugo-coder/layouts/posts 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;themes/hugo-coder/layouts/posts
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── li.html
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── list.html
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;└── single.html
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt; directories, &lt;span style=&#34;color:#bd93f9&#34;&gt;3&lt;/span&gt; files
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When we change the section name the article sits in, Hugo will not know how to render it correctly and will resolve to the default rendering for the theme. This ends us with something like the below.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;posts-blog-rendering-error.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-4-content-structure-and-refactoring//blog/who-goes-blogging-4-content-structure-and-refactoring/posts-blog-rendering-error.png&#34;
    alt=&#34;Screenshot of comparison of correct and incorrect rendering - publish date, minutes to read, and tags are missing&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Correct rendering on the left - notice publish date, minutes to read, and tags are missing&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;One way of fixing this would be to also change the &lt;code&gt;hugo-coder&lt;/code&gt; theme from &lt;code&gt;layouts/posts&lt;/code&gt; to &lt;code&gt;layouts/blog&lt;/code&gt; - however a much simpler solution exists. We can use the &lt;code&gt;type&lt;/code&gt; front matter setting to tell Hugo what layout to use to render it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;date&lt;/span&gt;: 2020-03-10
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;title&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Who Goes Blogging 3.2: Deployment Methods - GitHub Actions&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: Migrating to GitHub Actions as our CI tool
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;type&lt;/span&gt;: posts
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;Hugo &lt;a href=&#34;https://gohugo.io/content-management/front-matter&#34;&gt;front matter&lt;/a&gt; are content parameter settings you place into the heading of the content - an example of this is right above!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Having to define this in every new article can become repetitive. We can make this easier for ourselves by using &lt;a href=&#34;https://gohugo.io/content-management/archetypes/&#34;&gt;archetypes&lt;/a&gt;; which are essentially templates that new content is templated from.&lt;/p&gt;
&lt;p&gt;In your project root directory, create the &lt;code&gt;archetypes/blog&lt;/code&gt; directory and in there a file called &lt;code&gt;index.md&lt;/code&gt;, and populate it with the below:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;draft&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;type&lt;/span&gt;: posts
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# ...       Add in other defaults as you see fit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can then create new content from this template like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ hugo new --kind blog blog/blog-post 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;/home/jdheyburn/projects/jdheyburn.co.uk/content/blog/blog-post created
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ tree content/blog/blog-post         
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;content/blog/blog-post
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;└── index.md
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bd93f9&#34;&gt;0&lt;/span&gt; directories, &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt; file
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ cat content/blog/blog-post/index.md 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;draft: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;type: posts
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;fixing-existing-urls&#34;&gt;Fixing Existing URLs&lt;/h3&gt;
&lt;p&gt;Changing the section the article is in affects the URL too; meaning articles will appear at &lt;code&gt;https://jdheyburn.co.uk/blog/blog-bootstrap/&lt;/code&gt; instead of &lt;code&gt;https://jdheyburn.co.uk/posts/blog-bootstrap/&lt;/code&gt;. Therefore existing hyperlinks pointing to these articles will break.&lt;/p&gt;
&lt;p&gt;There is another quick fix available for us through the front matter setting &lt;code&gt;aliases&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;date&lt;/span&gt;: 2020-03-10
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;title&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Who Goes Blogging 3.2: Deployment Methods - GitHub Actions&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;description&lt;/span&gt;: Migrating to GitHub Actions as our CI tool
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;type&lt;/span&gt;: posts
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;aliases&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;    - /posts/who-goes-blogging-3-2-deployment-methods-github-actions/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can read more about how &lt;code&gt;aliases&lt;/code&gt; does its operations &lt;a href=&#34;https://gohugo.io/content-management/urls/#aliases&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;from-blogs-to-blog&#34;&gt;From Blogs to Blog&lt;/h3&gt;
&lt;p&gt;Earlier on, we came across this weird bug&amp;hellip;&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;blogs-landing-page.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-4-content-structure-and-refactoring//blog/who-goes-blogging-4-content-structure-and-refactoring/blogs-landing-page.png&#34;
    alt=&#34;Screenshot of Blog landing page with incorrect Blogs title&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;This is occurring because of the translation feature of Hugo (sourced from &lt;a href=&#34;https://gohugo.io/functions/i18n/&#34;&gt;i18n&lt;/a&gt;) which converts singular word titles to plural on content section landing pages, since Hugo is listing them out. This made sense when our previous section name was posts, but now we&amp;rsquo;d rather it have say blog.&lt;/p&gt;
&lt;p&gt;Remembering that &lt;code&gt;content/blog&lt;/code&gt; is a &lt;a href=&#34;https://gohugo.io/content-management/page-bundles/#branch-bundles&#34;&gt;branch bundle&lt;/a&gt;, we can override the inferred title with a &lt;code&gt;_index.md&lt;/code&gt; file at that directory.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# content/blog/_index.md&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;title: Blog
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;type: posts
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We have another &lt;code&gt;type: posts&lt;/code&gt; setting here because similar to above, we want Hugo to render the landing page the same as we did for posts.&lt;/p&gt;
&lt;h3 id=&#34;heading-to-blog&#34;&gt;Heading to Blog&lt;/h3&gt;
&lt;p&gt;Lastly and most easily, the &lt;strong&gt;Blog&lt;/strong&gt; shortcut at the top-right corner of the page still directs us to &lt;code&gt;/posts&lt;/code&gt; - we need to change that in the top-level &lt;code&gt;config.toml&lt;/code&gt; to point to &lt;code&gt;/blog&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[[languages.en.menu.main]]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;name = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Blog&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;weight = &lt;span style=&#34;color:#bd93f9&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;url = &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;/blog/&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Thanks for reading!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Who Goes Blogging 3.2: Deployment Methods - GitHub Actions</title><enclosure url="https://jdheyburn.co.uk/blog/who-goes-blogging-3-2-deployment-methods-github-actions/github-actions-build.png" type="image/jpg"></enclosure>
      
      <link>https://jdheyburn.co.uk/blog/who-goes-blogging-3-2-deployment-methods-github-actions/</link>
      <pubDate>Tue, 10 Mar 2020 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/who-goes-blogging-3-2-deployment-methods-github-actions/</guid>-->
      <description>&lt;h2 id=&#34;from-travisci-to-github-actions&#34;&gt;From TravisCI to GitHub Actions&lt;/h2&gt;
&lt;p&gt;In the &lt;a href=&#34;https://jdheyburn.co.uk/posts/who-goes-blogging-3-1-deployment-methods-travisci/&#34;&gt;previous post&lt;/a&gt; we looked at moving to a CI/CD model by moving from the &lt;code&gt;deploy.sh&lt;/code&gt; script to TravisCI.&lt;/p&gt;
&lt;p&gt;In this post we will look at how we can migrate from TravisCI to &lt;a href=&#34;https://help.github.com/en/actions/getting-started-with-github-actions/about-github-actions&#34;&gt;GitHub Actions&lt;/a&gt;, GitHub&amp;rsquo;s own CI/CD tool.&lt;/p&gt;
&lt;p&gt;This post will also be useful if you are looking to onboard GitHub Actions as your CI/CD pipeline! &amp;#x1f680;&lt;/p&gt;
&lt;h3 id=&#34;benefits-white_check_mark&#34;&gt;Benefits &amp;#x2705;&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s talk about why we want to migrate away from TravisCI in the first place.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Firstly and most importantly&lt;/strong&gt;, there is a whole community of shared actions (a set of build instructions) which can save you a &lt;em&gt;huge&lt;/em&gt; amount of time when it comes to piecing together a CI pipeline. If the TravisCI config seemed a bit intimidating, then these will be a whole lot more gentler to you.&lt;/p&gt;
&lt;p&gt;Whereas in our Travis config we had to define the individual commands needed to set up our environment and then how to build it, there&amp;rsquo;s an &lt;a href=&#34;https://github.com/marketplace/actions/hugo-setup&#34;&gt;action&lt;/a&gt; for that! Want to include some markdown linting? There&amp;rsquo;s an &lt;a href=&#34;https://github.com/marketplace/actions/markdownlint-cli&#34;&gt;action&lt;/a&gt; for that!&lt;/p&gt;
&lt;p&gt;I think you folks get the picture now. There&amp;rsquo;s an &lt;a href=&#34;https://github.com/sdras/awesome-actions&#34;&gt;awesome-actions&lt;/a&gt; repository worth checking out for more actions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Secondly&lt;/strong&gt;, all your DevOps tools are in one place! I&amp;rsquo;m a big sucker for &lt;a href=&#34;https://about.gitlab.com/&#34;&gt;GitLab&lt;/a&gt; and while I don&amp;rsquo;t use it for my personal projects, I&amp;rsquo;ve used it in a past life and found its seamless integration with all other tools second-to-none. Not having to worry about integrating between multiple services can only increase your productivity - allowing you to focus more on the application you&amp;rsquo;re writing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lastly&lt;/strong&gt;, all configuration is managed in the workflow configuration file. One enhancement in particular that we will be introducing can be achieved with an additional setting in the workflow config file; for us to achieve the same in Travis would have to be done via the GUI. I&amp;rsquo;m a big sucker for having configuration baked into code so this is a very good plus.&lt;/p&gt;
&lt;h3 id=&#34;pricing-alarm_clock&#34;&gt;Pricing &amp;#x23f0;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;However&lt;/strong&gt; - one downsides to GitHub Actions is how many build minutes you get. Remember Travis allowed unlimited build minutes for a public repository? With Actions - you are limited to &lt;a href=&#34;https://github.com/pricing&#34;&gt;2,000 minutes in their free plan&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve been building your project in Travis already, you&amp;rsquo;ll notice it has been building (in my case at least) in ~30 seconds. With a bit of maths we can then say we will have 4,000 builds in a month on GitHub Actions.&lt;/p&gt;
&lt;p&gt;Given that this isn&amp;rsquo;t a huge project with multiple contributors working on it, I think it&amp;rsquo;s safe to say we won&amp;rsquo;t ever reach this limit - unless you&amp;rsquo;re churning out blog posts left right and centre!&lt;/p&gt;
&lt;p&gt;Sound good? Let&amp;rsquo;s go.&lt;/p&gt;
&lt;h2 id=&#34;creating-our-workflow&#34;&gt;Creating Our Workflow&lt;/h2&gt;
&lt;p&gt;Like all great services in the world, there is &lt;a href=&#34;https://help.github.com/en/actions&#34;&gt;great documentation&lt;/a&gt; to go along with them. Take a look over there if you&amp;rsquo;d like the detailed version.&lt;/p&gt;
&lt;p&gt;What I will be focusing on is the documentation for two sets of predefined actions; &lt;a href=&#34;https://github.com/peaceiris/actions-hugo&#34;&gt;actions-hugo&lt;/a&gt; for building our website, and &lt;a href=&#34;https://github.com/peaceiris/actions-gh-pages&#34;&gt;actions-gh-pages&lt;/a&gt; for deploying it to GitHub Pages.&lt;/p&gt;
&lt;h3 id=&#34;deployment-keys-setup&#34;&gt;Deployment Keys Setup&lt;/h3&gt;
&lt;p&gt;The very first thing we need to do is set up some keys that will allow our source repository (where the workflow will reside on) to push the built project to the GitHub Pages repo.&lt;/p&gt;
&lt;p&gt;In your terminal, create those keys now and copy the contents of the public key to your clipboard.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ ssh-keygen -t rsa -b &lt;span style=&#34;color:#bd93f9&#34;&gt;4096&lt;/span&gt; -C &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;$(&lt;/span&gt;git config user.email&lt;span style=&#34;color:#ff79c6&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&lt;/span&gt; -f ~/.ssh/gh-pages -N &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Generating public/private rsa key pair.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Your identification has been saved in /home/jdheyburn/.ssh/gh-pages.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Your public key has been saved in /home/jdheyburn/.ssh/gh-pages.pub
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ pbcopy &amp;lt; ~/.ssh/gh-pages.pub
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;macOS has a handy terminal command to copy file contents to the clipboard called &lt;code&gt;pbcopy&lt;/code&gt;. I&amp;rsquo;ve created an alias on my Linux laptop that does the same.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;alias pbcopy=&amp;quot;xclip -selection clipboard&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In GitHub load up your GitHub Pages repo and navigate to &lt;code&gt;Settings&lt;/code&gt; and then &lt;code&gt;Deploy keys&lt;/code&gt;. Give it an appropriate name, and paste in the public key. Make sure you check &lt;code&gt;Allow write access&lt;/code&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;gha-deploy-key.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-3-2-deployment-methods-github-actions//blog/who-goes-blogging-3-2-deployment-methods-github-actions/gha-deploy-key.png&#34;
    alt=&#34;GitHub Pages repo deploy keys page&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Copy the contents of the &lt;em&gt;private key&lt;/em&gt; you created earlier (perhaps using your new command?! &amp;#x1f60f;) and navigate to the source code repository&amp;rsquo;s &lt;code&gt;Settings&lt;/code&gt; page, then &lt;code&gt;Secrets&lt;/code&gt;. You&amp;rsquo;ll need to give it a sensible name as this then referred to later in the workflow configuration. Paste the private key in the value field.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;gha-secrets-key.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-3-2-deployment-methods-github-actions//blog/who-goes-blogging-3-2-deployment-methods-github-actions/gha-secrets-key.png&#34;
    alt=&#34;GitHub source code repo secrets page&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;No secrets here!&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;workflow-configuration&#34;&gt;Workflow Configuration&lt;/h3&gt;
&lt;p&gt;In the root directory of your source code repo, create a directory called &lt;code&gt;.github/workflows&lt;/code&gt;. In this directory is where GitHub Actions will look for jobs to do. Create a &lt;code&gt;yml&lt;/code&gt; file in this directory to contain your build job definition. I went ahead and named mine &lt;code&gt;deploy.yml&lt;/code&gt;, but you can name it whatever you like.&lt;/p&gt;
&lt;p&gt;I used the &lt;a href=&#34;https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-repository-type---project&#34;&gt;example&lt;/a&gt; provided in the &lt;code&gt;actions-gh-pages&lt;/code&gt; documentation as a base for my build definition.&lt;/p&gt;
&lt;script src=&#34;https://gist.github.com/jdheyburn/b4b2cad15604de30f21ad0e1a85ee6b9.js&#34;&gt;&lt;/script&gt;

&lt;p&gt;This config is much simpler to understand than the Travis one - let&amp;rsquo;s break it down once more.&lt;/p&gt;
&lt;h4 id=&#34;build-metadata-and-environment&#34;&gt;Build Metadata and Environment&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Build and deploy to jdheyburn.github.io
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;on&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;push&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;branches&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      - master
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;schedule&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    - &lt;span style=&#34;color:#ff79c6&#34;&gt;cron&lt;/span&gt;:  &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;0 10 * * *&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;jobs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;deploy&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;runs-on&lt;/span&gt;: ubuntu-18.04
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is pretty box standard at this point. We&amp;rsquo;re simply giving the name to the workflow and saying to run it on every push to the &lt;code&gt;master&lt;/code&gt; branch.&lt;/p&gt;
&lt;p&gt;An enhancement that we&amp;rsquo;re adding is the &lt;code&gt;on.schedule.cron&lt;/code&gt; setting. This tells Actions not only to build the project on every push to the &lt;code&gt;master&lt;/code&gt; branch but on a timed schedule too - in my example this is at 10am everyday. We could have done the same via Travis, but that can only be &lt;a href=&#34;https://docs.travis-ci.com/user/cron-jobs/&#34;&gt;configured via the GUI&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The benefit to this is the way in which Hugo generates content. Hugo will only build content pages where the date on the content is either today or in the past, and is not a draft.&lt;/p&gt;
&lt;p&gt;Therefore if you had written a post due to be published in the future, you can define that date and have the daily Hugo build publish it when that date has been reached - which is exactly how this blog post was published!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;jobs&lt;/code&gt; is the field that contains the work we want to run. We&amp;rsquo;re giving the job a name of &lt;code&gt;deploy&lt;/code&gt; and telling it to run on &lt;code&gt;ubuntu-18.04&lt;/code&gt; - which is the equivalent to &lt;code&gt;bionic&lt;/code&gt; in Ubuntu.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In order to keep our build the same as Travis&amp;rsquo;s, we could instruct the job to run on &lt;code&gt;ubuntu-16.04&lt;/code&gt;, nonetheless I&amp;rsquo;m pretty confident it will run on the next &lt;a href=&#34;https://wiki.ubuntu.com/Releases&#34;&gt;LTS of Ubuntu&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&#34;project-checkout&#34;&gt;Project Checkout&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;steps&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/checkout@v2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Checkout submodules
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;shell&lt;/span&gt;: bash
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: |&lt;span style=&#34;color:#f1fa8c&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;      auth_header=&amp;#34;$(git config --local --get http.https://github.com/.extraheader)&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;      git submodule sync --recursive
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;      git -c &amp;#34;http.extraheader=$auth_header&amp;#34; -c protocol.version=2 submodule update --init --force --recursive --depth=1&lt;/span&gt;      
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here we&amp;rsquo;re executing the GitHub &lt;a href=&#34;https://github.com/actions/checkout&#34;&gt;checkout action&lt;/a&gt; which will pull the repo to the build server. It&amp;rsquo;s worth noting that this action version I&amp;rsquo;m using here doesn&amp;rsquo;t checkout git submodules too - which is a problem for us as that&amp;rsquo;s how we&amp;rsquo;re currently pulling the theme for our Hugo site. We can workaround it with the next step in the build - &lt;code&gt;Checkout submodules&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This functionality was available in v1 of the action, so you can use that if you&amp;rsquo;d prefer:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;steps&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  - &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: actions/checkout@v2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt;: 
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#ff79c6&#34;&gt;submodules&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;It&amp;rsquo;s worth noting that we didn&amp;rsquo;t have to do this step for Travis - since it will checkout the repository with submodules already.&lt;/p&gt;
&lt;p&gt;This is because GitHub Actions can be used for many more things than just repository code manipulation where you may not necessarily need the repo checked out.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&#34;build-and-deploy-setup&#34;&gt;Build and Deploy Setup&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Setup Hugo
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: peaceiris/actions-hugo@v2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;hugo-version&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;0.58.3&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;extended&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As stated we&amp;rsquo;re going to be using the Action &lt;a href=&#34;https://github.com/peaceiris/actions-hugo&#34;&gt;actions-hugo&lt;/a&gt; to set up &lt;code&gt;hugo&lt;/code&gt; on our build server. It can take in a number of parameters to allow us to customise it; for us we&amp;rsquo;re only concerned with hardcoding the version of Hugo, and to use the Hugo Extended binary as required by our theme.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;At the time of writing 0.58.3 is not the latest version of Hugo, whereas when I wrote &lt;code&gt;.travis.yml&lt;/code&gt; it was. I want the GitHub Actions build to be as close as possible to the TravisCI build in order to make them as similar as possible to prevent any unexpected build errors.&lt;/p&gt;
&lt;p&gt;Once I&amp;rsquo;ve achieved a successful build - I&amp;rsquo;ll look to upgrade to a newer version, and then iron out any issues from there.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Build
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: hugo --gc --minify
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once &lt;code&gt;hugo&lt;/code&gt; is set up we can then build it easily enough, as self-documented in the code.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Copy CNAME
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;run&lt;/span&gt;: cp CNAME ./public/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Lastly, we can&amp;rsquo;t forget to copy the &lt;code&gt;CNAME&lt;/code&gt; file we made in &lt;a href=&#34;https://jdheyburn.co.uk/posts/who-goes-blogging-2-custom-domain/#solidying-our-changes-with-a-cname-file&#34;&gt;part 2&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&#34;deployment-to-github-pages&#34;&gt;Deployment to GitHub Pages&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- &lt;span style=&#34;color:#ff79c6&#34;&gt;name&lt;/span&gt;: Deploy
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;uses&lt;/span&gt;: peaceiris/actions-gh-pages@v3
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;deploy_key&lt;/span&gt;: ${{ secrets.DEPLOY_KEY }}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;external_repository&lt;/span&gt;: jdheyburn/jdheyburn.github.io
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;publish_branch&lt;/span&gt;: master
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;publish_dir&lt;/span&gt;: ./public
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;commit_message&lt;/span&gt;: ${{ github.event.head_commit.message }}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For the deployment to GitHub Pages I&amp;rsquo;m using the action &lt;a href=&#34;https://github.com/peaceiris/actions-gh-pages&#34;&gt;actions-gh-pages&lt;/a&gt;. Again it only requires a bare minimum of parameters to work; an explanation of what I&amp;rsquo;m using is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;deploy_key&lt;/code&gt; is the &lt;em&gt;private key&lt;/em&gt; we set up &lt;a href=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-3-2-deployment-methods-github-actions/#deployment-keys-setup&#34;&gt;earlier in this post&lt;/a&gt; in &lt;code&gt;Settings&lt;/code&gt; -&amp;gt; &lt;code&gt;Secrets&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;If you didn&amp;rsquo;t name yours &lt;code&gt;DEPLOY_KEY&lt;/code&gt; then you&amp;rsquo;ll need to change it here too.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;external_repository&lt;/code&gt; tells the action where we want the built website to go to - we set this to our GitHub Pages repo&lt;/li&gt;
&lt;li&gt;&lt;code&gt;publish_branch&lt;/code&gt; is the branch of the repo we publish to&lt;/li&gt;
&lt;li&gt;&lt;code&gt;publish_dir&lt;/code&gt; is the directory on the build server that we want to push to the repo
&lt;ul&gt;
&lt;li&gt;Remember that &lt;code&gt;hugo&lt;/code&gt; builds the website to the &lt;code&gt;public&lt;/code&gt; directory locally&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;commit_message&lt;/code&gt; allows us to specify a custom commit message to the target repo
&lt;ul&gt;
&lt;li&gt;Here I am telling it to inherit the commit message used in the source repo&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;bringing-it-all-together-again&#34;&gt;Bringing It All Together (Again)&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re migrating from a previous CI tool (perhaps Travis?) then you&amp;rsquo;ll need to disable the builds on there since you may cause a conflict either build process.&lt;/p&gt;
&lt;p&gt;For Travis, you can do that by navigating to your source code repo settings on Travis (&lt;a href=&#34;https://travis-ci.com/jdheyburn/jdheyburn.co.uk/settings&#34;&gt;https://travis-ci.com/jdheyburn/jdheyburn.co.uk/settings&lt;/a&gt; for me) and disabling &lt;code&gt;Build pushed branches&lt;/code&gt;.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;travis-disable-build.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-3-2-deployment-methods-github-actions//blog/who-goes-blogging-3-2-deployment-methods-github-actions/travis-disable-build.png&#34;
    alt=&#34;Build pushed branches disabled on Travis&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Now that&amp;rsquo;s done, go ahead and check in your new GitHub Actions workflow file and then navigate to the &lt;code&gt;Actions&lt;/code&gt; tab of your source code repo on GitHub.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git add --all
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git commit -m &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;Migrate to GitHub Actions&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git push
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;figure&gt;&lt;a href=&#34;github-actions-build.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-3-2-deployment-methods-github-actions//blog/who-goes-blogging-3-2-deployment-methods-github-actions/github-actions-build.png&#34;
    alt=&#34;Successful build on GitHub Actions&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Great success!&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Hopefully your build went to success! If it didn&amp;rsquo;t have a look through the logs and see what the issue was. It took me a few builds to determine my finalised workflow config. You can even see it at my &lt;a href=&#34;https://github.com/jdheyburn/jdheyburn.co.uk/actions&#34;&gt;source code repo Actions page&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Now that we&amp;rsquo;ve migrated across over to GitHub Actions, we can close out the permissions that TravisCI has on our projects, and demise any secret keys we gave it.&lt;/p&gt;
&lt;p&gt;From the tone of my writing you can probably tell which one I favour. That&amp;rsquo;s not to say I do not like TravisCI - each service has its own pros and cons. For this particular project, I prefer the one platform approach for which I am used to in GitLab. The number of build minutes available for GA is a concern, but not one I will have to worry about for now.&lt;/p&gt;
&lt;p&gt;Thanks for reading! &amp;#x1f31d;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Who Goes Blogging 3.1: Deployment Methods - TravisCI</title><enclosure url="https://jdheyburn.co.uk/blog/who-goes-blogging-3-1-deployment-methods-travisci/travisci-card.png" type="image/jpg"></enclosure>
      
      <link>https://jdheyburn.co.uk/blog/who-goes-blogging-3-1-deployment-methods-travisci/</link>
      <pubDate>Tue, 25 Feb 2020 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/who-goes-blogging-3-1-deployment-methods-travisci/</guid>-->
      <description>&lt;h2 id=&#34;recap&#34;&gt;Recap&lt;/h2&gt;
&lt;p&gt;Since &lt;a href=&#34;https://jdheyburn.co.uk/posts/who-goes-blogging-1-getting-started/&#34;&gt;part 1&lt;/a&gt;, we have been using a simple bash script called &lt;code&gt;deploy.sh&lt;/code&gt; to build our Hugo website and upload it to our GitHub Pages repo. In &lt;a href=&#34;https://jdheyburn.co.uk/posts/who-goes-blogging-2-custom-domain/&#34;&gt;part 2&lt;/a&gt; we modified it slightly to include the &lt;code&gt;CNAME&lt;/code&gt; file post-build to ensure GitHub Pages uses the custom domain we set up in that same part.&lt;/p&gt;
&lt;p&gt;For this part, I will tell you about how I migrated from deploying via a script, to a CI/CD tool - namely &lt;a href=&#34;https://travis-ci.com/&#34;&gt;TravisCI&lt;/a&gt;. Then I will document how I migrated from this, to the new &lt;a href=&#34;https://github.com/features/actions&#34;&gt;GitHub Actions&lt;/a&gt;; GitHub&amp;rsquo;s offering into the CI/CD space.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;CI/CD&lt;/code&gt; is an acronym for Continuous Integration / Continuous Deployment which is a very important concept in the DevOps culture.
If you would like to find out more about that and DevOps culture, check out these resources &amp;#x1f447;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.atlassian.com/devops&#34;&gt;https://www.atlassian.com/devops&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/faun/the-basics-of-continuous-integration-delivery-with-10-most-popular-tools-to-use-9514231533f0&#34;&gt;https://medium.com/faun/the-basics-of-continuous-integration-delivery-with-10-most-popular-tools-to-use-9514231533f0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.redhat.com/en/topics/devops/what-is-ci-cd&#34;&gt;https://www.redhat.com/en/topics/devops/what-is-ci-cd&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.atlassian.com/devops&#34;&gt;https://www.atlassian.com/devops&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.amazon.co.uk/dp/B07B9F83WM&#34;&gt;https://www.amazon.co.uk/dp/B07B9F83WM&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;I had originally planned this to be one post, but it soon became far too long - and I&amp;rsquo;m a fan of taking in content in sizeable chunks.&lt;/p&gt;
&lt;p&gt;So this post will focus on migrating to TravisCI from the &lt;code&gt;deploy.sh&lt;/code&gt; script. Whereas the next post will focus on migrating to, and setting up GitHub Actions. If you&amp;rsquo;re only interested in using that as your CI tool then I&amp;rsquo;ll provide a link to that here when it is posted.&lt;/p&gt;
&lt;h2 id=&#34;moving-away-from-deploysh&#34;&gt;Moving Away From deploy.sh&lt;/h2&gt;
&lt;p&gt;There&amp;rsquo;s nothing necessarily wrong with using &lt;code&gt;deploy.sh&lt;/code&gt; to push our code, however we want to get all the bells and whistles that Continuous Integration can provide to us such as running a series of tests and checks automatically against every commit to our repository. Once those tests and checks pass then we can automate the deployment of our website.&lt;/p&gt;
&lt;p&gt;Now there are many CI tools out there with the most well known likely to be &lt;a href=&#34;https://jenkins.io/&#34;&gt;Jenkins&lt;/a&gt;, but there are also hosted solutions available which will take your code and perform your pipelines against them.&lt;/p&gt;
&lt;p&gt;One of those hosted solutions is &lt;a href=&#34;https://travis-ci.com/&#34;&gt;TravisCI&lt;/a&gt;, where they integrate quite nicely with GitHub repositories to attach &lt;a href=&#34;https://developer.github.com/webhooks/&#34;&gt;webhooks&lt;/a&gt; against them. They have several pricing options available, but for public open source projects, it is completely free!&lt;/p&gt;
&lt;p&gt;So it is a good idea to set your source code repository on GitHub to be public. TravisCI does include (at the time of writing) private projects in their free plan, but you are capped in some shape or form on how much the platform will do for you.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;travis-free.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-3-1-deployment-methods-travisci//blog/who-goes-blogging-3-1-deployment-methods-travisci/travis-free.png&#34;
    alt=&#34;Screenshot of Travis free pricing plan&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Free is definitely a thing you love to see&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;travisci-account-setup&#34;&gt;TravisCI Account Setup&lt;/h2&gt;
&lt;p&gt;The TravisCI account setup for TravisCI is very streamlined - instead of creating &lt;em&gt;another&lt;/em&gt; account for you to manage, it integrates in with GitHub, so this is the account you use to sign-up with. Head over to &lt;a href=&#34;https://travis-ci.com/&#34;&gt;https://travis-ci.com/&lt;/a&gt; and click on &lt;code&gt;Sign in with GitHub&lt;/code&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;travis-landing.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-3-1-deployment-methods-travisci//blog/who-goes-blogging-3-1-deployment-methods-travisci/travis-landing.png&#34;
    alt=&#34;Screenshot of Travis landing page&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;You can&amp;rsquo;t resist a big green button&amp;hellip;&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;GitHub will ask you if you &lt;em&gt;really&lt;/em&gt; want to share some of your GitHub data with Travis.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;travis-github-authorise.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-3-1-deployment-methods-travisci//blog/who-goes-blogging-3-1-deployment-methods-travisci/travis-github-authorise.png&#34;
    alt=&#34;Screenshot of GitHub authorising Travis&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Another green button? Why not!&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Once you&amp;rsquo;ve done that, you&amp;rsquo;ll be redirected to your new Travis Dashboard which&amp;hellip; is looking rather lonely &amp;#x1f626; - let&amp;rsquo;s fix that!&lt;/p&gt;
&lt;p&gt;All we&amp;rsquo;ve done so far is allowed Travis to reach GitHub for creating an account for us - we now need to activate GitHub Apps integration to permit it to read and write to our repositories. The &lt;a href=&#34;https://travis-ci.com/account/repositories&#34;&gt;https://travis-ci.com/account/repositories&lt;/a&gt; page is what you need for that - then click on the &lt;code&gt;Activate&lt;/code&gt; button.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;travis-github-apps-integration.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-3-1-deployment-methods-travisci//blog/who-goes-blogging-3-1-deployment-methods-travisci/travis-github-apps-integration.png&#34;
    alt=&#34;Screenshot of GitHub Apps Integration&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;&amp;hellip;More green buttons?!&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Now on the next screen you may or may not want the default selection of &lt;code&gt;All repositories&lt;/code&gt; which will give Travis read and write access to all your repos. I completely trust Travis if I were to select this, however it is a best practice to follow the &lt;a href=&#34;https://en.wikipedia.org/wiki/Principle_of_least_privilege&#34;&gt;&lt;em&gt;principle of least privilege&lt;/em&gt;&lt;/a&gt; (POLP); not just for users but for services too.&lt;/p&gt;
&lt;p&gt;For the scope of this effort we&amp;rsquo;re only wanting Travis to read and manipulate against two repos, &lt;code&gt;jdheyburn.co.uk&lt;/code&gt; and &lt;code&gt;jdheyburn.github.io&lt;/code&gt; - it also gives you a cleaner Travis dashboard too.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;travis-github-repos-selection.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-3-1-deployment-methods-travisci//blog/who-goes-blogging-3-1-deployment-methods-travisci/travis-github-repos-selection.png&#34;
    alt=&#34;Screenshot of GitHub Travis Repository Authorisation&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;back-to-github&#34;&gt;Back to GitHub&lt;/h3&gt;
&lt;p&gt;The next step is required to permit Travis to push the built project to our GitHub Pages repo. We need to generate a secret with the permissions that Travis requires and keep it aside for the Travis config file later.&lt;/p&gt;
&lt;p&gt;Navigate to the &lt;a href=&#34;https://github.com/settings/tokens&#34;&gt;GitHub Personal Access Tokens&lt;/a&gt; page and click on &lt;code&gt;Generate new token&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll come across a page asking for the name of the token being created. It doesn&amp;rsquo;t matter what you call it, but it may be useful to link it back to what it is being used for. You&amp;rsquo;re also going to want to select the &lt;code&gt;repo&lt;/code&gt; checkbox as done so below.&lt;/p&gt;
&lt;p&gt;After this you don&amp;rsquo;t need to provide any more permissions to the token. Scroll down to the end of the page and click &lt;code&gt;Generate token&lt;/code&gt;.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;travis-github-pat.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-3-1-deployment-methods-travisci//blog/who-goes-blogging-3-1-deployment-methods-travisci/travis-github-pat.png&#34;
    alt=&#34;Screenshot of GitHub Personal Access Token Creation - repos is checked&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;The token&amp;rsquo;s secret will display on the next screen. &lt;strong&gt;Make sure you copy it&lt;/strong&gt; and place it somewhere you can refer back to it later such as a text editor like Notepad - we&amp;rsquo;ll need it again in the next section.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;travis-github-pat-created.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-3-1-deployment-methods-travisci//blog/who-goes-blogging-3-1-deployment-methods-travisci/travis-github-pat-created.png&#34;
    alt=&#34;Screenshot of GitHub Personal Access Token Creation - token complete&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;travisci-configuration&#34;&gt;TravisCI Configuration&lt;/h2&gt;
&lt;p&gt;Once we have our Travis account set up, we need to add in a &lt;a href=&#34;https://docs.travis-ci.com/user/tutorial/&#34;&gt;configuration file&lt;/a&gt; that Travis will read from to determine what steps we&amp;rsquo;d like it to perform.&lt;/p&gt;
&lt;p&gt;In our source code repository (&lt;code&gt;jdheyburn.co.uk&lt;/code&gt; in my case) we want to create a file at the root directory and call it &lt;code&gt;.travis.yml&lt;/code&gt;. See below for an example of how I configured mine.&lt;/p&gt;
&lt;script src=&#34;https://gist.github.com/jdheyburn/073bd6d4cb9284774e7e7feee093d86f.js&#34;&gt;&lt;/script&gt;

&lt;p&gt;Let&amp;rsquo;s break it down section by section.&lt;/p&gt;
&lt;h3 id=&#34;build-environment&#34;&gt;Build Environment&lt;/h3&gt;
&lt;p&gt;These settings here all refer to the build environment that we&amp;rsquo;d like our project to build on.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;dist&lt;/span&gt;: xenial
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;git&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;depth&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;env&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;global&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    - HUGO_VERSION=&amp;#34;0.58.3&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;matrix&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;secure&lt;/span&gt;: REDACTED
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;dist&lt;/code&gt; specifies what platform Travis should build the project on. In this case &lt;code&gt;xenial&lt;/code&gt; refers to Ubuntu 16.04, which is a Linux distribution. There are &lt;a href=&#34;https://docs.travis-ci.com/user/reference/overview/&#34;&gt;several others&lt;/a&gt; to choose from and more likely than not you&amp;rsquo;ll want the platform to be Linux. However if you had a Windows application written in &lt;code&gt;.NET&lt;/code&gt; then you would likely want it built on a Windows Server since that is what supports it.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git.depth&lt;/code&gt; tells Travis how many commits of your project to check out. This is passed directory to the &lt;code&gt;git&lt;/code&gt; parameter &lt;code&gt;--depth&lt;/code&gt; (more info on that &lt;a href=&#34;https://git-scm.com/docs/git-clone&#34;&gt;here&lt;/a&gt;). For our use case we&amp;rsquo;re not interested in this option so we set it to &lt;code&gt;false&lt;/code&gt; to disable the flag being passed to &lt;code&gt;git&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;env.global&lt;/code&gt; allows us to define what variables should be set in the environment. This is done in the form of an array of strings in the format &lt;code&gt;key=value&lt;/code&gt;. So given the example, &lt;code&gt;HUGO_VERSION&lt;/code&gt; will be set to &lt;code&gt;0.58.3&lt;/code&gt;. We&amp;rsquo;ll come back to this later.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;env.matrix&lt;/code&gt; is the encrypted value that gets passed to the &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; environment variable which is used to allow Travis to commit the built project to our GitHub Pages repo.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You&amp;rsquo;re going to want to take the personal access token generated from GitHub in the earlier step and encrypt it using &lt;a href=&#34;https://docs.travis-ci.com/user/environment-variables#encrypting-environment-variables&#34;&gt;this method&lt;/a&gt;, then add it back to this setting&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;external-dependencies&#34;&gt;External Dependencies&lt;/h3&gt;
&lt;p&gt;Once the build environment is defined, we can tell Travis to pull in some additional dependencies or files required for our project. Now remember this is a &lt;code&gt;hugo&lt;/code&gt; project and we needed to install it on our local machines to run &lt;code&gt;deploy.sh&lt;/code&gt;, we need to do the same for Travis too.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;install&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- wget -q https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_Linux-64bit.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- tar xf hugo_extended_${HUGO_VERSION}_Linux-64bit.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- mv hugo ~/bin/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Remember we defined &lt;code&gt;HUGO_VERSION&lt;/code&gt; earlier? This is where it is called back again. In order, the steps we&amp;rsquo;re performing are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Downloading the archive containing the specified &lt;code&gt;hugo&lt;/code&gt; version&lt;/li&gt;
&lt;li&gt;Extracting all contents of the archive&lt;/li&gt;
&lt;li&gt;Moving the &lt;code&gt;hugo&lt;/code&gt; binary to the &lt;code&gt;~/bin/&lt;/code&gt; directory
&lt;ul&gt;
&lt;li&gt;This directory is on the build servers &lt;code&gt;PATH&lt;/code&gt;, which enables us to execute the binary using just the &lt;code&gt;hugo&lt;/code&gt; command later&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;previous-issues&#34;&gt;Previous Issues&lt;/h4&gt;
&lt;p&gt;In a previous version of &lt;code&gt;hugo&lt;/code&gt; I used, there was an additional dependency I needed to include. The &lt;code&gt;hugo-coder&lt;/code&gt; theme requires to be built with Hugo Extended since it requires Sass/SCSS support.&lt;/p&gt;
&lt;p&gt;For this particular Hugo Extended version, it required a library which was not included in the build server distribution in the past. On the plus side - Travis allows us to define additional build steps to ensure all required libraries are on the build server beforehand.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;before_install&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#6272a4&#34;&gt;# This workaround is required to avoid libstdc++ errors while running &amp;#34;extended&amp;#34; hugo with SASS support.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; - wget -q -O libstdc++6 http://security.ubuntu.com/ubuntu/pool/main/g/gcc-5/libstdc++6_5.4.0-6ubuntu1~16.04.10_amd64.deb
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; - sudo dpkg --force-all -i libstdc++6
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;However in later versions of Hugo (including the one I am using today) this dependency is no longer required, hence why it is commented out in &lt;code&gt;.travis.yml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You may not need this stage in your pipeline, I know my project no longer requires it. However this may come in handy later knowing that you have the option of specifying more pipeline steps if the build distribution you&amp;rsquo;re using requires some additional dependencies.&lt;/p&gt;
&lt;h3 id=&#34;build-script&#34;&gt;Build Script&lt;/h3&gt;
&lt;p&gt;Now onto the juicy stuff - building the project. This is pretty much where the &lt;code&gt;deploy.sh&lt;/code&gt; starts from, since on our local machines we already had the &lt;code&gt;hugo&lt;/code&gt; binary installed.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;script&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- hugo version
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- hugo --gc --minify
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;- cp CNAME public/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Not a lot is going on here, but to detail what each step is doing:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Print out the version of &lt;code&gt;hugo&lt;/code&gt; being used&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This is helpful for debugging the build. By printing out the version used we can try to replicate the bug locally for troubleshooting.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build the project&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We do this with two flags, &lt;code&gt;--gc&lt;/code&gt; and &lt;code&gt;--minify&lt;/code&gt;. These weren&amp;rsquo;t defined in the &lt;code&gt;deploy.sh&lt;/code&gt; script we used earlier so let&amp;rsquo;s cover them here.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--gc&lt;/code&gt; tells &lt;code&gt;hugo&lt;/code&gt; to cleanup some unused cache files after the build
&lt;ul&gt;
&lt;li&gt;This helps keep a tidy environment for the build server.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--minify&lt;/code&gt; performs &lt;a href=&#34;https://en.wikipedia.org/wiki/Minification_(programming)&#34;&gt;minification&lt;/a&gt; on your website to reduce the size of the generated content, enabling it to load faster on your users&amp;rsquo; devices
&lt;ul&gt;
&lt;li&gt;Coupled with a CDN like in the &lt;a href=&#34;https://jdheyburn.co.uk/posts/who-goes-blogging-2-custom-domain/#adding-our-cdn-layer&#34;&gt;previous part&lt;/a&gt;, your website will load almost instantly&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Copy the &lt;code&gt;CNAME&lt;/code&gt; file from the project root to the generated &lt;code&gt;public/&lt;/code&gt; directory.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This ensures that the custom domain name we set up in the previous part continues to be set in the generated website code that gets pushed to our GitHub Pages repo. &lt;a href=&#34;https://jdheyburn.co.uk/posts/who-goes-blogging-2-custom-domain/#building-into-our-deploy-script&#34;&gt;See here&lt;/a&gt; for a refresher.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This section is effectively the &amp;lsquo;CI&amp;rsquo; part of &amp;lsquo;CI/CD&amp;rsquo;. We could stop here and just use Travis as a build server to determine whether the website is able to be built successfully. Any errors in the pipeline would have resulted in a failed build.&lt;/p&gt;
&lt;p&gt;The next section details the Continuous Deployment, ensuring that on the successful build of a project we deploy it to our production environment.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;deploy-to-github-pages&#34;&gt;Deploy to GitHub Pages&lt;/h3&gt;
&lt;p&gt;Once the project has been built, we need to push it to the GitHub Pages repository. Travis has &lt;a href=&#34;https://docs.travis-ci.com/user/deployment/pages/&#34;&gt;good documentation&lt;/a&gt; on this. My setup follows the below:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-yml&#34; data-lang=&#34;yml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;deploy&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;provider&lt;/span&gt;: pages
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;skip-cleanup&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;github-token&lt;/span&gt;: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;$GITHUB_TOKEN&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;keep-history&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;local-dir&lt;/span&gt;: public
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;repo&lt;/span&gt;: jdheyburn/jdheyburn.github.io
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;target-branch&lt;/span&gt;: master
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#ff79c6&#34;&gt;verbose&lt;/span&gt;: &lt;span style=&#34;color:#ff79c6&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For the last time, let&amp;rsquo;s walk through what each of these is doing.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;provider&lt;/code&gt; tells Travis this is a GitHub Pages deployment&lt;/li&gt;
&lt;li&gt;&lt;code&gt;skip-cleanup&lt;/code&gt; set to &lt;code&gt;true&lt;/code&gt;, so that Travis does not delete the build before we&amp;rsquo;ve got the chance to upload it&lt;/li&gt;
&lt;li&gt;&lt;code&gt;github-token&lt;/code&gt; is set to the environment variable &lt;code&gt;$GITHUB_TOKEN&lt;/code&gt; which was set for us earlier on in the build environment.
&lt;ul&gt;
&lt;li&gt;This is passed to the provider so that it has valid credentials to push the code to the GitHub Pages repo.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;keep-history&lt;/code&gt; performs an incremental commit against the project
&lt;ul&gt;
&lt;li&gt;Setting it to &lt;code&gt;true&lt;/code&gt; allows us to view back the changes in the commit history such as &lt;a href=&#34;https://github.com/jdheyburn/jdheyburn.github.io/commit/419da0cc71415d0253996b823d4ccf6844db4042&#34;&gt;this one&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;local-dir&lt;/code&gt; specifies the directory that should be pushed to the target repo
&lt;ul&gt;
&lt;li&gt;We set it to &lt;code&gt;public&lt;/code&gt; because that is the name of the generated website directory from the previous &lt;code&gt;script&lt;/code&gt; step&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;repo&lt;/code&gt; is the target repo where we should be deploying to&lt;/li&gt;
&lt;li&gt;&lt;code&gt;target-branch&lt;/code&gt; is the branch that we want &lt;code&gt;local-dir&lt;/code&gt; to be pushed to
&lt;ul&gt;
&lt;li&gt;For our setup we are using &lt;code&gt;master&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;verbose&lt;/code&gt; specifies how much detail Travis will log about its deploy activities&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That concludes the configuration section. Remember to change it or add in some other functionality needed for your project and save it to &lt;code&gt;.travis.yml&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;bringing-it-all-together&#34;&gt;Bringing It All Together&lt;/h2&gt;
&lt;p&gt;Now you&amp;rsquo;ve completed your config file, let&amp;rsquo;s check it all in to GitHub.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git add --all
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git commit -m &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;Adding CI&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git push
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Travis will automatically detect that a new change has been made against your repo. And with the inclusion of &lt;code&gt;.travis.yml&lt;/code&gt;, it will now work against that file.&lt;/p&gt;
&lt;p&gt;You can view the status of your repository&amp;rsquo;s build by navigating to it from the &lt;a href=&#34;https://travis-ci.com/dashboard&#34;&gt;dashboard&lt;/a&gt; and clicking on it.&lt;/p&gt;
&lt;p&gt;If any of the commands in the &lt;code&gt;script&lt;/code&gt; section of the config return a &lt;a href=&#34;https://en.wikipedia.org/wiki/Exit_status#Shell_and_scripts&#34;&gt;nonzero status code&lt;/a&gt;, the build will fail. If this happens then have a look at a build log and investigate the issue. More likely than not someone else has encountered the problem before, so Google is your gatekeeper to solutions!&lt;/p&gt;
&lt;p&gt;Once you&amp;rsquo;ve got a successful build, that means your website has been deployed to the GitHub Pages repo and will be available on the web to view. In a browser you can now view your changes on the website.&lt;/p&gt;
&lt;p&gt;If they aren&amp;rsquo;t there then your browser is most likely caching an older version. By default, websites served by GitHub Pages have a browser cache TTL &lt;a href=&#34;https://webapps.stackexchange.com/questions/119286/caching-assets-in-website-served-from-github-pages&#34;&gt;set to 10 minutes&lt;/a&gt;. So you can either wait 10 minutes, or clear your browser cache!&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;You&amp;rsquo;re on the way to DevOps masterclass. As mentioned the next post will focus on migrating to GitHub Actions so if you&amp;rsquo;re happy with TravisCI then there&amp;rsquo;s no more you need to do! Go ahead and add other repositories you may have to Travis and build up multiple pipelines &amp;#x1f64c;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>On Becoming An Open Source Software Contributor</title><enclosure url="https://jdheyburn.co.uk/blog/on-becoming-an-open-source-software-contributor/terraform-aws.png" type="image/jpg"></enclosure>
      
      <link>https://jdheyburn.co.uk/blog/on-becoming-an-open-source-software-contributor/</link>
      <pubDate>Sun, 26 Jan 2020 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/on-becoming-an-open-source-software-contributor/</guid>-->
      <description>&lt;p&gt;This is going to be a relatively shorter post that&amp;rsquo;ll be in two parts since it&amp;rsquo;s more of a self-promotion&amp;hellip; But as of this Friday just gone, I finally had a &lt;a href=&#34;https://github.com/terraform-providers/terraform-provider-aws/pull/11388&#34;&gt;pull request&lt;/a&gt; approved to be merged to &lt;a href=&#34;https://github.com/terraform-providers/terraform-provider-aws&#34;&gt;terraform&amp;ndash;provider-aws&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Okay so it&amp;rsquo;s only a small piece of documentation, I&amp;rsquo;ve actually got another larger PR waiting to be merged in to the same project with new added functionality - and a bug fix in the pipeline too - which I hope to talk about in the future at some point. Although I can&amp;rsquo;t say I&amp;rsquo;m surprised this one got approved first - since it is by far the simplest change, and increased documentation is rarely a bad thing to add to a project.&lt;/p&gt;
&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;To talk a bit more about the context behind it, &lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/sysman-patch-baselines.html&#34;&gt;AWS SSM Patch Baselines&lt;/a&gt; is used to define the rules of what patches should be installed on your EC2 instances.&lt;/p&gt;
&lt;p&gt;For patch baselines tied to Linux instances, you can only define what patches should be installed &lt;em&gt;at the OS level&lt;/em&gt;. However for Windows instances - in addition to defining OS level patches - you can also define &lt;em&gt;Microsoft application patches&lt;/em&gt; to be installed too!&lt;/p&gt;
&lt;p&gt;While this can be done in the AWS Console easily enough, it is very common to define these resources as infrastructure-as-code (IaC), for which there are several to choose from. For my team we use &lt;a href=&#34;https://www.terraform.io&#34;&gt;Terraform&lt;/a&gt; - and we wanted a way to be able to replicate the patch baselines we could create in the console into Terraform code too.&lt;/p&gt;
&lt;p&gt;After some digging I came across &lt;a href=&#34;https://github.com/terraform-providers/terraform-provider-aws/issues/8942&#34;&gt;this issue&lt;/a&gt; on &lt;code&gt;terraform-provider-aws&lt;/code&gt; that indicated the functionality was missing from the provider. When in fact the support had been there all along, since the AWS provider is merely a wrapper around &lt;a href=&#34;https://github.com/aws/aws-sdk-go&#34;&gt;aws-sdk-go&lt;/a&gt; which calls the necessary APIs to create the resources. Where Terraform comes in is generating the dependency graphs for said resources and creating them in order asynchronously.&lt;/p&gt;
&lt;p&gt;As you can see from the ticket, I was suggested to improve on the documentation - which leads us to where we are now. It is still pending release, however when it is out I&amp;rsquo;ll link it below.&lt;/p&gt;
&lt;p&gt;In the meantime, let me talk about the importance of documentation in the next section.&lt;/p&gt;
&lt;h2 id=&#34;documentation-case-study&#34;&gt;Documentation Case Study&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m a bit of a sucker when it comes to documentation. Yes everyone talks about how important it is to include it not just open source projects but also in your day-to-day job. A whole lot of the time there is far too much knowledge-retention and not enough knowledge-sharing.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This reminds me of the character Brent from the novel &lt;a href=&#34;https://itrevolution.com/book/the-phoenix-project/&#34;&gt;The Phoenix Project&lt;/a&gt; - who doesn&amp;rsquo;t share a lot of the domain knowledge he experiences. FWIW I highly recommend the book!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In fact, documentation isn&amp;rsquo;t always necessarily for sharing knowledge with others, it is also about sharing knowledge &lt;em&gt;with yourself&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;What do I mean by that?&lt;/p&gt;
&lt;p&gt;Documenting down architecture, processes, runbooks, etc., can help to give you clarity into the subject you are writing about. Once you&amp;rsquo;ve extracted all the details and are able to view them at a high-level, you&amp;rsquo;re able to iron out any creases and make improvements from there.&lt;/p&gt;
&lt;p&gt;However, more importantly, and one that is most beneficial to me is that&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You will forget that shit and will need to reference it back again!&lt;/strong&gt;&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;elephant-cartoon.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/on-becoming-an-open-source-software-contributor//blog/on-becoming-an-open-source-software-contributor/elephant-cartoon.png&#34;
    alt=&#34;Cartoon elephant&#34; width=&#34;400x&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Not everyone can have the memory of an elephant&amp;hellip;&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;We are only human right? There is no way we can remember what most of us did a few weeks ago let alone a project from 6+ months ago.&lt;/p&gt;
&lt;h2 id=&#34;example&#34;&gt;Example&lt;/h2&gt;
&lt;p&gt;As an example of this a few years ago at a previous employer, I worked on a project to build a number of microservices that acted as an onboarding layer to integrate between internal corporate services (e.g. user entitlements, processes that user had access to, etc.) and &lt;a href=&#34;https://www.appdynamics.com/&#34;&gt;AppDynamics&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I laid down all the flows, the design decisions, the architecture, the models, etc., on my documentation tool of choice (currently it&amp;rsquo;s &lt;a href=&#34;https://www.atlassian.com/software/confluence&#34;&gt;Confluence&lt;/a&gt;) as a place of reference should either my team, or myself need to refer back to it later on.&lt;/p&gt;
&lt;p&gt;A few months later I took an internal move to another team within the same firm. In this new team I picked up a whole load of new information, of which there was no way I was able to retain everything all at once.&lt;/p&gt;
&lt;p&gt;Later on when my former teammates wanted to add an additional feature to the previous project, or wanted to troubleshoot a particular bug and wanted some context behind why things were operating the way that they were, they would reach out to me.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;At that point I directed myself and my former teammate to the documentation page for a refresher, and to let them know where they can go to solve the issue without me.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Without that documentation I guarantee that I would&amp;rsquo;ve spent a whole load more time on trying to recollect my thoughts, only to waste the time of my teammate. If there was an ongoing incident too, then the time to resolution would be hastened too.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Don&amp;rsquo;t be that person to retain knowledge, it&amp;rsquo;ll end up coming round and biting you in the arse.&lt;/p&gt;
&lt;p&gt;After you&amp;rsquo;re done reading this, make a mental note to write down any process or piece of information you come across. Even if you&amp;rsquo;re doing a task or exercise for the first time, document your steps to how you got there - you&amp;rsquo;ll certainly need it in order to replicate the steps in future.&lt;/p&gt;
&lt;p&gt;Make a small start now that will make a big difference later on.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Who Goes Blogging 2: Custom Domain</title><enclosure url="https://jdheyburn.co.uk/blog/who-goes-blogging-2-custom-domain/namecheap_landing.png" type="image/jpg"></enclosure>
      
      <link>https://jdheyburn.co.uk/blog/who-goes-blogging-2-custom-domain/</link>
      <pubDate>Thu, 12 Dec 2019 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/who-goes-blogging-2-custom-domain/</guid>-->
      <description>&lt;p&gt;In the &lt;a href=&#34;https://jdheyburn.co.uk/posts/who-goes-blogging-1-getting-started/&#34;&gt;previous post&lt;/a&gt;, we got ourselves up and running with a website generated by Hugo, deployed to GitHub, and hosted by GitHub Pages.&lt;/p&gt;
&lt;p&gt;Now, we&amp;rsquo;re going to add a custom domain to our website so that we hide the &lt;code&gt;&amp;lt;username&amp;gt;.github.io&lt;/code&gt; domain that GitHub Pages is kindly hosting for us for free.&lt;/p&gt;
&lt;p&gt;At the same time, we&amp;rsquo;re going to make our website blazingly fast for users by adding a caching layer with a content distribution network (CDN).&lt;/p&gt;
&lt;p&gt;Lastly, I&amp;rsquo;m going to throw in a bonus guide on how to redirect from multiple top-level domains (TLDs) to one (e.g. &lt;code&gt;&amp;lt;your-domain&amp;gt;.com&lt;/code&gt; redirects to &lt;code&gt;&amp;lt;your-domain&amp;gt;.co.uk&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id=&#34;pre-requisites&#34;&gt;Pre-requisites&lt;/h2&gt;
&lt;p&gt;There&amp;rsquo;s not much more to add from the last post. I talk a lot about domains and domain name system (DNS), which is the address-finder of the Internet, and an entirely huge beast in its own right.&lt;/p&gt;
&lt;p&gt;Again - I won&amp;rsquo;t try to replicate already great guides out there on the topic. So if you&amp;rsquo;d like to find out more, see below for some helpful guides.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.digitalocean.com/community/tutorials/an-introduction-to-dns-terminology-components-and-concepts&#34;&gt;https://www.digitalocean.com/community/tutorials/an-introduction-to-dns-terminology-components-and-concepts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://opensource.com/article/17/4/introduction-domain-name-system-dns&#34;&gt;https://opensource.com/article/17/4/introduction-domain-name-system-dns&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;applying-a-custom-domain-to-github-pages&#34;&gt;Applying a Custom Domain to GitHub Pages&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s add a degree of professionalism to our site by having a custom domain apply to it. You&amp;rsquo;ll need to make sure you own a domain first before you go ahead, so have a look at a few providers and see which works best for you &lt;a href=&#34;https://www.techradar.com/uk/news/best-domain-registrars-in-2019&#34;&gt;from a comparison list&lt;/a&gt;. I bought mine from &lt;a href=&#34;https://www.namecheap.com/&#34;&gt;namecheap&lt;/a&gt; just because of the price and WhoisGuard features. There may be other providers that have the same features, so make sure to make your own comparison!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In the case of &lt;code&gt;.co.uk&lt;/code&gt; domains, because it is a UK domain that resides in the EU (for now), the WhoIS lookup is disabled by default - which is a huge win for privacy. WhoisGuard is available for non-EU domains and I highly recommend it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;GitHub has a &lt;a href=&#34;https://help.github.com/en/github/working-with-github-pages/configuring-a-custom-domain-for-your-github-pages-site&#34;&gt;series of documentation&lt;/a&gt; on applying a custom domain to GitHub Pages in much greater detail than what I am about to write out, should you wish to find out more information.&lt;/p&gt;
&lt;h2 id=&#34;acquire-a-domain&#34;&gt;Acquire a Domain&lt;/h2&gt;
&lt;p&gt;The rest of the post will depict a lot of Namecheap semantics, since that is the registrar I have access to. You can choose to follow the guide alongside a different registrar if you wish, at a high-level they will be pretty similar. For now, let&amp;rsquo;s move on ahead with Namecheap and navigate through to the &lt;a href=&#34;https://www.namecheap.com/&#34;&gt;domain purchase page&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;namecheap_landing.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-2-custom-domain//blog/who-goes-blogging-2-custom-domain/namecheap_landing.png&#34;
    alt=&#34;Screenshot depicting the namecheap domain landing page&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;The hardest part is deciding on the domain name&amp;hellip;&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;a href=&#34;namecheap-domain-purchase.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-2-custom-domain//blog/who-goes-blogging-2-custom-domain/namecheap-domain-purchase.png&#34;
    alt=&#34;Screenshot depicting the namecheap domain purchase page&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Once you&amp;rsquo;ve set up an account and purchased your domain, your accounts domain landing page will look something like the below.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;namecheap-domain-acquired.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-2-custom-domain//blog/who-goes-blogging-2-custom-domain/namecheap-domain-acquired.png&#34;
    alt=&#34;Screenshot depicting the namecheap domain landing page&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Make sure you have auto-renew selected, otherwise you can kiss that domain goodbye when it expires!&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;When you purchase a domain from Namecheap, by default it will be pointing to their own domain name service (DNS) nameservers, as you can see from the picture above.&lt;/p&gt;
&lt;p&gt;This means when we type in our new domain into a browser, it will contact Namecheap for the IP address for that record.&lt;/p&gt;
&lt;p&gt;Currently Namecheap is none the wiser about these records, which isn&amp;rsquo;t very exciting. Let&amp;rsquo;s move on to adding the CDN for the website.&lt;/p&gt;
&lt;h2 id=&#34;adding-our-cdn-layer&#34;&gt;Adding Our CDN Layer&lt;/h2&gt;
&lt;p&gt;As discussed in a &lt;a href=&#34;https://jdheyburn.co.uk/posts/who-goes-blogging-0-applying-cartography/#supercharge-your-delivery&#34;&gt;previous post&lt;/a&gt;, a CDN can provide us with many benefits. Go check out the page for a refresher of what those are and for why I selected Cloudflare. You can use whichever you like, however the remainder of this guide will focus on Cloudflare in particular - the concepts can still be applied at a high level to other CDNs.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Like with any introduction of an architectural component, a CDN has some drawbacks, such as making your service now dependent on a third party for which you have no control over. Namely Cloudflare in particular has had some high profile outages of recent date, but has been extremely reliable in my previous experiences with them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;You can go &lt;a href=&#34;https://blog.webnames.ca/advantages-and-disadvantages-of-a-content-delivery-network/&#34;&gt;here&lt;/a&gt; for a list of pros and cons of CDNs. Given this information I believe you can make your own mind up on what is best for yourself. For me, I the benefits far outweigh the downsides.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;cloudflare-our-domain&#34;&gt;Cloudflare our Domain&lt;/h3&gt;
&lt;p&gt;Create an account with &lt;a href=&#34;https://www.cloudflare.com/&#34;&gt;Cloudflare&lt;/a&gt; if you haven&amp;rsquo;t done so already. Once done you&amp;rsquo;ll need to click &lt;strong&gt;Add Site&lt;/strong&gt; at the top of the browser dashboard. Enter your newly purchased domain from the previous section.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;cloudflare-add-domain.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-2-custom-domain//blog/who-goes-blogging-2-custom-domain/cloudflare-add-domain.png&#34;
    alt=&#34;Screenshot depicting the Cloudflare add domain page&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Enter your newly purchased domain from the previous section&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Go ahead now and select the free plan, if you want to &lt;a href=&#34;https://www.cloudflare.com/en-gb/plans/&#34;&gt;go more advanced&lt;/a&gt; then you can do so.&lt;/p&gt;
&lt;p&gt;Once that&amp;rsquo;s created you&amp;rsquo;ll see Cloudflare scan the DNS records for this domain you&amp;rsquo;ve added - for now let&amp;rsquo;s navigate back to the &lt;a href=&#34;https://dash.cloudflare.com/&#34;&gt;Cloudflare Dashboard&lt;/a&gt; and selecting the domain. You&amp;rsquo;ll be presented with a page similar to below, minus all the activity!&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;cloudflare-domain-landing.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-2-custom-domain//blog/who-goes-blogging-2-custom-domain/cloudflare-domain-landing.png&#34;
    alt=&#34;Screenshot depicting the Cloudflare domain home page&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Name me a more iconic duo than numbers and graphs. I&amp;rsquo;ll wait&amp;hellip;&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;update-cloudflare-dns-records&#34;&gt;Update Cloudflare DNS Records&lt;/h3&gt;
&lt;p&gt;In order for Cloudflare to provide its benefits, it acts as the DNS server for your domain. This means that it will forward requests of our website to the IP addresses where our website is being hosted. It&amp;rsquo;s place in the topology is like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1. Domain registrar ---&amp;gt; 2. DNS server and CDN provider ---&amp;gt; 3. Web server location
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Translated to our architecture:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;1. Namecheap ---&amp;gt; 2. Cloudflare ---&amp;gt; 3. GitHub Pages
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;What we need to do is instruct Cloudflare where to direct clients of the website. There are several ways of doing this, whether you want your website to be available at &lt;code&gt;www.&amp;lt;your-domain&amp;gt;.co.uk&lt;/code&gt; (CNAME record), or &lt;code&gt;&amp;lt;your-domain&amp;gt;.co.uk&lt;/code&gt; (APEX (A) record). Both of which are &lt;a href=&#34;https://help.github.com/en/github/working-with-github-pages/managing-a-custom-domain-for-your-github-pages-site&#34;&gt;well documented by GitHub&lt;/a&gt; already. For the purpose of this post, I&amp;rsquo;ll guide you on my set up which is an ALIAS record.&lt;/p&gt;
&lt;p&gt;Back to Cloudflare, you&amp;rsquo;ll need to load up the dashboard for your domain and navigate to the DNS icon in the taskbar at the top. Clicking on &lt;strong&gt;Add record&lt;/strong&gt; will allow you to add the A records that point to GitHub Pages&amp;rsquo;s IP addresses. As of writing (and &lt;a href=&#34;https://help.github.com/en/github/working-with-github-pages/managing-a-custom-domain-for-your-github-pages-site#configuring-an-apex-domain&#34;&gt;documented&lt;/a&gt;) they are:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;185.199.108.153
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;185.199.109.153
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;185.199.110.153
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;185.199.111.153
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s add these to the Cloudflare DNS page below.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;cloudflare-dns-record.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-2-custom-domain//blog/who-goes-blogging-2-custom-domain/cloudflare-dns-record.png&#34;
    alt=&#34;Screenshot depicting adding DNS records to Cloudflare&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Add in the GitHub IP addresses one by one&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Once done - your records will look something like this:&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;cloudflare-completed-records.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-2-custom-domain//blog/who-goes-blogging-2-custom-domain/cloudflare-completed-records.png&#34;
    alt=&#34;Screenshot depicting completed DNS management&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;You&amp;rsquo;ll notice an additional CNAME record at the bottom for &lt;code&gt;www&lt;/code&gt;. This will redirect any requests made to &lt;code&gt;www.jdheyburn.co.uk&lt;/code&gt; to &lt;code&gt;jdheyburn.co.uk&lt;/code&gt;. This could be something you&amp;rsquo;d want to replicate too if you wish.&lt;/p&gt;
&lt;p&gt;One thing to note down before we move on is to capture the Cloudflare DNS Nameservers that have been assigned to our domain. You can find these on the same DNS management page we are on, but by just scrolling down we can see these nameservers.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;cloudflare-nameservers.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-2-custom-domain//blog/who-goes-blogging-2-custom-domain/cloudflare-nameservers.png&#34;
    alt=&#34;Screenshot depicting completed Cloudflare DNS nameservers&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Make a note of these nameservers for your domain&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;direct-namecheap-to-cloudflare&#34;&gt;Direct Namecheap to Cloudflare&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://jdheyburn.co.uk/posts/who-goes-blogging-2-custom-domain/#acquire-a-domain&#34;&gt;Earlier in this post&lt;/a&gt;, I mentioned that a newly created Namecheap domain will default to their own DNS nameservers. We want to change this to Cloudflares DNS nameservers from which we configured our DNS records.&lt;/p&gt;
&lt;p&gt;Navigate to the &lt;a href=&#34;https://ap.www.namecheap.com/domains/list/&#34;&gt;Namecheap management page&lt;/a&gt; for your domain and enter the Cloudflare nameservers once you have selected &lt;strong&gt;Custom DNS&lt;/strong&gt;.&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;namecheap-nameservers.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-2-custom-domain//blog/who-goes-blogging-2-custom-domain/namecheap-nameservers.png&#34;
    alt=&#34;Screenshot depicting completed nameservers pointing to Cloudflare&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Add in your Cloudflare domains from previously&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Once this is done you may need to wait a while for the DNS updates to propagate throughout the world. While we&amp;rsquo;re waiting for that, there&amp;rsquo;s one final piece of the puzzle which can keep us busy.&lt;/p&gt;
&lt;h3 id=&#34;github-pages-configuration&#34;&gt;GitHub Pages Configuration&lt;/h3&gt;
&lt;p&gt;The last place to configure is GitHub Pages. Currently it is hosting at our &lt;code&gt;.github.io&lt;/code&gt; domain, but we need to instruct it to redirect to our custom domain. There are two methods for doing this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Configure the repository settings&lt;/li&gt;
&lt;li&gt;Use a &lt;code&gt;CNAME&lt;/code&gt; file in your repository&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&#34;configure-the-repository-settings&#34;&gt;Configure the repository settings&lt;/h4&gt;
&lt;p&gt;This is the quicker of the two solutions, so I advise to follow this step to understand if you have everything in place correctly. Once done then you can lock-in your changes with step 2 above.&lt;/p&gt;
&lt;p&gt;For this step, you need to navigate to the settings pages for your &lt;code&gt;.github.io&lt;/code&gt; repo containing your rendered code.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;https://github.com/&amp;lt;USERNAME&amp;gt;/&amp;lt;USERNAME&amp;gt;.github.io/settings
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now scroll down, keep going until you hit the &lt;strong&gt;GitHub Pages&lt;/strong&gt; heading. Here you will see a form for entering a custom domain; do the honours and enter it in like below.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;github_pages_setup.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-2-custom-domain//blog/who-goes-blogging-2-custom-domain/github_pages_setup.png&#34;
    alt=&#34;Screenshot depicting GitHub Pages form for specifying a custom domain&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Ignore my already published domain&amp;hellip;&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Now again wait for DNS to propagate across the world. When GitHub is happy with the changes then you will see the green banner similar to the one in the screenshot above. This means everything is being served up! Why not give it a try ourselves? Head to your domain now and see if everything is working!&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;jdheyburn_co_uk_custom_domain.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-2-custom-domain//blog/who-goes-blogging-2-custom-domain/jdheyburn_co_uk_custom_domain.png&#34;
    alt=&#34;Screenshot depicting completed custom domain&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;You should end up with something like this!&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 id=&#34;solidying-our-changes-with-a-cname-file&#34;&gt;Solidying our changes with a CNAME file&lt;/h4&gt;
&lt;p&gt;We can use the above method to quickly try using GitHub repository settings to see if everything is working, however I&amp;rsquo;m a big fan of setting changes in code (Infrastructure-as-code anyone?). GitHub supports another method which is to use a file named &lt;code&gt;CNAME&lt;/code&gt; in our generated &lt;code&gt;.github.io&lt;/code&gt; repo that contains the domain name we wish to use.&lt;/p&gt;
&lt;p&gt;In my case, I would have the following&amp;hellip;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# CNAME&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;jdheyburn.co.uk
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This then tells a repo that is enabled for GitHub Pages to use the domain in this file as our custom domain, effectively producing the steps in the previous section. Neato.&lt;/p&gt;
&lt;p&gt;The change to implement this is fairly easy. I have to admit I picked it up from somewhere but I don&amp;rsquo;t have the source to reference it to.&lt;/p&gt;
&lt;p&gt;So back in your &lt;code&gt;blog-source&lt;/code&gt; repo, you want to execute the below, replacing the template with your domain.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;lt;DOMAIN-NAME&amp;gt;&amp;#34;&lt;/span&gt; &amp;gt; CNAME
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;building-into-our-deploy-script&#34;&gt;Building into our deploy script&lt;/h4&gt;
&lt;p&gt;Once that is done, you will need to modify your &lt;code&gt;deploy.sh&lt;/code&gt; script to copy over the file into your generated Hugo site, because Hugo won&amp;rsquo;t do it for you! Don&amp;rsquo;t have the deploy script or need a refresher? Head back to the &lt;a href=&#34;https://jdheyburn.co.uk/posts/who-goes-blogging-1-getting-started/#bash-script-deploying&#34;&gt;previous post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You will want to copy it after Hugo has done its thing, take a look at the Gist below - line 9 is your friend.&lt;/p&gt;
&lt;script src=&#34;https://gist.github.com/jdheyburn/e84bab9176dc1753416637324a04603d.js&#34;&gt;&lt;/script&gt;

&lt;p&gt;What this is doing is taking the &lt;code&gt;CNAME&lt;/code&gt; file that already exists in our &lt;code&gt;blog-source&lt;/code&gt; repo and moving it to the generated &lt;code&gt;public/&lt;/code&gt; directory which Hugo created for us. It is then this &lt;code&gt;public/&lt;/code&gt; directory that gets committed to the GitHub Pages repo.&lt;/p&gt;
&lt;p&gt;Then that&amp;rsquo;s it! Take a look at my &lt;a href=&#34;https://github.com/jdheyburn/jdheyburn.github.io&#34;&gt;finished repo&lt;/a&gt; and you&amp;rsquo;ll see where &lt;code&gt;CNAME&lt;/code&gt; fits in.&lt;/p&gt;
&lt;h2 id=&#34;bonus-tld-redirection&#34;&gt;Bonus: TLD Redirection&lt;/h2&gt;
&lt;p&gt;Let me tell you a story. Your website is up and operational. You&amp;rsquo;re super proud of it, and you give yourself a round of applause &amp;#x1f44f;&lt;/p&gt;
&lt;p&gt;But you don&amp;rsquo;t want to be the only person looking at it, you want the whole world to! You tell your parents, your significant other, the dog off the street - they all remember the name of your website, but &lt;em&gt;was it at &lt;code&gt;.com&lt;/code&gt; or &lt;code&gt;.&amp;lt;insert snazzy TLD here&amp;gt;&lt;/code&gt;&lt;/em&gt;?&lt;/p&gt;
&lt;p&gt;Of course, you domain isn&amp;rsquo;t at &lt;code&gt;.com&lt;/code&gt;, that&amp;rsquo;s boring as hell! You just forked out $50 on a &lt;code&gt;.dev&lt;/code&gt; TLD, and no one will see it!&lt;/p&gt;
&lt;p&gt;Luckily there is a way&amp;hellip;&lt;/p&gt;
&lt;p&gt;You can buy additional domains at different TLDs and have them redirect to your &lt;em&gt;one-domain-to-rule-them-all&lt;/em&gt; with little to no hassle! There is the cost of purchasing the domain and renewing it year after year, but with that being ~£10 or so per year - I&amp;rsquo;d call that a good insurance policy to ensure people land at your website!&lt;/p&gt;
&lt;p&gt;In my case, I purchased &lt;code&gt;jdheyburn.com&lt;/code&gt; and had it redirect to &lt;code&gt;jdheyburn.co.uk&lt;/code&gt; - why not give it a try: &lt;a href=&#34;https://jdheyburn.com&#34;&gt;https://jdheyburn.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The steps are already defined in this post. For a breakdown of what they are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-2-custom-domain/#acquire-a-domain&#34;&gt;Purchase a domain&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-2-custom-domain/#cloudflare-our-domain&#34;&gt;Create a new website in Cloudflare&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-2-custom-domain/#update-cloudflare-dns-records&#34;&gt;Create a DNS A record to redirect to your correct domain&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-2-custom-domain/#direct-namecheap-to-cloudflare&#34;&gt;Configure Namecheap to use Cloudflare&amp;rsquo;s nameservers&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Steps 1 and 2 are pretty easy to perform yourselves - so for this exercise I&amp;rsquo;ll join in at step 3.&lt;/p&gt;
&lt;h2 id=&#34;cloudflare-domain-redirection&#34;&gt;Cloudflare Domain Redirection&lt;/h2&gt;
&lt;p&gt;Assuming that you&amp;rsquo;ve completed steps 1 and 2, we&amp;rsquo;ll need to configure the redirection - but we don&amp;rsquo;t do this via inserting a DNS record like we did previously, Cloudflare has a feature that handles that for us called &lt;strong&gt;Page Rules&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;On your Cloudflare website landing page, navigate to &lt;strong&gt;Page Rules&lt;/strong&gt; in the toolbar at the top and then &lt;strong&gt;Create Page Rule&lt;/strong&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;cloudflare_page_rules.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-2-custom-domain//blog/who-goes-blogging-2-custom-domain/cloudflare_page_rules.png&#34;
    alt=&#34;Screenshot depicting how to access the Create Page Rule feature&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;In the next screen we&amp;rsquo;re going to define the rule. Cloudflare have &lt;a href=&#34;https://support.cloudflare.com/hc/en-us/categories/200276257-Page-Rules&#34;&gt;documentation on Page Rules&lt;/a&gt;, and even more &lt;a href=&#34;https://support.cloudflare.com/hc/en-us/articles/200172286-Configuring-URL-forwarding-or-redirects-with-Cloudflare-Page-Rules&#34;&gt;specifically on redirection&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;From this screen you will want something that appears as below.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;cloudflare_new_page_rule.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-2-custom-domain//blog/who-goes-blogging-2-custom-domain/cloudflare_new_page_rule.png&#34;
    alt=&#34;Screenshot depicting how to populate the Create Page Rule form&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Let&amp;rsquo;s break down what&amp;rsquo;s happening here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We define a pattern of &lt;code&gt;*jdheyburn.com/*&lt;/code&gt;, indicating that the rule is active should a URL be queried to Cloudflare&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;This pattern will match any request at this domain&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;Next comes the rule action, which is:&lt;/li&gt;
&lt;li&gt;Set this URL as a forwarding URL - to reply back to the client with &lt;code&gt;301 Permanent Redirect&lt;/code&gt; status code&lt;/li&gt;
&lt;li&gt;With the forwarded URL being &lt;code&gt;https://jdheyburn.co.uk/$2&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The &lt;code&gt;/$2&lt;/code&gt; at the end of the rule is crucial here. This will carry across any URL path parameters or query parameters to the redirected URL. In fact &lt;code&gt;$2&lt;/code&gt; refers to anything that matches the second asterisk (&lt;code&gt;*&lt;/code&gt;) in the rule pattern (&lt;code&gt;*jdheyburn.com/*&lt;/code&gt;). So &lt;code&gt;https://jdheyburn.com/contact&lt;/code&gt; will redirect to &lt;code&gt;https://jdheyburn.co.uk/contact&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Having the forwarded URL set to the &lt;code&gt;https://&lt;/code&gt; scheme will ensure that your users will receive your website encrypted, safe from man-in-the-middle attacks.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Lastly, click on &lt;strong&gt;Save and Deploy&lt;/strong&gt; to finalise your changes; you&amp;rsquo;ll have a view such as below.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;cloudflare_completed_page_rule.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-2-custom-domain//blog/who-goes-blogging-2-custom-domain/cloudflare_completed_page_rule.png&#34;
    alt=&#34;Screenshot depicting the newly created page rule&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;That&amp;rsquo;s all you need - once again you will have to wait for DNS replication to trickle down. You can repeat this over again for other domains you may have.&lt;/p&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;That it for this part, we covered quite a lot of things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Purchasing a custom domain&lt;/li&gt;
&lt;li&gt;Applying a CDN cache layer&lt;/li&gt;
&lt;li&gt;HTTPS redirection&lt;/li&gt;
&lt;li&gt;Redirect multiple domains to one location&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next up I&amp;rsquo;ll document the various deployment methods I have used for the website.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Who Goes Blogging 1: Getting Started</title><enclosure url="https://jdheyburn.co.uk/blog/who-goes-blogging-1-getting-started/local_example_site.jpg" type="image/jpg"></enclosure>
      
      <link>https://jdheyburn.co.uk/blog/who-goes-blogging-1-getting-started/</link>
      <pubDate>Sat, 09 Nov 2019 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/who-goes-blogging-1-getting-started/</guid>-->
      <description>&lt;p&gt;I&amp;rsquo;ve done a lot of talking about how this website is currently, but let&amp;rsquo;s talk about how you can get set up with the same as I have done here. Let&amp;rsquo;s recap exactly what that is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A GitHub repo with the site source code&lt;/li&gt;
&lt;li&gt;Another GitHub project with the rendered site&lt;/li&gt;
&lt;li&gt;Hosted on GitHub Pages&lt;/li&gt;
&lt;li&gt;Fronted by a custom domain&lt;/li&gt;
&lt;li&gt;Globally cached by a CDN&lt;/li&gt;
&lt;li&gt;Redirecting multiple domains&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We&amp;rsquo;ll cover the first three points in this post, with the remainder to come in a follow up post.&lt;/p&gt;
&lt;h2 id=&#34;pre-requisites--assumptions&#34;&gt;Pre-requisites &amp;amp; Assumptions&lt;/h2&gt;
&lt;p&gt;These series of posts is aimed at between the newcomer, to intermediate programmer. There may be some concepts which I won&amp;rsquo;t be covering to due brevity, and because there are several guides out there who have explained these concepts better than I could.&lt;/p&gt;
&lt;p&gt;Having said that, I assume you are comfortable with the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Navigating your way through a terminal&lt;/li&gt;
&lt;li&gt;Happy with the basic set of &lt;code&gt;git&lt;/code&gt; commands&lt;/li&gt;
&lt;li&gt;Understand how website requests are made (though this isn&amp;rsquo;t crucial)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;hugo-static-site-builder&#34;&gt;Hugo Static Site Builder&lt;/h2&gt;
&lt;p&gt;There are many website building platforms out there for your static sites. There is no one platform to rule them all, you should decide on which one works best for you. For myself, I wanted to explore further into the realm of Golang as a learning opportunity. I&amp;rsquo;ve built websites from scratch before from basic HTML, CSS, and JavaScript all the way to using frameworks such as React and Angular.&lt;/p&gt;
&lt;p&gt;However while using these frameworks provide you with the highest flexibility in terms of customisability, they can take a while to get something out there and hosted, when really you just want something to generate the HTML and CSS for you and allow you to focus on the content.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s where static site frameworks come in. They usually have some opinionated folder structure and some nuances on how things in a website should be, but comply by these rules and the website will be generated for you! Once such example is &lt;a href=&#34;https://gohugo.io/&#34;&gt;Hugo&lt;/a&gt; - which is what this website is built on; it&amp;rsquo;s opensource and written in Golang, you can even check out the &lt;a href=&#34;https://github.com/gohugoio/hugo&#34;&gt;source code&lt;/a&gt; to see how it works!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;While Hugo itself is free and comes with a bunch of &lt;a href=&#34;https://themes.gohugo.io/&#34;&gt;themes&lt;/a&gt; you can use for free, there are fancier and more feature-rich themes available to purchase from designers which may have a cost attached to them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Hugo isn&amp;rsquo;t the only static site framework out there, there are plenty more too each with their own pros and cons. I won&amp;rsquo;t aim to reproduce a complete list of comparisons against other platforms, other people have already made the comparisons much better than I could! But nevertheless, some that I am aware of off the top of my head are &lt;a href=&#34;https://jekyllrb.com/&#34;&gt;Jekyll&lt;/a&gt;, &lt;a href=&#34;https://ghost.org/&#34;&gt;Ghost&lt;/a&gt;, and the infamous &lt;a href=&#34;https://wordpress.com/&#34;&gt;WordPress&lt;/a&gt; - so investigate using your search engine of choice if you wish to know the differences.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;UPDATE 2019-11-30&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I came across &lt;a href=&#34;https://news.ycombinator.com/item?id=21616149&#34;&gt;this thread&lt;/a&gt; on HackerNews which ultimately lead me to these comparison websites for static site generators:&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.staticgen.com/&#34;&gt;https://www.staticgen.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://staticsitegenerators.net/&#34;&gt;https://staticsitegenerators.net/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;All set on Hugo? Awesome - let&amp;rsquo;s get started on some project foundations.&lt;/p&gt;
&lt;h2 id=&#34;git-set-up&#34;&gt;Git Set Up&lt;/h2&gt;
&lt;p&gt;I mentioned before I split my website into two repos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/jdheyburn/jdheyburn.co.uk&#34;&gt;jdheyburn.co.uk&lt;/a&gt; holds all the source code&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/jdheyburn/jdheyburn.github.io&#34;&gt;jdheyburn.github.io&lt;/a&gt; holds the rendered website&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the first repo, it doesn&amp;rsquo;t really matter what you call it - you can call it your destined domain name, or &lt;code&gt;blog-source&lt;/code&gt;, or &lt;code&gt;dogs-are-great&lt;/code&gt;. It&amp;rsquo;s the second one which you will need to think about, where it must be &lt;code&gt;&amp;lt;YOUR_USERNAME&amp;gt;.github.io&lt;/code&gt; - which will ultimately be made available at &lt;code&gt;https://&amp;lt;YOUR_USERNAME&amp;gt;.github.io&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Create two &lt;a href=&#34;https://github.com/new&#34;&gt;empty Github repos now&lt;/a&gt;, then clone both to your environment. If you don&amp;rsquo;t have git available in your terminal, check out &lt;a href=&#34;https://help.github.com/en/github/getting-started-with-github/set-up-git&#34;&gt;this guide&lt;/a&gt; to get set up.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mkdir ~/projects
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;cd&lt;/span&gt; projects
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git clone https://github.com/&amp;lt;USERNAME&amp;gt;/blog-source.git
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git clone https://github.com/&amp;lt;USERNAME&amp;gt;/&amp;lt;USERNAME&amp;gt;.github.io.git
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now that we have the repos set up, let&amp;rsquo;s get started on building the website templates.&lt;/p&gt;
&lt;h2 id=&#34;blog-bootstrapping&#34;&gt;Blog Bootstrapping&lt;/h2&gt;
&lt;p&gt;Before you get started, you&amp;rsquo;re going to need to install Hugo - head to &lt;a href=&#34;https://gohugo.io/getting-started/installing&#34;&gt;this page&lt;/a&gt; for instructions on going so. Once done continue on below.&lt;/p&gt;
&lt;p&gt;Firstly you would need to create your Hugo template. You can do this from either executing &lt;code&gt;hugo new &amp;lt;site|theme&amp;gt;&lt;/code&gt; and building up from there - or do what I did which was to browse the &lt;a href=&#34;https://themes.gohugo.io/&#34;&gt;Hugo themes&lt;/a&gt; and &lt;code&gt;git clone&lt;/code&gt; the example site for the chosen theme and then make your changes around that. It may entirely depend on your learning approach which way works best for you.&lt;/p&gt;
&lt;p&gt;In my case, I wanted to get up and running in the smallest time possible (isn&amp;rsquo;t that the point of static site generators?), so I followed the approach above.&lt;/p&gt;
&lt;h3 id=&#34;serves-up&#34;&gt;Serves Up!&lt;/h3&gt;
&lt;p&gt;The theme this blog uses as of publication is &lt;a href=&#34;https://themes.gohugo.io/hugo-coder/&#34;&gt;hugo-coder&lt;/a&gt;, written by &lt;a href=&#34;https://luizdepra.dev/&#34;&gt;Luiz de Prá&lt;/a&gt;. You may wish to use it, or something else. It&amp;rsquo;s entirely up to you! Let&amp;rsquo;s get started by laying down the foundations and cloning the theme.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;cd&lt;/span&gt; ~/projects
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git clone https://github.com/luizdepra/hugo-coder.git
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cp -r hugo-coder/exampleSite blog-source
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mkdir blog-source/themes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ln -s ~/projects/hugo-coder blog-source/themes/hugo-coder
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;cd&lt;/span&gt; blog-source
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;hugo serve
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That last command will locally serve the example site, so that you can view it at &lt;code&gt;http://localhost:1313/&lt;/code&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;local_example_site.jpg&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-1-getting-started//blog/who-goes-blogging-1-getting-started/local_example_site.jpg&#34;
    alt=&#34;Screenshot depicting the locally run example site&#34;&gt;&lt;/a&gt;&lt;figcaption&gt;
      &lt;p&gt;Success!&lt;/p&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id=&#34;experimenting&#34;&gt;Experimenting&lt;/h3&gt;
&lt;p&gt;Once you have the site hosted locally, feel free to make as many changes as you like to gain an understanding of how everything plugs together.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;blog-template/config.toml&lt;/code&gt; file will contain the majority of configurations that are used to generate the site. Go ahead and even comment out some config and see what affect that has. You can even &lt;a href=&#34;https://github.com/jdheyburn/jdheyburn.co.uk/blob/master/config.toml&#34;&gt;be nosey at my config file&lt;/a&gt; if you&amp;rsquo;re looking for some inspiration! The &lt;code&gt;hugo serve&lt;/code&gt; command will watch all files in the directory for any changes you make, rebuild them, and refresh your webpage too with the changes - making for hasty development!&lt;/p&gt;
&lt;p&gt;The Hugo config file will be a source of many informations, and the &lt;a href=&#34;https://gohugo.io/getting-started/configuration/&#34;&gt;documentation for it is very thorough&lt;/a&gt;. While we&amp;rsquo;re on the subject, the rest of the Hugo documentation is great, so check the rest of it out if you haven&amp;rsquo;t done so already - particularly how the &lt;a href=&#34;https://gohugo.io/getting-started/directory-structure/&#34;&gt;directories are structured&lt;/a&gt;, and how &lt;a href=&#34;https://gohugo.io/content-management/&#34;&gt;content is managed&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I advise you now go off and explore all the customisation options for your site, when you&amp;rsquo;re ready to have it deployed, continue on reading.&lt;/p&gt;
&lt;h3 id=&#34;theme-git-submodules&#34;&gt;Theme Git Submodules&lt;/h3&gt;
&lt;p&gt;When we were setting up the &lt;code&gt;exampleSite&lt;/code&gt; locally, we created a symlink from &lt;code&gt;~/projects/blog-source/themes/hugo-coder&lt;/code&gt; -&amp;gt; &lt;code&gt;~/projects/hugo-coder&lt;/code&gt;. This is okay for local development, however not particularly the best practice in the real world. We want to take an existing git repo (hugo-coder) and apply that to a directory in our project, but what we &lt;em&gt;don&amp;rsquo;t&lt;/em&gt; want to do is duplicate the code in our own repo.&lt;/p&gt;
&lt;p&gt;This problem is solved exactly by &lt;a href=&#34;https://git-scm.com/book/en/v2/Git-Tools-Submodules&#34;&gt;&lt;strong&gt;git submodules&lt;/strong&gt;&lt;/a&gt;, and we define it within the &lt;code&gt;.gitmodules&lt;/code&gt; file of our source code repo.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# .gitmodules&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;[&lt;/span&gt;submodule &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;themes/hugo-coder&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;path&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; themes/hugo-coder
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;url&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; https://github.com/luizdepra/hugo-coder.git
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now when this file is checked into the repo, any future clones will also include the hugo theme as a submodule in the &lt;code&gt;themes/hugo-coder&lt;/code&gt; directory.&lt;/p&gt;
&lt;p&gt;Once you&amp;rsquo;ve done this, your source code is all set! Why not share it with the rest of the world?&lt;/p&gt;
&lt;h2 id=&#34;releasing-into-the-wild&#34;&gt;Releasing into the wild&lt;/h2&gt;
&lt;p&gt;So far we have only been playing with the &lt;code&gt;hugo serve&lt;/code&gt; command, which is great for local development but not for production. There is a more appropriate command for building hugo projects - aptly named &lt;code&gt;hugo&lt;/code&gt;; pretty simple right?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;hugo
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This renders the HTML and CSS files from your config and markdown for the theme and places them in the &lt;code&gt;public/&lt;/code&gt; directory of your source code repo. In theory once you&amp;rsquo;ve executed this command you can host a webserver at that address and everything would operate as normal. Why not give it a try?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;cd&lt;/span&gt; public/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;python -m SimpleHTTPServer &lt;span style=&#34;color:#bd93f9&#34;&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can even go one step further by adding flags to the &lt;code&gt;hugo&lt;/code&gt; command, such as &lt;code&gt;--minify&lt;/code&gt; which will minimalise all the supported files into a much smaller size, resulting in faster load times for your users.&lt;/p&gt;
&lt;p&gt;Since we are still in our source code repo, we want to make sure that &lt;code&gt;public/&lt;/code&gt; directory doesn&amp;rsquo;t get included in future commits. This is because the &lt;code&gt;blog-source&lt;/code&gt; repo should be entirely for source code, our other repo is the one that holds the rendered code.&lt;/p&gt;
&lt;p&gt;For this we can utilise a &lt;code&gt;.gitignore&lt;/code&gt; file, instructing git to ignore any files that match the terms in the contents. Let&amp;rsquo;s get one created now and check our code into the repo.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;public/\nresources/&amp;#34;&lt;/span&gt; &amp;gt; .gitignore
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git add .
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git commit -m &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;Initial commit&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git push
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We&amp;rsquo;re currently committing to one repo, however we want GitHub Pages to host it for us. As mentioned earlier, GP can only host repos at the domain &lt;code&gt;github.io&lt;/code&gt;, so we need to get our rendered website into that repo.&lt;/p&gt;
&lt;h3 id=&#34;bash-script-deploying&#34;&gt;Bash Script Deploying&lt;/h3&gt;
&lt;p&gt;Now the original way I did this was through a simple bash script which largely followed &lt;a href=&#34;https://gohugo.io/hosting-and-deployment/hosting-on-github/#step-by-step-instructions&#34;&gt;this process&lt;/a&gt; as documented by the Hugo team. Note that this is largely the process we have followed thus far, with the exception of step 6 onwards - so ensure you follow those steps.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I&amp;rsquo;ve since moved over onto a CI/CD platform which I will discuss in a future post.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;After following the process in the link prior, you should be able to invoke your deploy script at &lt;code&gt;./deploy.sh&lt;/code&gt;, which will push the built &lt;code&gt;public/&lt;/code&gt; directory to your &lt;code&gt;&amp;lt;USERNAME&amp;gt;.github.io&lt;/code&gt;. GitHub should pick up that this is a GitHub Pages repo and have your site ready for you at &lt;code&gt;https://&amp;lt;USERNAME&amp;gt;.github.io&lt;/code&gt;!&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;In this post, we have done the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Created two GitHub repos:
&lt;ul&gt;
&lt;li&gt;one for storing the source code for the website&lt;/li&gt;
&lt;li&gt;another containing the rendered web pages&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Built Hugo locally for development using &lt;code&gt;hugo serve&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Written a script for deployment&lt;/li&gt;
&lt;li&gt;Deployed to GitHub Pages&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Next up, we&amp;rsquo;ll be adding a custom domain to the site, front it with a CDN, and redirect multiple domains to it.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Who Goes Blogging 0: Applying Cartography</title><enclosure url="https://jdheyburn.co.uk/blog/who-goes-blogging-0-applying-cartography/blog-arch-cover.png" type="image/jpg"></enclosure>
      
      <link>https://jdheyburn.co.uk/blog/who-goes-blogging-0-applying-cartography/</link>
      <pubDate>Wed, 11 Sep 2019 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/who-goes-blogging-0-applying-cartography/</guid>-->
      <description>&lt;h2 id=&#34;applying-cartography&#34;&gt;Applying Cartography&lt;/h2&gt;
&lt;p&gt;With my academic background focused in infrastructure, I love seeing diagrams of topologies - they&amp;rsquo;re a pretty damn useful way of understanding architecture of an application flow amongst other things. Let&amp;rsquo;s take a look at how my portfolio site is architected out.&lt;/p&gt;
&lt;p&gt;As I mentioned in my &lt;a href=&#34;https://jdheyburn.co.uk/posts/blog-bootstrap/&#34;&gt;first blog post&lt;/a&gt; - I&amp;rsquo;ve got several ideas on how I can improve on the architecture of this site. But what good is evaluating where you&amp;rsquo;ve come from if you don&amp;rsquo;t document what you currently have?&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s when you can make true comparisons in any system. As such the aim of this series of posts will be to explain how this website is architected, so that we may reference it in future posts. Later on, I&amp;rsquo;ll also talk about how you&amp;rsquo;re able to get yourself set up as well.&lt;/p&gt;
&lt;h3 id=&#34;the-setup&#34;&gt;The Setup&lt;/h3&gt;
&lt;p&gt;Because we all love an architecture diagram, let&amp;rsquo;s slap one in now.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;blog-arch-cover.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-0-applying-cartography//blog/who-goes-blogging-0-applying-cartography/blog-arch-cover.png&#34;
    alt=&#34;An architecture diagram of my portfolio site&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Wow. That is&amp;hellip; really not much at all. Apologies if you were expecting a lot more arrows and boxes!&lt;/p&gt;
&lt;p&gt;In a way, I shouldn&amp;rsquo;t be sorry, because one of the core priniciples in software development is KISS (Keep It Simple Stupid!) - and this setup is just an advantage of how I decided the first implementation of my website to be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Easy to implement&lt;/li&gt;
&lt;li&gt;Easy to maintain (there is none required!)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;m a firm believer that you&amp;rsquo;re best off getting a minimum viable product out there ASAP and then work to perfect it afterward. What I hope to document as time goes on are the various enhancements and changes that I intend on making to the website so that yourselves can follow along too with your own site. That goes not just for this site (how much can it really be improved?!) but for whatever projects I work on.&lt;/p&gt;
&lt;h2 id=&#34;so-what-does-it-all-mean&#34;&gt;So what does it all mean?&lt;/h2&gt;
&lt;p&gt;Back to the diagram, let&amp;rsquo;s follow it from right-to-left and start talking about what&amp;rsquo;s going on.&lt;/p&gt;
&lt;h3 id=&#34;github-pages---somewhere-to-call-home&#34;&gt;GitHub Pages - somewhere to call home&lt;/h3&gt;
&lt;p&gt;You can see that the site is hosted on GitHub Pages. Essentially this is GitHub&amp;rsquo;s platform for hosting the static resources that are checked into the public repos. This then presents the assets at a domain prefix of your choosing, suffixed by &lt;code&gt;.github.io&lt;/code&gt; - in my case it is at &lt;code&gt;jdheyburn.github.io&lt;/code&gt;. GitHub Pages only hosts your site via HTTPS so you know your pages aren&amp;rsquo;t being subject to a man-in-the-middle attack, and it takes care of your TLS certificates so you don&amp;rsquo;t need to worry about renewing them!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You might be wondering why you would need to serve a static site over HTTPS when you aren&amp;rsquo;t handling anything confidential. I&amp;rsquo;ll turn you to Troy Hunt&amp;rsquo;s excellent article &lt;a href=&#34;https://www.troyhunt.com/heres-why-your-static-website-needs-https/&#34;&gt;Here&amp;rsquo;s Why Your Static Website Needs HTTPS&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;supercharge-your-delivery&#34;&gt;Supercharge your delivery!&lt;/h3&gt;
&lt;p&gt;Now that the website is hosted at GitHub - I stuck a content delivery network (CDN) in front of GitHub for a whole load of reasons, some of which are:&lt;/p&gt;
&lt;h4 id=&#34;fast-end-user-response-times&#34;&gt;Fast end-user response times&lt;/h4&gt;
&lt;p&gt;CDNs cache your content at edge locations dotted all over the globe, where the client is then served content from the one closest geographically (let me direct you to &lt;a href=&#34;https://people.eecs.berkeley.edu/~rcs/research/interactive_latency.html&#34;&gt;latency numbers every programmer should know&lt;/a&gt;).&lt;/p&gt;
&lt;h4 id=&#34;security&#34;&gt;Security&lt;/h4&gt;
&lt;p&gt;The CDN acts as a proxy between the client and your servers - their requests don&amp;rsquo;t actually hit you directly.&lt;/p&gt;
&lt;p&gt;Not only that, a lot of CDNs provide DDoS protection to ensure excessive requests don&amp;rsquo;t bring down your servers.&lt;/p&gt;
&lt;h4 id=&#34;analytics&#34;&gt;Analytics&lt;/h4&gt;
&lt;p&gt;Because everyone loves graphs and numbers&amp;hellip; right??&amp;hellip; Alright just me then&amp;hellip;&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll be able to see how many requests are being served up, where requests originate from, amongst others. Take a look at an example below.&lt;/p&gt;
&lt;figure&gt;&lt;a href=&#34;analytics_example.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/who-goes-blogging-0-applying-cartography//blog/who-goes-blogging-0-applying-cartography/analytics_example.png&#34;
    alt=&#34;A graph displaying total requests, and how many of them were cached&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;h4 id=&#34;custom-domain&#34;&gt;Custom domain&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;This means you can access your site on something other than &lt;code&gt;&amp;lt;my_site&amp;gt;.github.io&lt;/code&gt;, which is great for when you move off GitHub Pages to another platform, you can just tell Cloudflare to source requests from another server&lt;/li&gt;
&lt;li&gt;It also gives your site that extra polish and professionalism about it - wouldn&amp;rsquo;t you agree?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My CDN of choice is &lt;a href=&#34;https://www.cloudflare.com/&#34;&gt;Cloudflare&lt;/a&gt;, for no reason more than is it completely free to use and will most likely stay free until I decide I would benefit from the next &lt;a href=&#34;https://www.cloudflare.com/en-gb/plans/&#34;&gt;pricing step&lt;/a&gt;. Did I mention the website is pretty damn easy to use as well?&lt;/p&gt;
&lt;p&gt;That means that the operating cost of a site like this is exactly ZILCH (nada, et al.), which again is another great reason to get set up on a platform like this.&lt;/p&gt;
&lt;h3 id=&#34;top-level-domain-fatigue&#34;&gt;Top-level domain fatigue?&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;m from the UK - and I wanted my site to reflect that, so I purchased a domain with the top-level domain (TLD) &lt;code&gt;.co.uk&lt;/code&gt;. However when users come to visit me on my site they may not always remember whether it was &lt;code&gt;.com&lt;/code&gt;, &lt;code&gt;.dev&lt;/code&gt;, &lt;code&gt;.tk&lt;/code&gt; (remember those?). Therefore I also have &lt;code&gt;jdheyburn.com&lt;/code&gt; set up in the same way as its &lt;code&gt;.co.uk&lt;/code&gt; sibling to maximise that user experience.&lt;/p&gt;
&lt;p&gt;Okay well that means there is a slight cost to maintain the site through purchasing and renewing the custom domains (notice how jdheyburn.com redirects to jdheyburn.co.uk?) but we&amp;rsquo;re talking in the £10s per year for these two.&lt;/p&gt;
&lt;p&gt;So if you&amp;rsquo;re interested in having a setup like this, then over the next few posts I&amp;rsquo;ll be detailing how you can do the same.&lt;/p&gt;
&lt;p&gt;- jdheyburn&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Three Ways To Spice Up Your Python Code</title><enclosure url="https://jdheyburn.co.uk/images/jdheyburn_co_uk_card.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/blog/three-ways-to-spice-up-your-python-code/</link>
      <pubDate>Fri, 05 Jul 2019 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/three-ways-to-spice-up-your-python-code/</guid>-->
      <description>&lt;h2 id=&#34;spice-up-your-life-python-snake&#34;&gt;Spice Up Your &lt;del&gt;Life&lt;/del&gt; Python &amp;#x1f40d;&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m currently working on a side project that I&amp;rsquo;ve written in Python. Now I&amp;rsquo;ve have a lot of experience with Python before, I used it primarily to write a load of scripts for automating processes for when Bash didn&amp;rsquo;t quite cut it. Don&amp;rsquo;t get me wrong Bash is great, but like for every other language out there, each has its purpose in the world.&lt;/p&gt;
&lt;p&gt;My experience with Python didn&amp;rsquo;t go beyond setting some variables, hitting endpoints, executing some Bash commands to install some vendor components. All of which is what makes Python so great right? Implying on types is useful for some cases. In fact this reminds me of a tweet I saw, I can&amp;rsquo;t find it again but it went something like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Stages of learning programming:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Learn a typed language, such as Java - complain at its complexity&lt;/li&gt;
&lt;li&gt;Learn an untyped language, such as JavaScript or Python - marvel at its simplicity&lt;/li&gt;
&lt;li&gt;Get frustrated at implied types in step 2&lt;/li&gt;
&lt;li&gt;Revert to step 1&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;During said experiences I came across some poorly written scripts whiched &lt;strong&gt;triggered&lt;/strong&gt; some pet peeves:  what object a function is expecting, or what it returns? Did I break some downstream function? This made it terribly difficult to add new functionality to scripts.&lt;/p&gt;
&lt;p&gt;With this side project I wanted to make it right from the start. So I said to myself these are the key areas I want to target:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The application must be extensively tested (did I mention I &lt;a href=&#34;https://jdheyburn.co.uk/posts/extending-gotests-for-strict-error-tests/&#34;&gt;love tests?&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Functions must clearly define what the type of the objects the parameters are, and what the types of the objects they return are.&lt;/li&gt;
&lt;li&gt;&lt;del&gt;Best&lt;/del&gt; Practices are upheld throughout the way&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;From this list, we can include the following to solve the above:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&#34;https://jdheyburn.co.uk/blog/three-ways-to-spice-up-your-python-code/#tests&#34;&gt;Tests!!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jdheyburn.co.uk/blog/three-ways-to-spice-up-your-python-code/#static-type-checking&#34;&gt;Static type checking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://jdheyburn.co.uk/blog/three-ways-to-spice-up-your-python-code/#class-objects&#34;&gt;Classes&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;tests&#34;&gt;Tests&lt;/h2&gt;
&lt;p&gt;Alright so I know this one is a given. But in all honesty I&amp;rsquo;ve never really tested Python code before. Why so? I found it difficult to mock API calls and the effort required for the initial learning curve didn&amp;rsquo;t seem to pay off for the tiny script I was automating. Given the scope of my side project is rather large, this is a great opportunity to learn. Let&amp;rsquo;s start with a basic function:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# scratch.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;increment_int_by_one&lt;/span&gt;(int_to_inc):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    incremented_int &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; int_to_increment &lt;span style=&#34;color:#ff79c6&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; incremented_int
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;While this is something easy enough to test by doing so manually, at some point its functionality may increase, and at that point we would require automated tests to ensure its original feature-set was unchanged.&lt;/p&gt;
&lt;h3 id=&#34;implementation&#34;&gt;Implementation&lt;/h3&gt;
&lt;p&gt;There are &lt;a href=&#34;https://wiki.python.org/moin/PythonTestingToolsTaxonomy&#34;&gt;several testing frameworks&lt;/a&gt; out there, but for my use case I am using the built-in &lt;code&gt;unittest&lt;/code&gt; which is easy enough to use. A basic structure of a test file is seen below:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# test_scratch.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; unittest
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; scratch
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;TestIncrement&lt;/span&gt;(unittest&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;TestCase):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;test_increment&lt;/span&gt;(self):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        Should successfully return int value 2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        Given a parameter of int value 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        expected &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        int_to_inc &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        actual &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; scratch&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;increment_int_by_one(int_to_inc)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assertEqual(actual, expected)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; __name__ &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    unittest&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;main()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There&amp;rsquo;s a lot going on here, let&amp;rsquo;s break it down by section.&lt;/p&gt;
&lt;h4 id=&#34;imports&#34;&gt;Imports&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; unittest
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; scratch
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here we are specifying the modules that the test file is going to interact with. &lt;code&gt;unittest&lt;/code&gt; is the testing framework and &lt;code&gt;scratch&lt;/code&gt; is the Python file containing our business logic, as written earlier.&lt;/p&gt;
&lt;h4 id=&#34;class-declaration&#34;&gt;Class Declaration&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;TestIncrement&lt;/span&gt;(unittest&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;TestCase):
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Simply enough, this is a class that extends the &lt;code&gt;unittest.TestCase&lt;/code&gt; class, for which then unittest can then execute all the test methods defined within it, and you can name them like &lt;code&gt;class TestXXXX&lt;/code&gt;. There is no limit to how many classes you can have, but I like to group my TestClasses together in terms of what logic they are testing.&lt;/p&gt;
&lt;h4 id=&#34;test-methods&#34;&gt;Test Methods&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;test_increment&lt;/span&gt;(self):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;    Should successfully return int value 2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;    Given a parameter of int value 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;    &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    expected &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    int_to_inc &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    actual &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; scratch&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;increment_int_by_one(int_to_inc)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    self&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assertEqual(actual, expected)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These methods are the real juicy bits of your tests. Here you are defining the parameters that are going to be used by your functions, and hitting the logic you are testing. For each test that you write you will alter the test name, description, expected values, and input parameters accordingly to what you are testing.&lt;/p&gt;
&lt;h4 id=&#34;main-method-invocation&#34;&gt;Main Method Invocation&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; __name__ &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    unittest&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;main()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To close off nice and easily, this will tell Python to invoke all test classes that are described within &lt;code&gt;unittest.TestCase&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;execution&#34;&gt;Execution&lt;/h3&gt;
&lt;p&gt;Now just execute the test file and it will now run as expected:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt;&lt;/span&gt; python &lt;span style=&#34;color:#ff79c6&#34;&gt;-&lt;/span&gt;m unittest test_scratch                                     
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;                                                                     
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;----------------------------------------------------------------------&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Ran &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt; test &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;0.001&lt;/span&gt;s                                                  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                                                      
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;OK                                                                    
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A nice little touch is that we don&amp;rsquo;t even need to provide &lt;code&gt;-m unittest&lt;/code&gt; to the Python intepreter since we have defined the main method invocation:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt;&lt;/span&gt; python test_scratch&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;py                                              
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;                                                                     
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;----------------------------------------------------------------------&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Ran &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt; test &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;0.002&lt;/span&gt;s                                                  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                                                      
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;OK                                                                    
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;static-type-checking&#34;&gt;Static Type Checking&lt;/h2&gt;
&lt;p&gt;As I mentioned before, Python is great for throwing together a script to automate some task. It&amp;rsquo;s quick and easy to do mainly because the type of a variable is implied from whatever you set to it.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; my_int_var &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt;(my_int_var)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &amp;#39;&lt;span style=&#34;color:#50fa7b&#34;&gt;int&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This also applies to functions that take in a parameter, the type for it is implied from what it receives! Let&amp;rsquo;s revisit our incrementing function from earlier:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;increment_int_by_one&lt;/span&gt;(int_to_inc):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    incremented_int &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; int_to_increment &lt;span style=&#34;color:#ff79c6&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; incremented_int
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;print&lt;/span&gt;(increment_int_by_one(&lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bd93f9&#34;&gt;2&lt;/span&gt;                            
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;print&lt;/span&gt;(increment_int_by_one(&lt;span style=&#34;color:#bd93f9&#34;&gt;100&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bd93f9&#34;&gt;101&lt;/span&gt;                          
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;All good so far. But what happens when we pass in something that is not an int, such as a string?&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;print&lt;/span&gt;(increment_int_by_one(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;1&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Traceback (most recent call last):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  File &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;lt;stdin&amp;gt;&amp;#34;&lt;/span&gt;, line &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;&lt;/span&gt;module&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  File &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;lt;stdin&amp;gt;&amp;#34;&lt;/span&gt;, line &lt;span style=&#34;color:#bd93f9&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; increment_int_by_one
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;TypeError: must be &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Yikes, that&amp;rsquo;s a misleading error! Having a look at the error straight away, you&amp;rsquo;d be confused at &amp;lsquo;what&amp;rsquo; must be a string, not an int - didn&amp;rsquo;t you accidentally provide a str? So why is it telling us it must be a str and not an int?&lt;/p&gt;
&lt;p&gt;What this is actually error-ing on is how an int is being concatenated to a str on the left-hand side of the + operator.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Under the hood, Python believe you are trying to do a &lt;strong&gt;string concatenation&lt;/strong&gt; because the parameter is of type string, and is on the left-hand side of the + operator. The below shows a valid way to perform a string concatenation:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;quot;1&amp;quot; + &amp;quot;1&amp;quot; # -&amp;gt; &amp;quot;11&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If the int value was on the left-hand side of the operator and you passed a string, you&amp;rsquo;d get something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fallback&#34; data-lang=&#34;fallback&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;gt;&amp;gt; 1 + &amp;#34;1&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Traceback (most recent call last):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; File &amp;#34;&amp;lt;stdin&amp;gt;&amp;#34;, line 1, in &amp;lt;module&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;TypeError: unsupported operand type(s) for +: &amp;#39;int&amp;#39; and &amp;#39;str&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;But changing the right-side of the operator won&amp;rsquo;t fix this for us. What we need is to implement typing.&lt;/p&gt;
&lt;h3 id=&#34;implementation-1&#34;&gt;Implementation&lt;/h3&gt;
&lt;p&gt;Python 3.6 onwards introduced static type checking, so make sure you upgrade to it if you haven&amp;rsquo;t already - which you might want to do very soon as Python 2 is &lt;a href=&#34;https://pythonclock.org/&#34;&gt;EOL in 2020&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Taking our above &lt;code&gt;increment_int_by_one&lt;/code&gt; function, we can add &lt;code&gt;: int&lt;/code&gt; to the parameter which will tell the function what type it expects the parameter to be.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;increment_int_by_one&lt;/span&gt;(int_to_inc: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    incremented_int &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; int_to_increment &lt;span style=&#34;color:#ff79c6&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; incremented_int
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now when we pass in a &lt;code&gt;str&lt;/code&gt; as the parameter, we still get the same error we saw before. However, if the IDE you are using supports it, you will receive &lt;strong&gt;type hints&lt;/strong&gt;!&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;type_hinting.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/three-ways-to-spice-up-your-python-code//blog/three-ways-to-spice-up-your-python-code/type_hinting.png&#34;
    alt=&#34;Screenshot displaying type hinting provided by Visual Studio Code&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;I&amp;rsquo;m using VSCode with the Python extension installed too, you can see the hint that appears includes the type of the parameter.&lt;/p&gt;
&lt;p&gt;VSCode also has the ability to check the type of the value you are passing into the function:&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;type_checking.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/three-ways-to-spice-up-your-python-code//blog/three-ways-to-spice-up-your-python-code/type_checking.png&#34;
    alt=&#34;Screenshot displaying type checking provided by Visual Studio Code - the illegal type is highlighted by pyright&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;Here we have a visual indicator that the string is incompatible with the int type of the function. Alongside this, in the Problems tab we have a full explanation on what has gone wrong. This is both provided by the &lt;code&gt;pyright&lt;/code&gt; extension which can be found in the VSCode Extensions section.&lt;/p&gt;
&lt;h3 id=&#34;return-types&#34;&gt;Return Types&lt;/h3&gt;
&lt;p&gt;So all of that just describes how typing works for parameters, but how can we specify the return type of a function? That is done easily enough too. Let&amp;rsquo;s extend on our increment function from earlier.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;increment_int_by_one&lt;/span&gt;(int_to_inc: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    incremented_int &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; int_to_inc &lt;span style=&#34;color:#ff79c6&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; incremented_int
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note the only change here is the &lt;code&gt;-&amp;gt; int&lt;/code&gt; which specifies the type it is returning. Now our type checker will highlight to us when we violate this in two scenarios:&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;type_checking_bad_return.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/three-ways-to-spice-up-your-python-code//blog/three-ways-to-spice-up-your-python-code/type_checking_bad_return.png&#34;
    alt=&#34;Screenshot displaying type checking, highlighting the bad return value&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;The IDE has shown us that the function is expected to return a type different to what it is actually returning&amp;hellip;&lt;/p&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;type_checking_bad_assign.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/blog/three-ways-to-spice-up-your-python-code//blog/three-ways-to-spice-up-your-python-code/type_checking_bad_assign.png&#34;
    alt=&#34;Screenshot displaying type checking, highlighting the bad assigned variable&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;p&gt;And in this snapshot, we are trying to assign a variable of type &lt;code&gt;str&lt;/code&gt; to the output of the function which returns an &lt;code&gt;int&lt;/code&gt;!&lt;/p&gt;
&lt;p&gt;Another pattern you might see is if a method does not return anything (in Java-speak, it is &lt;code&gt;void&lt;/code&gt;), an example of this is if the method is a constructor for a class.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; __init__(self) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    self&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;attribute &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;DEFAULT_VALUE&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;class-objects&#34;&gt;Class Objects&lt;/h2&gt;
&lt;p&gt;So you might &lt;strong&gt;class&lt;/strong&gt; this (pardon the pun!) as a type of its own, but this is something I&amp;rsquo;ve learnt all the same recently, so I hope it benefits you too! Python has the ability to construct more complex objects in the form of classes. While I won&amp;rsquo;t go into the details of that (I&amp;rsquo;ll leave it up to &lt;a href=&#34;https://www.w3schools.com/python/python_classes.asp&#34;&gt;better resources&lt;/a&gt;), I wanted to show how I&amp;rsquo;ve been able to utilise them.&lt;/p&gt;
&lt;h3 id=&#34;test-mocks&#34;&gt;Test Mocks&lt;/h3&gt;
&lt;p&gt;One of the main reasons for implementing classes has been to resolve one of my requirements I described earlier on in this post, the ability to test my code easily. For my side project I interface with some third-party APIs to retrieve data, in order for me to test these I need to mock them appropriately. See below for a snippet of the code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# spotify.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;SpApi&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    client: spotipy&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;Spotify
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; __init__(self, &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;id&lt;/span&gt;: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;, secret: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ccm &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; SpotifyClientCredentials(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            client_id&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;id&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            client_secret&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;secret
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;client &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; spotipy&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;Spotify(client_credentials_manager&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;ccm)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;get_album_by_id&lt;/span&gt;(self, &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;id&lt;/span&gt;: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; SpAlbum:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; SpAlbum(self&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;client&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;album(&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;id&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;query_album_by_title&lt;/span&gt;(self, title: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; SpQueryRespWrapper:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        q &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; SpQueryBuilder(album&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;title)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;build()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; self&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute_query(q)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;query_album_by_title_and_artist&lt;/span&gt;(self,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                        title: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                        artist: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; SpQueryRespWrapper:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        q &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; SpQueryBuilder(album&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;title, album_artist&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;artist)&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;build()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; self&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;execute_query(q)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;execute_query&lt;/span&gt;(self, q: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; SpQueryRespWrapper:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; SpQueryRespWrapper(self&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;client&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;search(q&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;q, &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;album&amp;#39;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You might have guessed it, the API is Spotify&amp;rsquo;s, and I am using the &lt;a href=&#34;https://github.com/plamere/spotipy&#34;&gt;spotipy&lt;/a&gt; library to do the interfacing. The &lt;code&gt;SpApi&lt;/code&gt; class is essentially a wrapper around the client to allow me to easily re-use methods across the app. It&amp;rsquo;s usage would be &lt;code&gt;sp_api = SpApi()&lt;/code&gt;, which would return us a client available at &lt;code&gt;sp_api.client&lt;/code&gt;. It is this attribute of the class which we want the ability to mock for when we test it. See how I implemented the mock:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6272a4&#34;&gt;# test_spotify.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; unittest
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;from&lt;/span&gt; unittest.mock &lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; MagicMock
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; munch
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;import&lt;/span&gt; spotify
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;get_album_by_id_response_fname &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;sp_get_album_by_id.json&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;with&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;open&lt;/span&gt;(get_album_by_id_response_fname, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;r&amp;#39;&lt;/span&gt;, encoding&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;as&lt;/span&gt; f:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    get_album_by_id_response &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; json&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;load(f)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;TestSpApiClass&lt;/span&gt;(unittest&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;TestCase):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;get_mock_sp_api&lt;/span&gt;(self) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; spotify&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;SpApi:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        mock_sp_api &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; spotify&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;SpApi(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;id&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;secret&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        mocked_album &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; MagicMock(return_value&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;get_album_by_id_response)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        mock_sp_api&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;client &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; MagicMock(album&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;mocked_album)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; mock_sp_api
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;test_get_album_by_id&lt;/span&gt;(self):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        Given a request is made to Spotify API for Album ID
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        Should verify that the API is hit
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        mock_sp_api &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; self&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;get_mock_sp_api()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        expected &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; spotify&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;SpAlbum(munch&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;munchify(get_album_by_id_response))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        actual &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; mock_sp_api&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;get_album_by_id(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;album_id&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        mock_sp_api&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;client&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;album&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assert_called_once_with(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;album_id&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assertEqual(actual, expected)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Talking about &lt;code&gt;get_mock_sp_api&lt;/code&gt; first, this constructs a new &lt;code&gt;SpApi&lt;/code&gt; wrapper class, but then it overwrites the client attribute with MagicMocks from the unittest framework. MagicMock essentially creates a mocked object or function for you. When I create a MagicMock with a &lt;code&gt;return_value&lt;/code&gt; parameter, it will return that passed parameter when the MagicMock object is invoked.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Did you notice how it specifies the return type too?! &lt;code&gt;-&amp;gt; spotify.SpApi&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The test &lt;code&gt;test_get_album_by_id&lt;/code&gt; creates an instance of this mocked Spotify API, so when we call the wrapper method &lt;code&gt;get_album_by_id&lt;/code&gt; it makes a downstream call to &lt;code&gt;client.album&lt;/code&gt;, which is what we mocked already with the chained MagicMock object! So now we can tell it what to return back when hit with certain parameters - and validating back with &lt;code&gt;assert_called_once_with&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Trying to learn how to implement the above has probably been the biggest blocker for me to test Python code in this way - now that I&amp;rsquo;ve discovered it I can&amp;rsquo;t stop using it everywhere!&lt;/p&gt;
&lt;h3 id=&#34;complex-object-comparison&#34;&gt;Complex Object Comparison&lt;/h3&gt;
&lt;p&gt;Much like the previous section, this relates to the static typing we implemented earlier. I was writing methods that returned complex objects encapsulated in classes, and I wanted to be able to test for equality using unittest&amp;rsquo;s &lt;code&gt;self.assertEqual(actual, expected)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s take this simple class:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;SomeObject&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    some_str: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    some_int: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; __init__(self, some_str: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;str&lt;/span&gt;, some_int: &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;int&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;None&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;some_str &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; some_str
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;some_int &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; some_int
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;What we want to achieve is for us to be able to use the below test like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;TestObject&lt;/span&gt;(unittest&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;TestCase):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;test_equality&lt;/span&gt;(self):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        Should successfully test for object equality
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        Given both objects are equal
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        When using self.assertEqual
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;        &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        object1 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; scratch&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;SomeObject(some_str&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;str&amp;#39;&lt;/span&gt;, some_int&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        object2 &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; scratch&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;SomeObject(some_str&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#39;str&amp;#39;&lt;/span&gt;, some_int&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        self&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assertEqual(object1, object2)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;However executing the test gives us an error:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt;&lt;/span&gt; python test_scratch&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;py
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;F
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;======================================================================&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;FAIL: test_equality (__main__&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;TestObject)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;----------------------------------------------------------------------&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Traceback (most recent call last):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  File &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;test_scratch.py&amp;#34;&lt;/span&gt;, line &lt;span style=&#34;color:#bd93f9&#34;&gt;16&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; test_equality
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    self&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;assertEqual(object1, object2)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;AssertionError: &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;&lt;/span&gt;scratch&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;SomeObject &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;object&lt;/span&gt; at &lt;span style=&#34;color:#bd93f9&#34;&gt;0x000002A0A0199EB8&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;lt;&lt;/span&gt;scratch&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;SomeObject &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;object&lt;/span&gt; at &lt;span style=&#34;color:#bd93f9&#34;&gt;0x000002A0A0199EF0&lt;/span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;----------------------------------------------------------------------&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Ran &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt; test &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;0.003&lt;/span&gt;s
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;FAILED (failures&lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This fails because we have not overwritten the default &lt;code&gt;__eq__&lt;/code&gt; method on the class. Python then defaults to comparing the memory references for both objects. Since these are two entirely different objects they will be pointing to different memory references. What we need to do is tell Python what defines equality, so let&amp;rsquo;s add the method onto &lt;code&gt;SomeObject&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; __eq__(self, other):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt;(other) &lt;span style=&#34;color:#ff79c6&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt;(self):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; NotImplemented
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; self&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;some_str &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; other&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;some_str \
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;and&lt;/span&gt; self&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;some_int &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; other&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;some_int
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And here we are! First we are quickly checking to see the two objects are of the same type before we proceed with the underlying values (fail first, and early!). From there we are checking the attributes within the objects. Hurrah!&lt;/p&gt;
&lt;p&gt;One neat little trick I like to do for complex objects that have multiple attributes, is that instead of writing out a new &lt;code&gt;and&lt;/code&gt; comparator for every attribute, is to replace it all with this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;def&lt;/span&gt; __eq__(self, other):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt;(other) &lt;span style=&#34;color:#ff79c6&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt;(self):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; NotImplemented
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; self&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;__dict__ &lt;span style=&#34;color:#ff79c6&#34;&gt;==&lt;/span&gt; other&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;__dict__
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;__dict__&lt;/code&gt; attribute returns the objects in a Python dictionary format, which will contain all the attributes and their values. If the objects are entirely identical then they will return true! Now let&amp;rsquo;s run the test again.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt;&lt;/span&gt; python test_scratch&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;py                                              
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;.&lt;/span&gt;                                                                     
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ff79c6&#34;&gt;----------------------------------------------------------------------&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Ran &lt;span style=&#34;color:#bd93f9&#34;&gt;1&lt;/span&gt; test &lt;span style=&#34;color:#ff79c6&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;0.001&lt;/span&gt;s                                                  
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                                                      
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;OK                                                                    
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Normal service is resumed!!&lt;/p&gt;
&lt;h2 id=&#34;closing&#34;&gt;Closing&lt;/h2&gt;
&lt;p&gt;I hope the hints detailed in this helped you as much as it did for me! I&amp;rsquo;m sure there are many more tips out there - as I discover them be sure that I&amp;rsquo;ll share them once I find them, along with documenting more on the side project I&amp;rsquo;ve been working on!&lt;/p&gt;
&lt;p&gt;Thanks for reading &amp;#x1f603;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Extending Gotests for Strict Error Tests</title><enclosure url="https://jdheyburn.co.uk/images/jdheyburn_co_uk_card.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/blog/extending-gotests-for-strict-error-tests/</link>
      <pubDate>Mon, 29 Apr 2019 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/extending-gotests-for-strict-error-tests/</guid>-->
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;UPDATE 2020-07-17&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I wrote a follow up post to this one using the &lt;a href=&#34;https://godoc.org/github.com/stretchr/testify/assert&#34;&gt;assert&lt;/a&gt; package instead.&lt;/p&gt;
&lt;p&gt;Awesome - &lt;a href=&#34;https://jdheyburn.co.uk/blog/assertions-in-gotests-test-generation/&#34;&gt;take me there&lt;/a&gt;!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id=&#34;strict-error-tests-in-java&#34;&gt;Strict Error Tests in Java&lt;/h2&gt;
&lt;p&gt;I love confirming the stability of my code through writing tests and practicing Test-driven development (TDD).  For Java, JUnit was my preferred testing framework of choice. When writing tests to confirm an exception had been thrown, I used the optional parameter &lt;code&gt;expected&lt;/code&gt; for the annotation &lt;code&gt;@Test&lt;/code&gt;, however I quickly found that this solution would not work for methods where I raised the same exception class multiple times for different error messages, and testing on those messages.&lt;/p&gt;
&lt;p&gt;This is commonly found in writing a validation method such as the one below, which will take in a name of a dog and return a boolean if it is valid.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#8be9fd&#34;&gt;boolean&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;validateDogName&lt;/span&gt;(String dogName) &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;throws&lt;/span&gt; DogValidationException {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; (containsSymbols(dogName)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;new&lt;/span&gt; DogValidationException(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Dogs cannot have symbols in their name!&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; (dogName.&lt;span style=&#34;color:#50fa7b&#34;&gt;length&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;gt;&lt;/span&gt; 100) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;new&lt;/span&gt; DogValidationException(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Who has a name for a dog that long?!&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For this method, just using &lt;code&gt;@Test(expected = DogValidationException.class)&lt;/code&gt; on our test method is not sufficient; how can we determine that the exception was raised for a dogName.length breach and not for containing symbols?&lt;/p&gt;
&lt;p&gt;In order for me to resolve this, I came across the &lt;code&gt;ExpectedException&lt;/code&gt; class for JUnit on &lt;a href=&#34;https://www.baeldung.com/junit-assert-exception&#34;&gt;Baeldung&lt;/a&gt; which enables us to specify the error message expected. Here it is applied to the test case for this method:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@Rule
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;public&lt;/span&gt; ExpectedException exceptionRule &lt;span style=&#34;color:#ff79c6&#34;&gt;=&lt;/span&gt; ExpectedException.&lt;span style=&#34;color:#50fa7b&#34;&gt;none&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;@Test
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#8be9fd&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;shouldHandleDogNameWithSymbols&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    exceptionRule.&lt;span style=&#34;color:#50fa7b&#34;&gt;expect&lt;/span&gt;(DogValidationException.&lt;span style=&#34;color:#50fa7b&#34;&gt;class&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    exceptionRule.&lt;span style=&#34;color:#50fa7b&#34;&gt;expectMessage&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Dogs cannot have symbols in their name!&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    validateDogName(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;GoodestBoy#1&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;applying-to-golang&#34;&gt;Applying to Golang&lt;/h2&gt;
&lt;p&gt;Back to Golang, there is a built-in library aptly named &lt;code&gt;testing&lt;/code&gt; which enables us to assert on test conditions. When combined with &lt;a href=&#34;https://github.com/cweill/gotests&#34;&gt;Gotests&lt;/a&gt; - a tool for generating Go tests from your code - writing tests could not be easier! I love how this is bundled in with the Go extension for VSCode, my text editor of choice (for now&amp;hellip;).&lt;/p&gt;
&lt;p&gt;Converting the above Java &lt;code&gt;validateDogName&lt;/code&gt; method to Golang will produce something like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-golang&#34; data-lang=&#34;golang&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;validateDogName&lt;/span&gt;(name &lt;span style=&#34;color:#8be9fd&#34;&gt;string&lt;/span&gt;) (&lt;span style=&#34;color:#8be9fd&#34;&gt;bool&lt;/span&gt;, &lt;span style=&#34;color:#8be9fd&#34;&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;containsSymbols&lt;/span&gt;(name) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;false&lt;/span&gt;, errors.&lt;span style=&#34;color:#50fa7b&#34;&gt;New&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;dog cannot have symbols in their name&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;len&lt;/span&gt;(name) &amp;gt; &lt;span style=&#34;color:#bd93f9&#34;&gt;100&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;false&lt;/span&gt;, errors.&lt;span style=&#34;color:#50fa7b&#34;&gt;New&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;who has a name for a dog that long&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;true&lt;/span&gt;, &lt;span style=&#34;color:#ff79c6&#34;&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you have a Go method that returns the &lt;code&gt;error&lt;/code&gt; interface, then gotests will generate a test that look like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-golang&#34; data-lang=&#34;golang&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;Test_validateDogName&lt;/span&gt;(t &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;testing.T) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; args &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name &lt;span style=&#34;color:#8be9fd&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tests &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; []&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name    &lt;span style=&#34;color:#8be9fd&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        args    args
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        want    &lt;span style=&#34;color:#8be9fd&#34;&gt;bool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        wantErr &lt;span style=&#34;color:#8be9fd&#34;&gt;bool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Test error was thrown for dog name with symbols&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        args: args{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            name: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;GoodestBoy#1&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        want: &lt;span style=&#34;color:#ff79c6&#34;&gt;false&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        wantErr: &lt;span style=&#34;color:#ff79c6&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; _, tt &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;range&lt;/span&gt; tests {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        t.&lt;span style=&#34;color:#50fa7b&#34;&gt;Run&lt;/span&gt;(tt.name, &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;func&lt;/span&gt;(t &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;testing.T) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            got, err &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;validateDogName&lt;/span&gt;(tt.args.name)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; (err &lt;span style=&#34;color:#ff79c6&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;nil&lt;/span&gt;) &lt;span style=&#34;color:#ff79c6&#34;&gt;!=&lt;/span&gt; tt.wantErr {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                t.&lt;span style=&#34;color:#50fa7b&#34;&gt;Errorf&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;validateDogName() error = %v, wantErr %v&amp;#34;&lt;/span&gt;, err, tt.wantErr)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; got &lt;span style=&#34;color:#ff79c6&#34;&gt;!=&lt;/span&gt; tt.want {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                t.&lt;span style=&#34;color:#50fa7b&#34;&gt;Errorf&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;validateDogName() = %v, want %v&amp;#34;&lt;/span&gt;, got, tt.want)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;From the above we are limited to what error we can assert for, here &lt;em&gt;any&lt;/em&gt; error returned will pass the test. This is equivalent to using &lt;code&gt;@Test(expected=Exception.class)&lt;/code&gt; in JUnit! But there is another way&amp;hellip;&lt;/p&gt;
&lt;h3 id=&#34;modifying-the-generated-test&#34;&gt;Modifying the Generated Test&lt;/h3&gt;
&lt;p&gt;We only need to make a few simple changes to the generated test to give us the ability to assert on test error message&amp;hellip;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;Test_validateDogName&lt;/span&gt;(t &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;testing.T) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;type&lt;/span&gt; args &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name &lt;span style=&#34;color:#8be9fd&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    tests &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; []&lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name    &lt;span style=&#34;color:#8be9fd&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        args    args
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        want    &lt;span style=&#34;color:#8be9fd&#34;&gt;bool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;        wantErr &lt;span style=&#34;color:#8be9fd&#34;&gt;error&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Test error was thrown for dog name with symbols&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        args: args{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            name: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;GoodestBoy#1&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        want: &lt;span style=&#34;color:#ff79c6&#34;&gt;false&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;        wantErr: errors.&lt;span style=&#34;color:#50fa7b&#34;&gt;New&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;dog cannot have symbols in their name&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ff79c6&#34;&gt;for&lt;/span&gt; _, tt &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;range&lt;/span&gt; tests {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        t.&lt;span style=&#34;color:#50fa7b&#34;&gt;Run&lt;/span&gt;(tt.name, &lt;span style=&#34;color:#8be9fd;font-style:italic&#34;&gt;func&lt;/span&gt;(t &lt;span style=&#34;color:#ff79c6&#34;&gt;*&lt;/span&gt;testing.T) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            got, err &lt;span style=&#34;color:#ff79c6&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#50fa7b&#34;&gt;validateDogName&lt;/span&gt;(tt.args.name)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex; background-color:#3d3f4a&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; tt.wantErr &lt;span style=&#34;color:#ff79c6&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;nil&lt;/span&gt; &lt;span style=&#34;color:#ff79c6&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; !reflect.&lt;span style=&#34;color:#50fa7b&#34;&gt;DeepEqual&lt;/span&gt;(err, tt.wantErr) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                t.&lt;span style=&#34;color:#50fa7b&#34;&gt;Errorf&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;validateDogName() error = %v, wantErr %v&amp;#34;&lt;/span&gt;, err, tt.wantErr)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#ff79c6&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#ff79c6&#34;&gt;if&lt;/span&gt; got &lt;span style=&#34;color:#ff79c6&#34;&gt;!=&lt;/span&gt; tt.want {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                t.&lt;span style=&#34;color:#50fa7b&#34;&gt;Errorf&lt;/span&gt;(&lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;validateDogName() = %v, want %v&amp;#34;&lt;/span&gt;, got, tt.want)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;From the above there are three highlighted changes, let&amp;rsquo;s go over them individually:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;wantErr error&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;we are changing this from &lt;code&gt;bool&lt;/code&gt; so that we can make a comparison against the error returned from the function&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wantErr: errors.New(&amp;quot;dog cannot have symbols in their name&amp;quot;),&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;this is the error struct that we are expecting&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;if tt.wantErr != nil &amp;amp;&amp;amp; !reflect.DeepEqual(err, tt.wantErr) {&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;check to make sure the test is expected an error, if so then compare it against the returned error&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Point 3 provides additional support if there was a test case that did not expect an error. Note how &lt;code&gt;wantErr&lt;/code&gt; is omitted entirely from the test case below.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-golang&#34; data-lang=&#34;golang&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    name: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Should return true for valid dog name&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    args: args{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        name: &lt;span style=&#34;color:#f1fa8c&#34;&gt;&amp;#34;Benedict Cumberland the Sausage Dog&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    want: &lt;span style=&#34;color:#ff79c6&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;customising-gotests-generated-test&#34;&gt;Customising Gotests Generated Test&lt;/h3&gt;
&lt;p&gt;Gotests gives us the ability to provide our own templates for generating tests, and can easily be integrated into your text editor of choice. I&amp;rsquo;ll show you how this can be done in VSCode.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Check out gotests and copy the templates directory to a place of your choosing
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git clone https://github.com/cweill/gotests.git&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cp -R gotests/internal/render/templates ~/scratch/gotests&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Overwrite the contents of function.tmpl with &lt;a href=&#34;https://gist.github.com/jdheyburn/978e7b84dc9c197bcdd41afece2edab5&#34;&gt;the contents of this Gist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Add the following setting to VSCode&amp;rsquo;s settings.json
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;go.generateTestsFlags&amp;quot;: [&amp;quot;--template_dir=~/scratch/templates&amp;quot;]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once you have done that, future tests will now generate with stricter error testing! &amp;#x1f389;&lt;/p&gt;
&lt;h2 id=&#34;closing&#34;&gt;Closing&lt;/h2&gt;
&lt;p&gt;I understand that the recommendations above will make your code more fragile, as the code is subject to any changing of the error message of say a downstream library. However for myself, I prefer to write tests that are strict and minimalise the chance of other errors contaminating tests.&lt;/p&gt;
&lt;p&gt;I also understand that GoodestBoy#1 is probably a valid name for a dog! &amp;#x1f436;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Blog Bootstrap</title><enclosure url="https://jdheyburn.co.uk/images/jdheyburn_co_uk_card.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/blog/blog-bootstrap/</link>
      <pubDate>Mon, 11 Feb 2019 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/blog/blog-bootstrap/</guid>-->
      <description>&lt;p&gt;Howdy! &amp;#x1f44b;&lt;/p&gt;
&lt;p&gt;This site will be an area for me to braindump everything and anything that comes across my mind - although I&amp;rsquo;ll try my best to keep it tech-focused!&lt;/p&gt;
&lt;p&gt;The blog platform itself is pretty basic. It is built using &lt;a href=&#34;https://gohugo.io/&#34;&gt;Hugo&lt;/a&gt;, hosted using &lt;a href=&#34;https://pages.github.com/&#34;&gt;GitHub Pages&lt;/a&gt;, while served over a custom domain via CloudFlare. While this isn&amp;rsquo;t exactly anything to brag about, it is only meant to be a quick and easy platform to spin up and get hosted so that I can focus on what really matters&amp;hellip; the content!&lt;/p&gt;
&lt;p&gt;It also gives me an opportunity to migrate the platform from GitHub Pages onto another hosted platform. Maybe that is AWS, which is a perfect learning opportunity, or even customise the theme of this blog (which is very cool already - thanks &lt;a href=&#34;https://themes.gohugo.io/hugo-coder/&#34;&gt;hugo-coder!&lt;/a&gt;) that I have left as its default. All of which provides me with more content to blog about!&lt;/p&gt;
&lt;p&gt;See you around!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>:file_folder: Projects</title><enclosure url="https://jdheyburn.co.uk/projects/images/jdheyburn_co_uk_card.png" type="image/jpg"></enclosure>
      
      <link>https://jdheyburn.co.uk/projects/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/projects/</guid>-->
      <description>&lt;h2 id=&#34;portfolio-website&#34;&gt;Portfolio Website&lt;/h2&gt;
&lt;figure class=&#34;center&#34;&gt;&lt;a href=&#34;jdheyburn-homepage.png&#34;&gt;&lt;img src=&#34;https://jdheyburn.co.uk/projects//projects/jdheyburn-homepage.png&#34;
    alt=&#34;jdheyburn.co.uk homepage&#34;&gt;&lt;/a&gt;
&lt;/figure&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/jdheyburn/jdheyburn.co.uk&#34;&gt;jdheyburn.co.uk&lt;/a&gt; is my portfolio website - how you are reading this content!&lt;/li&gt;
&lt;li&gt;It is based the JAMstack framework &lt;a href=&#34;https://gohugo.io/&#34;&gt;Hugo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Hosted on &lt;a href=&#34;https://pages.github.com/&#34;&gt;GitHub Pages&lt;/a&gt; and built &amp;amp; deployed via &lt;a href=&#34;https://github.com/features/actions&#34;&gt;GitHub Actions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Globally distributed via &lt;a href=&#34;https://www.cloudflare.com/cdn/&#34;&gt;Cloudflare CDN&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;oss-contributions&#34;&gt;OSS Contributions&lt;/h2&gt;
&lt;h3 id=&#34;terraform-aws-providerhttpsgithubcomterraform-providersterraform-provider-aws&#34;&gt;&lt;a href=&#34;https://github.com/terraform-providers/terraform-provider-aws/&#34;&gt;terraform-aws-provider&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/terraform-providers/terraform-provider-aws/pull/9486&#34;&gt;&lt;strong&gt;Add data source for aws_ssm_patch_baseline&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adding a data source component to &lt;code&gt;aws_ssm_patch_baseline&lt;/code&gt; enables retrieval of AWS SSM Patch Baselines that already exist in your AWS account&lt;/li&gt;
&lt;li&gt;You can assign baselines that have been created by AWS, or managed outside of your Terraform configuration.&lt;/li&gt;
&lt;li&gt;Check out &lt;a href=&#34;https://jdheyburn.co.uk/blog/using-terraform-to-manage-aws-patch-baselines-at-enterprise-scale/&#34;&gt;this post&lt;/a&gt; for what capability it allows for&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Status&lt;/strong&gt;: Merged &amp;#x2705;&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/terraform-providers/terraform-provider-aws/pull/11879&#34;&gt;&lt;strong&gt;Add patch_source block to resource_aws_ssm_patch_baseline&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;a href=&#34;https://docs.aws.amazon.com/systems-manager/latest/userguide/patch-manager-how-it-works-alt-source-repository.html&#34;&gt;patch source&lt;/a&gt; API enables AWS SSM Patch Baselines to specify alternate patch repositories at runtime of the SSM Document AWS-RunPatchBaseline.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Status&lt;/strong&gt;: Merged &amp;#x2705;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>:mailbox: Contact</title><enclosure url="https://jdheyburn.co.uk/images/jdheyburn_co_uk_card.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/contact/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/contact/</guid>-->
      <description>&lt;p&gt;You can get in contact with me via any of the below:&lt;/p&gt;
&lt;p&gt;&amp;#x1f418; &lt;a href=&#34;https://hachyderm.io/@jdheyburn&#34;&gt;Mastodon&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x1f424; &lt;a href=&#34;https://twitter.com/jdheyburn/&#34;&gt;Twitter&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;#x1f4e7; jdheyburn [at] gmail [dot] com&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>:wave: Hey Joe!</title><enclosure url="https://jdheyburn.co.uk/images/jdheyburn_co_uk_card.png" type="image/jpg"></enclosure>
      <link>https://jdheyburn.co.uk/about/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      
      <guid>https://jdheyburn.co.uk/about/</guid>-->
      <description>&lt;p&gt;Hi there! &amp;#x1f44b;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m a technology enthusiast based in London, UK &amp;#x1f482;&amp;zwj;&amp;#x2642;&amp;#xfe0f; &amp;#x1f1ec;&amp;#x1f1e7; with experience across software development, DevOps, and Cloud Engineering.&lt;/p&gt;
&lt;p&gt;I mainly &lt;a href=&#34;https://jdheyburn.co.uk/blog/&#34;&gt;blog about&lt;/a&gt; stuff I&amp;rsquo;ve been working on - documenting my methodology throughout the way, along with writing up about any cool stuff &lt;a href=&#34;https://jdheyburn.co.uk/projects/&#34;&gt;I&amp;rsquo;ve built or contributed to&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re interested in contacting me then please feel free to &lt;a href=&#34;https://jdheyburn.co.uk/contact/&#34;&gt;reach out&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;heartbeat-passions&#34;&gt;&amp;#x1f493; Passions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Automation&lt;/li&gt;
&lt;li&gt;Code Quality&lt;/li&gt;
&lt;li&gt;Documentation&lt;/li&gt;
&lt;li&gt;Open Source&lt;/li&gt;
&lt;li&gt;Security&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;hammer_and_wrench-tools--technologies&#34;&gt;&amp;#x1f6e0;&amp;#xfe0f; Tools &amp;amp; Technologies&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Ansible&lt;/li&gt;
&lt;li&gt;ElasticSearch&lt;/li&gt;
&lt;li&gt;HAProxy&lt;/li&gt;
&lt;li&gt;MongoDB&lt;/li&gt;
&lt;li&gt;Redis&lt;/li&gt;
&lt;li&gt;SQL&lt;/li&gt;
&lt;li&gt;Terraform&lt;/li&gt;
&lt;li&gt;&amp;hellip; and a plethora of AWS services!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;trophy-scroll-certificates&#34;&gt;&amp;#x1f3c6; &amp;#x1f4dc; Certificates&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;AWS Solutions Architect Associate
&lt;ul&gt;
&lt;li&gt;2019 - 2022&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Cisco Certified Network Associate (CCNA)
&lt;ul&gt;
&lt;li&gt;2013 - 2019&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;speaking_head-globe_with_meridians-languages&#34;&gt;&amp;#x1f5e3;&amp;#xfe0f; &amp;#x1f310; Languages&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Bash scripting&lt;/li&gt;
&lt;li&gt;Golang&lt;/li&gt;
&lt;li&gt;Java&lt;/li&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
  </channel>
</rss>