<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="https://chris.bur.gs/">
  <title>Ungathered Thoughts</title>
  <subtitle>HOW DO YOU DO, PLEASE STATE YOUR PROBLEM &gt;_</subtitle>
  <link href="https://chris.bur.gs/feed/feed.xml" rel="self"/>
  <link href="https://chris.bur.gs/" />
  <updated>2026-09-14T16:09:27Z</updated>
  <id>https://chris.bur.gs/</id>
  <author>
    <name>Chris Burgess</name>
  </author>
  <entry>
    <title>Contents may differ</title>
    <link href="https://chris.bur.gs/contents-may-differ/"/>
    <updated>2026-08-31T00:00:00Z</updated>
    <id>https://chris.bur.gs/contents-may-differ/</id>
    <content xml:lang="en" type="html">&lt;div&gt;
  &lt;embed src=&quot;https://chris.bur.gs/media/contents-may-differ-in-several-languages.pdf&quot; type=&quot;application/pdf&quot; class=&quot;h-[80vh] w-full&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;WAHL product guide, advice that contents may differ from documentation in twenty-seven languages.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>A gadget for clutter-filled homes</title>
    <link href="https://chris.bur.gs/a-gadget-for-clutter-filled-homes/"/>
    <updated>2026-08-31T00:00:00Z</updated>
    <id>https://chris.bur.gs/a-gadget-for-clutter-filled-homes/</id>
    <content xml:lang="en" type="html">&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/a-gadget-for-clutter-filled-homes.jpg&quot; alt=&quot;screenshot of youtube ad for an Aldi gadget intended for clutter-filled homes&quot; class=&quot;rounded-lg ring-1 ring-black/10 shadow-lg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Advertisement, Google News Showcase, 2026.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Mesh in Ōtepoti</title>
    <link href="https://chris.bur.gs/mesh-in-otepoti/"/>
    <updated>2026-04-27T00:00:00Z</updated>
    <id>https://chris.bur.gs/mesh-in-otepoti/</id>
    <content xml:lang="en" type="html">&lt;blockquote&gt;
&lt;p&gt;Hi Chris do you have a purchase link for that repeater? I&#39;m looking to get one.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yes I do, but I think Ali Express URLs are too long for Meshcore messages. So ... here we go!&lt;/p&gt;
&lt;h2&gt;Liberton Repeater - SenseCAP Solar Node P1 Pro&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://chris.bur.gs/media/liberton-repeater-seeed-sensecap-solar-node-p1-pro.jpg&quot;&gt;&lt;img src=&quot;https://chris.bur.gs/media/liberton-repeater-seeed-sensecap-solar-node-p1-pro.jpg&quot; alt=&quot;Liberton Repeater - SenseCAP Solar Node P1 Pro&quot; class=&quot;float-right ml-4 mb-4 w-72&quot; eleventy:widths=&quot;256,512&quot; sizes=&quot;16rem&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I got a Seeed SenseCap Solar Node P1 Pro. The name is a MOUTHFUL but the &amp;quot;Pro&amp;quot; is significant - the non-Pro version lacks batteries as well as GPS. (The batteries were convenient, the node is fixed so GPS less useful to me.)&lt;/p&gt;
&lt;p&gt;I got mine from the Seeed store, I was holding off buying (as shipping to Aotearoa was hugely expensive) until they ran a Boxing Day (?) free global shipping sale and I jumped on it. If I was buying another today I&#39;d consider Ali Express as shipping from there can be cheaper.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.seeedstudio.com/SenseCAP-Solar-Node-P1-Pro-for-Meshtastic-LoRa-p-6412.html&quot;&gt;Seeed Studio - SenseCAP Solar Node P1-Pro for Meshtastic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.aliexpress.com/item/1005009301480801.html&quot;&gt;Ali Express - SenseCAP Solar Meshtastic Node 868MHz 915MHz Meshcore P1 Pro XIAO L76K GPS nRF52840 Plus built-in Battery 2dBi Antenna Grove&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;img src=&quot;https://chris.bur.gs/media/liberton-repeater-battery.png&quot; alt=&quot;Liberton Repeater - Battery tracking in Home Assistant&quot; class=&quot;float-right ml-4 mb-4 w-72&quot; eleventy:widths=&quot;256,512&quot; sizes=&quot;16rem&quot; /&gt;
&lt;p&gt;It&#39;s a good little unit, seems to be holding its battery OK here in Liberton as the skies are getting greyer (April / Autumn). I am not an RF expert, but it mounted up fine on roof vent pipe here in Liberton, has run reliably on Meshtastic (initially) and now Meshcore, and doesn&#39;t ask me for maintenance. No issues with weather; I could perhaps improve its location for better connectivity.&lt;/p&gt;
&lt;p&gt;Warning - the mounting bracket on mine has sharp edges - I should give it a run over with a file next time I get it down. Currently I&#39;m wearing a fibreglass cast on my wrist and not going up ladders ...&lt;/p&gt;
&lt;p&gt;I appreciate that lots of locals seem to be building the mesh nodes out from parts, which I respect - it feels more in keeping with the resilience goals of the network. This unit worked out of the box (both as Meshtastic and later Meshcore) and is an easy place to start.&lt;/p&gt;
&lt;h2&gt;Wio Tracker L1 Pro&lt;/h2&gt;
&lt;img src=&quot;https://chris.bur.gs/media/seeed-wio-tracker.jpg&quot; alt=&quot;Product pic - Seeed WIO Tracker L1 Pro&quot; class=&quot;float-right ml-4 mb-4 w-72&quot; eleventy:widths=&quot;256,512&quot; sizes=&quot;16rem&quot; /&gt;
&lt;p&gt;I &lt;em&gt;want&lt;/em&gt; to like this one - cute little screen you can read messages on, joystick control. It has a decent antenna! The battery lasts ok, a few days. The joystick control ... works, but it&#39;s not a keyboard, so really you&#39;re as good without unless you want a very retro texting experience.&lt;/p&gt;
&lt;p&gt;The USB C connection doesn&#39;t really hold, meaning this one needs care to charge. With the better antenna it has greater reach, and I favoured it at first ... but then with charging issues forgot where I left it (plugged into a charger which &lt;em&gt;did&lt;/em&gt; work) for a month. During that time I started using Meshcore more; for incidental (misplacement) reasons this device is the last one on Meshtastic.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.seeedstudio.com/Wio-Tracker-L1-Pro-p-6454.html&quot;&gt;Seeed Studio - Wio Tracker L1 Pro&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Seeed T-1000e tracker card&lt;/h2&gt;
&lt;img src=&quot;https://chris.bur.gs/media/seeed-t1000e.jpg&quot; alt=&quot;Product pic - Seeed T-1000e tag&quot; class=&quot;float-right ml-4 mb-4 w-72&quot; eleventy:widths=&quot;256,512&quot; sizes=&quot;16rem&quot; /&gt;
&lt;p&gt;This was an extra when I purchased the above two; I needed to get the order north of a certain price to clear the free shipping offer.&lt;/p&gt;
&lt;p&gt;The battery on it seems to quit at about 50%. It has a &lt;a href=&quot;https://www.seeedstudio.com/USB-Cable-Type-A-to-4P-with-magnetic-type-Black-1000mm-p-5977.html&quot;&gt;magnetic charger connector&lt;/a&gt; which works great - but means you need to know where the charge cable is for that one device.&lt;/p&gt;
&lt;p&gt;The form factor is good, it&#39;s a sealed unit, so this one depends on use with a mobile phone or paired device.&lt;/p&gt;
&lt;h2&gt;Rak Wismesh tag&lt;/h2&gt;
&lt;img src=&quot;https://chris.bur.gs/media/rak-wismesh-tag.webp&quot; alt=&quot;Product pic - Rak Wismesh tag&quot; class=&quot;float-right ml-4 mb-4 w-72&quot; eleventy:widths=&quot;256,512&quot; sizes=&quot;16rem&quot; /&gt;
&lt;p&gt;This replaced the Seeed T-1000e. I got it from Ali Express. Lives in my bag, charges every few days, is my primary mesh node now. It has internal antenna only so you need to be within a km or two of a repeater - as for the T-1000e.&lt;/p&gt;
&lt;p&gt;Uses the same charger connector as the T-1000e, so now I have two of those cables ...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.aliexpress.com/item/1005009754254701.html&quot;&gt;Ali Express - Rak Wismesh tag&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Heltec v4&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://chris.bur.gs/media/heltec-v4-and-box.jpeg&quot;&gt;&lt;img src=&quot;https://chris.bur.gs/media/heltec-v4-and-box.jpeg&quot; alt=&quot;Heltec v4 and the handy plastic box it comes in&quot; class=&quot;float-right ml-4 mb-4 w-72&quot; eleventy:widths=&quot;256,512&quot; sizes=&quot;16rem&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I grabbed a couple of these to play with mesh networks from Linux systems. One is attached to a Raspberry Pi running HomeAssistant, the other is on another Pi running regular linux. Playing with using these to observe mesh (via the HASS unit) and message channels (the other).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.aliexpress.com/item/1005011756799047.html&quot;&gt;Ali Express - Heltec V4 OLED LoRa 32 Dev-Board ESP32-S3 SX1262 WiFi Bluetooth PSRAM 16MB&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These come with a handy box that&#39;ll do for a case, once you add a hole each for USB-C and antenna. The antenna shown here is not the one the device comes with.&lt;/p&gt;
&lt;h2&gt;More local mesh notes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;For me the appeal of mesh is about local-first networks, community and resilience.&lt;/li&gt;
&lt;li&gt;Discord is where lots of chat happens (yes, this is ironic; still, go first where community is)
&lt;ul&gt;
&lt;li&gt;In Ōtepoti Dunedin, I recommend the &lt;a href=&quot;https://discord.gg/dCX47Hvs&quot;&gt;Make Stuff Dunedin&lt;/a&gt; server (hop into #rf-stuff), and&lt;/li&gt;
&lt;li&gt;for Meshcore I recommend the &lt;a href=&quot;https://discord.gg/KtPRUjSE&quot;&gt;Meshcore Discord&lt;/a&gt; (look in &amp;quot;regional-channels&amp;quot;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Dunedin (and I hear, most of NZ) is using the &amp;quot;NZ (narrow)&amp;quot; settings - broadcast settings are not compatible, ie if you and your neighbour use different settings, you won&#39;t see each other &lt;em&gt;at all&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Some of these items may say &amp;quot;Meshtastic&amp;quot; in the product listing, I see Seeed are selling some pre-flashed with Meshcore now as well. To use Meshcore, obtain the device, then use the flasher at &lt;a href=&quot;https://meshcore.io/flasher&quot;&gt;https://meshcore.io/flasher&lt;/a&gt; ... it&#39;s the same hardware.&lt;/li&gt;
&lt;li&gt;Lots of folks on the mesh locally are HAM radio folks, so &lt;a href=&quot;https://www.zl4aa.org.nz/&quot;&gt;https://www.zl4aa.org.nz/&lt;/a&gt; also worth checking if you&#39;re in Otago / Southland.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of this is current at time of writing ... I just wanted to quickly share purchase links for the person who asked! None of these links are referral links (as best I can tell, anyway).&lt;/p&gt;
&lt;p&gt;You can probably find me on Meshcore as ChrisB and on Meshtastic as CMB0, in Discord I&#39;m @xurizaemon. I don&#39;t have a fancy callsign yet :)&lt;/p&gt;
&lt;p&gt;Corrections welcome!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Aureuo Marker 24 palette</title>
    <link href="https://chris.bur.gs/aureuo-marker-24-palette/"/>
    <updated>2026-04-04T00:00:00Z</updated>
    <id>https://chris.bur.gs/aureuo-marker-24-palette/</id>
    <content xml:lang="en" type="html">&lt;p&gt;A palette of the Aureuo Acrylic Marker 24pk. This set of pens is amazing, the colours are so thick and wet.&lt;/p&gt;
&lt;div class=&quot;palette-grid&quot;&gt;
  &lt;div class=&quot;white&quot;&gt;White&lt;/div&gt;
  &lt;div class=&quot;lightgrey&quot;&gt;Light grey&lt;/div&gt;
  &lt;div class=&quot;grey&quot;&gt;Grey&lt;/div&gt;
  &lt;div class=&quot;limegreen&quot;&gt;Lime green&lt;/div&gt;
  &lt;div class=&quot;brightyellow&quot;&gt;Bright Yellow&lt;/div&gt;
  &lt;div class=&quot;darkyellow&quot;&gt;Dark Yellow&lt;/div&gt;
  &lt;div class=&quot;lightbrown&quot;&gt;Light Brown&lt;/div&gt;
  &lt;div class=&quot;copper&quot;&gt;Copper&lt;/div&gt;
  &lt;div class=&quot;orange&quot;&gt;Orange&lt;/div&gt;
  &lt;div class=&quot;pink&quot;&gt;Pink&lt;/div&gt;
  &lt;div class=&quot;coral&quot;&gt;Coral&lt;/div&gt;
  &lt;div class=&quot;crimson&quot;&gt;Crimson&lt;/div&gt;
  &lt;div class=&quot;lilac&quot;&gt;Lilac&lt;/div&gt;
  &lt;div class=&quot;magenta&quot;&gt;Magenta&lt;/div&gt;
  &lt;div class=&quot;purple&quot;&gt;Purple&lt;/div&gt;
  &lt;div class=&quot;viridian&quot;&gt;Viridian&lt;/div&gt;
  &lt;div class=&quot;darkblue&quot;&gt;Dark Blue&lt;/div&gt;
  &lt;div class=&quot;ultramarineblue&quot;&gt;Ultramarine Blue&lt;/div&gt;
  &lt;div class=&quot;aquamarine&quot;&gt;Aquamarine&lt;/div&gt;
  &lt;div class=&quot;brightgreen&quot;&gt;Bright Green&lt;/div&gt;
  &lt;div class=&quot;green&quot;&gt;Green&lt;/div&gt;
  &lt;div class=&quot;darkgreen&quot;&gt;Dark Green&lt;/div&gt;
  &lt;div class=&quot;gold&quot;&gt;Gold&lt;/div&gt;
  &lt;div class=&quot;black&quot;&gt;Black&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/aureuo-marker-24/aureuo-acrylic-markers-24.jpeg&quot; alt=&quot;Aureuo acrylic markers 24 pack&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://chris.bur.gs/media/Aureuo-Marker-24.gpl&quot;&gt;Aureuo-Marker-24.gpl&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.gordonharris.co.nz/collections/all-markers-and-pens/products/aureuo-acrylic-marker-set-24&quot;&gt;Gordon Harris product page&lt;/a&gt; lists the following colours: Crimson, Dark Yellow, Green, Purple, Black, Grey, White, Viridian, Light Brown, Ultramarine Blue, Gold, Orange, Lilac, Lime Green, Dark Green, Pink, Light Grey, Magenta, Light Blue, Red, Dark Blue, Bright Greren, Bright Yellow.&lt;/p&gt;
&lt;p&gt;I have a copper colour which is not in the Gordon Harris list (AFAICT), and one of the greys is metallic-silver. So I&#39;ve mostly used names from that website, but swapped a few where I didn&#39;t feel they fit quite, hence Coral, Aquamarine.&lt;/p&gt;
&lt;p&gt;The colour codes here are sampled from a set of swatches I made, pics taken with a ten year old iPhone. This page pretty much came of me trying to work out what the colour names were, and making a fun little palette output from some front matter data in Eleventy.&lt;/p&gt;
&lt;style&gt;

  
  .palette-grid div.white {
    border-color: rgb(221, 229. 233);
    background-image: url(&#39;/media/aureuo-marker-24/nibs/white.png&#39;), linear-gradient(white, white);
    
  }
  .palette-grid div.white::before {
    background-image: url(&#39;/media/aureuo-marker-24/swatch/white.png&#39;);
  }

  
  .palette-grid div.lightgrey {
    border-color: rgb(177, 186, 197);
    background-image: url(&#39;/media/aureuo-marker-24/nibs/lightgrey.png&#39;), linear-gradient(white, white);
    
  }
  .palette-grid div.lightgrey::before {
    background-image: url(&#39;/media/aureuo-marker-24/swatch/lightgrey.png&#39;);
  }

  
  .palette-grid div.grey {
    border-color: rgb(122, 138, 157);
    background-image: url(&#39;/media/aureuo-marker-24/nibs/grey.png&#39;), linear-gradient(white, white);
    
  }
  .palette-grid div.grey::before {
    background-image: url(&#39;/media/aureuo-marker-24/swatch/grey.png&#39;);
  }

  
  .palette-grid div.limegreen {
    border-color: rgb(174, 191, 100);
    background-image: url(&#39;/media/aureuo-marker-24/nibs/limegreen.png&#39;), linear-gradient(white, white);
    
  }
  .palette-grid div.limegreen::before {
    background-image: url(&#39;/media/aureuo-marker-24/swatch/limegreen.png&#39;);
  }

  
  .palette-grid div.brightyellow {
    border-color: rgb(233, 209, 81);
    background-image: url(&#39;/media/aureuo-marker-24/nibs/brightyellow.png&#39;), linear-gradient(white, white);
    
  }
  .palette-grid div.brightyellow::before {
    background-image: url(&#39;/media/aureuo-marker-24/swatch/brightyellow.png&#39;);
  }

  
  .palette-grid div.darkyellow {
    border-color: rgb(217, 154, 64);
    background-image: url(&#39;/media/aureuo-marker-24/nibs/darkyellow.png&#39;), linear-gradient(white, white);
    
  }
  .palette-grid div.darkyellow::before {
    background-image: url(&#39;/media/aureuo-marker-24/swatch/darkyellow.png&#39;);
  }

  
  .palette-grid div.lightbrown {
    border-color: rgb(234, 173, 152);
    background-image: url(&#39;/media/aureuo-marker-24/nibs/lightbrown.png&#39;), linear-gradient(white, white);
    
  }
  .palette-grid div.lightbrown::before {
    background-image: url(&#39;/media/aureuo-marker-24/swatch/lightbrown.png&#39;);
  }

  
  .palette-grid div.copper {
    border-color: rgb(182, 131, 107);
    background-image: url(&#39;/media/aureuo-marker-24/nibs/copper.png&#39;), linear-gradient(white, white);
    
  }
  .palette-grid div.copper::before {
    background-image: url(&#39;/media/aureuo-marker-24/swatch/copper.png&#39;);
  }

  
  .palette-grid div.orange {
    border-color: rgb(223, 127, 58);
    background-image: url(&#39;/media/aureuo-marker-24/nibs/orange.png&#39;), linear-gradient(white, white);
    
  }
  .palette-grid div.orange::before {
    background-image: url(&#39;/media/aureuo-marker-24/swatch/orange.png&#39;);
  }

  
  .palette-grid div.pink {
    border-color: rgb(203, 78, 144);
    background-image: url(&#39;/media/aureuo-marker-24/nibs/pink.png&#39;), linear-gradient(white, white);
    
  }
  .palette-grid div.pink::before {
    background-image: url(&#39;/media/aureuo-marker-24/swatch/pink.png&#39;);
  }

  
  .palette-grid div.coral {
    border-color: rgb(227, 64, 104);
    background-image: url(&#39;/media/aureuo-marker-24/nibs/coral.png&#39;), linear-gradient(white, white);
    
  }
  .palette-grid div.coral::before {
    background-image: url(&#39;/media/aureuo-marker-24/swatch/coral.png&#39;);
  }

  
  .palette-grid div.crimson {
    border-color: rgb(184, 59, 69);
    background-image: url(&#39;/media/aureuo-marker-24/nibs/crimson.png&#39;), linear-gradient(white, white);
    
  }
  .palette-grid div.crimson::before {
    background-image: url(&#39;/media/aureuo-marker-24/swatch/crimson.png&#39;);
  }

  
  .palette-grid div.lilac {
    border-color: rgb(204, 113, 183);
    background-image: url(&#39;/media/aureuo-marker-24/nibs/lilac.png&#39;), linear-gradient(white, white);
    
  }
  .palette-grid div.lilac::before {
    background-image: url(&#39;/media/aureuo-marker-24/swatch/lilac.png&#39;);
  }

  
  .palette-grid div.magenta {
    border-color: rgb(188, 54, 113);
    background-image: url(&#39;/media/aureuo-marker-24/nibs/magenta.png&#39;), linear-gradient(white, white);
    color: #ccc;
  }
  .palette-grid div.magenta::before {
    background-image: url(&#39;/media/aureuo-marker-24/swatch/magenta.png&#39;);
  }

  
  .palette-grid div.purple {
    border-color: rgb(75, 35, 105);
    background-image: url(&#39;/media/aureuo-marker-24/nibs/purple.png&#39;), linear-gradient(white, white);
    color: #ccc;
  }
  .palette-grid div.purple::before {
    background-image: url(&#39;/media/aureuo-marker-24/swatch/purple.png&#39;);
  }

  
  .palette-grid div.viridian {
    border-color: rgb(1, 101, 115);;
    background-image: url(&#39;/media/aureuo-marker-24/nibs/viridian.png&#39;), linear-gradient(white, white);
    color: #ccc;
  }
  .palette-grid div.viridian::before {
    background-image: url(&#39;/media/aureuo-marker-24/swatch/viridian.png&#39;);
  }

  
  .palette-grid div.darkblue {
    border-color: rgb(0, 99, 174);
    background-image: url(&#39;/media/aureuo-marker-24/nibs/darkblue.png&#39;), linear-gradient(white, white);
    color: #ccc;
  }
  .palette-grid div.darkblue::before {
    background-image: url(&#39;/media/aureuo-marker-24/swatch/darkblue.png&#39;);
  }

  
  .palette-grid div.ultramarineblue {
    border-color: rgb(9, 45, 119);
    background-image: url(&#39;/media/aureuo-marker-24/nibs/ultramarineblue.png&#39;), linear-gradient(white, white);
    color: #ccc;
  }
  .palette-grid div.ultramarineblue::before {
    background-image: url(&#39;/media/aureuo-marker-24/swatch/ultramarineblue.png&#39;);
  }

  
  .palette-grid div.aquamarine {
    border-color: rgb(100, 188, 207);
    background-image: url(&#39;/media/aureuo-marker-24/nibs/aquamarine.png&#39;), linear-gradient(white, white);
    
  }
  .palette-grid div.aquamarine::before {
    background-image: url(&#39;/media/aureuo-marker-24/swatch/aquamarine.png&#39;);
  }

  
  .palette-grid div.brightgreen {
    border-color: rgb(26, 106, 68);
    background-image: url(&#39;/media/aureuo-marker-24/nibs/brightgreen.png&#39;), linear-gradient(white, white);
    color: #ccc;
  }
  .palette-grid div.brightgreen::before {
    background-image: url(&#39;/media/aureuo-marker-24/swatch/brightgreen.png&#39;);
  }

  
  .palette-grid div.green {
    border-color: rgb(13, 110, 91);
    background-image: url(&#39;/media/aureuo-marker-24/nibs/green.png&#39;), linear-gradient(white, white);
    color: #ccc;
  }
  .palette-grid div.green::before {
    background-image: url(&#39;/media/aureuo-marker-24/swatch/green.png&#39;);
  }

  
  .palette-grid div.darkgreen {
    border-color: rgb(0, 105, 98);
    background-image: url(&#39;/media/aureuo-marker-24/nibs/darkgreen.png&#39;), linear-gradient(white, white);
    color: #ccc;
  }
  .palette-grid div.darkgreen::before {
    background-image: url(&#39;/media/aureuo-marker-24/swatch/darkgreen.png&#39;);
  }

  
  .palette-grid div.gold {
    border-color: rgb(116, 108, 78);
    background-image: url(&#39;/media/aureuo-marker-24/nibs/gold.png&#39;), linear-gradient(white, white);
    color: #ccc;
  }
  .palette-grid div.gold::before {
    background-image: url(&#39;/media/aureuo-marker-24/swatch/gold.png&#39;);
  }

  
  .palette-grid div.black {
    border-color: rgb(27, 27, 35);;
    background-image: url(&#39;/media/aureuo-marker-24/nibs/black.png&#39;), linear-gradient(white, white);
    color: #ccc;
  }
  .palette-grid div.black::before {
    background-image: url(&#39;/media/aureuo-marker-24/swatch/black.png&#39;);
  }


.palette-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 4px;
}

.palette-grid div {
  color: black;
  padding: 8px;
  font-size: 0.85rem;
  font-weight: bold;
  border-width: 5px;
  position: relative;
  overflow: hidden;
  min-height: 100px;
  background-size: 20% 90%;
  background-repeat: no-repeat;
  background-position: calc(100% - 10px) calc(100% + 5px);
  z-index: -1;
  padding-right: 50px;
  line-height: 115%;
}

.palette-grid div::before {
  content: &#39;&#39;;
  position: absolute;
  inset: 0;
  left: 0;
  width: 66.666%;
  background-size: cover;
  background-position: center;
  z-index: -2;
}

.palette-grid div.dark {
  color: #ccc;
}

.palette-grid code {
  display: block;
  font-size: 0.75rem;
  background-color: rgb(200, 200, 200);
  margin-top: 4px;
  font-weight: normal;
}
&lt;/style&gt;
</content>
  </entry>
  <entry>
    <title>So, you should know about: SearXNG</title>
    <link href="https://chris.bur.gs/so-you-should-know-about-searxng/"/>
    <updated>2026-02-18T00:00:00Z</updated>
    <id>https://chris.bur.gs/so-you-should-know-about-searxng/</id>
    <content xml:lang="en" type="html">&lt;p&gt;It&#39;s interesting to reflect how much we&#39;ve come to depend on tools like search engines in our daily lives, and I guess I&lt;br /&gt;
could be grateful to Google for making its centrality so stark when they introduced their AI Overviews - sometimes on&lt;br /&gt;
point, and quite often outlandishly wrong.&lt;/p&gt;
&lt;p&gt;For a couple of months I switched to Duck Duck Go, hoping for a replacement which worked like I was used to. But it&lt;br /&gt;
did not hit the same, I looked around for more options, and found SearXNG. I&#39;m impressed.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;My searches now hit several engines &lt;em&gt;including&lt;/em&gt; Google, all of which might withdraw service at any time, and the results I get are an aggregation of what&#39;s returned.&lt;/li&gt;
&lt;li&gt;There&#39;s no AI Overview presented at all - great! The results feel much better - more like search from a few years ago.&lt;/li&gt;
&lt;li&gt;There&#39;s no ads. There&#39;s no ads. Glorious.&lt;/li&gt;
&lt;li&gt;Results come from a number of search engines, so it&#39;s &lt;em&gt;somewhat&lt;/em&gt; more resilient, but I don&#39;t think of it as an entirely dependable long-term solution.&lt;/li&gt;
&lt;li&gt;If or when it breaks for some reason, or essential engines block access, I might have to find an alternative. Oh well.&lt;/li&gt;
&lt;li&gt;It doesn&#39;t try to innovate in UI, I guess it&#39;s &amp;quot;conservative&amp;quot; in that it presents as a fairly ordinary search engine.&lt;/li&gt;
&lt;li&gt;It&#39;s capable, it does the job, and it&#39;s far less annoying than Google had gotten.&lt;/li&gt;
&lt;li&gt;It&#39;s open source, so you can review its internals, run it yourself, and contribute fixes or extend with search providers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&#39;m happy to have found it, even if it doesn&#39;t last forever - nothing does. It&#39;s the best alternative I found.&lt;/p&gt;
&lt;h2&gt;Using SearXNG&lt;/h2&gt;
&lt;p&gt;You don&#39;t have to run SearXNG yourself: there are public instances you can use listed at &lt;a href=&quot;https://searx.space/&quot;&gt;https://searx.space/&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Running SearXNG&lt;/h2&gt;
&lt;p&gt;I&#39;m running an instance myself, behind SSL and basic auth so it&#39;s All Mine and Only Just Mine, and it&#39;s working great.&lt;/p&gt;
&lt;p&gt;Instructions at &lt;a href=&quot;https://docs.searxng.org/&quot;&gt;https://docs.searxng.org/&lt;/a&gt; for the method that suits you. You don&#39;t really care how I run it :)&lt;/p&gt;
&lt;p&gt;Why run a copy myself? I don&#39;t have to wonder who&#39;s running the instance I&#39;m using, or what their privacy policy says,&lt;br /&gt;
or whether they might get bored of running it. My instance is private, so if I stop using it, I can turn it off.&lt;/p&gt;
&lt;h2&gt;Thoughts on organisational usage&lt;/h2&gt;
&lt;p&gt;For an organisation with a variety of internal systems, I think it has potential as an organisational search engine.&lt;br /&gt;
Imagine if your people could use a single search interface, and get results from both the internet at large &lt;em&gt;and&lt;/em&gt; your&lt;br /&gt;
Sharepoint, wiki, or other systems. That seems like a great deal, even if it meant rolling your own plugin to support&lt;br /&gt;
whatever weird cheesy systems you needed to search.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Sandymount track</title>
    <link href="https://chris.bur.gs/sandymount-track/"/>
    <updated>2026-02-16T00:00:00Z</updated>
    <id>https://chris.bur.gs/sandymount-track/</id>
    <content xml:lang="en" type="html">&lt;p&gt;We took a short walk along &lt;a href=&quot;https://www.dunedin.govt.nz/community-facilities/walking-tracks/peninsula-tracks/sandymount-track&quot;&gt;Sandymount Track&lt;/a&gt; to Sandfly Bay this weekend.&lt;/p&gt;
&lt;p&gt;To test out a possible backpack I loaded it up and took it for the walk. It&#39;s a gentle track but a long steady (sandy) incline on the return.&lt;/p&gt;
&lt;p&gt;There were lots of lepidoptera amongst the track and plants - we saw &lt;a href=&quot;https://inaturalist.nz/observations/338846630&quot;&gt;Coppers&lt;/a&gt;, a &lt;a href=&quot;https://inaturalist.nz/observations/338850195&quot;&gt;Magpie Moth&lt;/a&gt;, and a &lt;a href=&quot;https://inaturalist.nz/observations/338846623&quot;&gt;Red Admiral&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Gitlab CI Local</title>
    <link href="https://chris.bur.gs/gitlab-ci-local/"/>
    <updated>2026-02-15T00:00:00Z</updated>
    <id>https://chris.bur.gs/gitlab-ci-local/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I&#39;m such a fan of &lt;code&gt;firecow/gitlab-ci-local&lt;/code&gt;. It&#39;s a CLI tool which allows to execute Gitlab CI pipelines locally, without needing to push changes through Gitlab. For fast local development of pipelines, or targeted testing of jobs configured in pipelines, it&#39;s an amazing speed boost.&lt;/p&gt;
&lt;p&gt;For example, in a local project I might have a job cypress, which perhaps depends on some prior build and install tasks. With gitlab-ci-local installed, from a CLI within the project directory I can execute -&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gitlab-ci-local --needs cypress
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to see the CI job and its dependencies run locally, in either docker or shell runners.&lt;/p&gt;
&lt;p&gt;The tool tracks any files which are tracked by Git. You don&#39;t need to commit changes; it&#39;s sufficient that the files are tracked, and gitlab-ci-local will use the local versions of the files.&lt;/p&gt;
&lt;p&gt;Artifacts from the CI jobs are preserved in &lt;project&gt;/.gitlab-ci-local/artifacts by default, along with a handful of other useful directories which track output&lt;/project&gt;&lt;/p&gt;
&lt;p&gt;If the job needs some additional CI variables set, I can pass --variable PATH=/some/path or add a &lt;project&gt;/.gitlab-ci-local-variables.yml:&lt;/project&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
# CI_PIPELINE_SOURCE: merge_request_event
XDEBUG_CONFIG: client_host=192.168.178.56
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;gitlab-ci-local&lt;/code&gt; functions as a containerised testing environment with configuration syntax inherited from Gitlab CI. Since I&#39;m already using that for most projects, adding gitlab-ci-local to the mix is fantastic - fast, no-push execution of targeted CI jobs, which I can push to Gitlab once I&#39;m happy with the local tests.&lt;/p&gt;
&lt;h2&gt;Disk usage!&lt;/h2&gt;
&lt;p&gt;One thing to watch for with this tool is disk consumption via Docker volumes. When cleaning disk usage on my laptop, including deleting ~800 Docker volumes, 200 of which had names like gcl-{jobname}-{NNN}-(build|tmp) (eg gcl-cypress-126182-build, gcl-cypress-126182-tmp). Just normal Docker things really, but gitlab-ci-local was a bit of a multiplying factor ...&lt;/p&gt;
&lt;h2&gt;Save steps!&lt;/h2&gt;
&lt;p&gt;Useful detail to know: if you are changing the job &amp;quot;cypress&amp;quot;, and it depends on the job &amp;quot;composer build&amp;quot;&lt;/p&gt;
&lt;p&gt;You can run the job gitlab-ci-local --needs cypress first, to execute the build job&lt;/p&gt;
&lt;p&gt;Then you can make changes to your code, and run just gitlab-ci-local cypress to skip the build job; if you&#39;re not changing composer.lock then the build job is skippable, and this lets you run pipelines again even faster than CI would.&lt;/p&gt;
&lt;h2&gt;drupal-ci-local&lt;/h2&gt;
&lt;p&gt;Documentation &lt;a href=&quot;https://project.pages.drupalcode.org/gitlab_templates/info/setup/&quot;&gt;here&lt;/a&gt; for Drupal Gitlab Templates.&lt;/p&gt;
&lt;p&gt;If you need extras:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;drupal-ci-local --variable _GITLAB_TEMPLATES_REF=default-ref phpunit
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>Importing image directories to Eleventy</title>
    <link href="https://chris.bur.gs/importing-image-directories-to-eleventy/"/>
    <updated>2026-02-15T00:00:00Z</updated>
    <id>https://chris.bur.gs/importing-image-directories-to-eleventy/</id>
    <content xml:lang="en" type="html">&lt;p&gt;My previous attempt at getting images into Eleventy didn&#39;t work out. I mean, it &lt;em&gt;worked&lt;/em&gt;, but I wanted something different from what I&#39;d built.&lt;/p&gt;
&lt;p&gt;That package might be useful (eg if I wanted to publish all my cloud and sunset photos), but mostly I want to share a handful of photos like &amp;quot;Sandymount walk&amp;quot;, which is a different use case.&lt;/p&gt;
&lt;p&gt;Previous workflow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Put images into Immich, manage/organise them there (eg into an album)&lt;/li&gt;
&lt;li&gt;Get the album or media UUID out of Immich (manual step)&lt;/li&gt;
&lt;li&gt;Use an &lt;code&gt;or&lt;/code&gt; tag in my content&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;New workflow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Copy images into &lt;code&gt;./src/media/&lt;/code&gt; or directly to &lt;code&gt;./public/media/&lt;/code&gt; (depends if I want to put the files in Git)&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;npm run image:ingest &amp;lt;ingest-directory&amp;gt; &amp;lt;output-directory&amp;gt;&lt;/code&gt; (eg &lt;code&gt;npm run image:ingest ./src/media/sandymount-walk ./src/photos/sandymount-walk&lt;/code&gt;, where &lt;code&gt;src/media/sandymount-walk&lt;/code&gt; is the directory containing the images, and &lt;code&gt;src/photos/sandymount-walk&lt;/code&gt; is the generated markdown files)&lt;/li&gt;
&lt;li&gt;Edit markdown content which is pre-filled with image tags&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is still pretty manual, but simplifies versus having content across disparate systems.&lt;/p&gt;
&lt;p&gt;I generally &lt;code&gt;echo &#39;*&#39; &amp;gt; src/media/&amp;lt;directory&amp;gt;/.gitignore&lt;/code&gt; and handle the media uploads with &lt;code&gt;rsync&lt;/code&gt;, to keep the large files out of the repo.&lt;/p&gt;
&lt;p&gt;Need to populate &amp;quot;description&amp;quot; in frontmatter too as that can provide alt= for the images.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>2015 Corolla Fielder Hybrid G Aerotourer</title>
    <link href="https://chris.bur.gs/fielder/"/>
    <updated>2026-01-25T00:00:00Z</updated>
    <id>https://chris.bur.gs/fielder/</id>
    <content xml:lang="en" type="html">&lt;p&gt;My old Toyota Carib bit the dust. We got by for a few months but eventually I had to bite the bullet and get a new car. This is what I got - a 2015 Toyota Corolla Fielder Hybrid G Aerotourer, imported from Japan.&lt;/p&gt;
&lt;p&gt;Now I had questions!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How to change the time on the dashboard? I could not find it in the dashboard settings menu (despite being able to find and change the date there). Answer: There&#39;s a pair of odometer-reset looking buttons on the speedometer, they are HH and MM!&lt;/li&gt;
&lt;li&gt;Where do I find a product manual? &lt;a href=&quot;https://manual.toyota.jp/pdf/corollafielder/corollafielder_hybrid_201503.pdf&quot;&gt;&lt;code&gt;corollafielder_hybrid_201503.pdf&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;What&#39;s with the SIM card slot in the head unit? (miniB-CAS, for decoding TV, or is it T-Connect?)&lt;/li&gt;
&lt;li&gt;What&#39;s happening with the map update microSD card?&lt;/li&gt;
&lt;li&gt;What&#39;s Nanoe on the heater? There&#39;s a button labelled &amp;quot;Nanoe&amp;quot; - this was unfamiliar. It&#39;s &lt;a href=&quot;https://en.wikipedia.org/wiki/Nanoelectric_heater&quot;&gt;a nano-electric heater&lt;/a&gt; that makes ozone by electrifying the air? &lt;a href=&quot;https://www.panasonic.com/nz/hvac/nanoe/consumer/faq.html&quot;&gt;Panasonic&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;p225: Equipment inside RageJill (some translations)&lt;/h2&gt;
&lt;p&gt;Here&#39;s the product manual for my car (original and two rough translations). The translations are imperfect but useful if you can read through them.&lt;/p&gt;
&lt;div class=&quot;relative left-1/2 w-screen -translate-x-1/3 max-w-none ml-[5%] pr-[15rem]&quot;&gt;
  &lt;div class=&quot;grid grid-cols-1 gap-4 lg:grid-cols-3&quot;&gt;
    &lt;div&gt;
      &lt;a href=&quot;https://chris.bur.gs/media/fielder-2015/corollafielder_hybrid_201503.pdf&quot;&gt;Original version in Japanese&lt;/a&gt;&lt;br /&gt;
      &lt;embed src=&quot;https://chris.bur.gs/media/fielder-2015/corollafielder_hybrid_201503.pdf&quot; type=&quot;application/pdf&quot; class=&quot;h-[80vh] w-full&quot; /&gt;
    &lt;/div&gt;
    &lt;div&gt;
      &lt;a href=&quot;https://chris.bur.gs/media/fielder-2015/corollafielder_hybrid_201503-mono.pdf&quot;&gt;English translation A&lt;/a&gt; (via &lt;a href=&quot;https://github.com/PDFMathTranslate/PDFMathTranslate&quot;&gt;PDFMathTranslate&lt;/a&gt;)&lt;br /&gt;
      &lt;embed src=&quot;https://chris.bur.gs/media/fielder-2015/corollafielder_hybrid_201503-mono.pdf&quot; type=&quot;application/pdf&quot; class=&quot;h-[80vh] w-full&quot; /&gt;
    &lt;/div&gt;
    &lt;div&gt;
      &lt;a href=&quot;https://chris.bur.gs/media/fielder-2015/corollafielder_hybrid_201503.google_translate.pdf&quot;&gt;English translation B&lt;/a&gt; (via &lt;a href=&quot;https://translate.google.com/&quot;&gt;Google Translate&lt;/a&gt;)&lt;br /&gt;
      &lt;embed src=&quot;https://chris.bur.gs/media/fielder-2015/corollafielder_hybrid_201503.google_translate.pdf&quot; type=&quot;application/pdf&quot; class=&quot;h-[80vh] w-full&quot; /&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;Locating manual PDFs and other docs:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://toyota.com/&quot;&gt;Toyota.com&lt;/a&gt; links me from &lt;a href=&quot;https://www.toyota.com/owners/warranty-owners-manuals/&quot;&gt;Owners&#39; Manuals&lt;/a&gt; to &lt;a href=&quot;https://assets.sia.toyota.com/publications/en/om-s/OM12G79U/pdf/OM12G79U.pdf&quot;&gt;2015 Toyota Corolla owner&#39;s manual&lt;/a&gt;, but this is an AccessDenied XML error (for me, in Aotearoa). Same for &lt;a href=&quot;https://assets.sia.toyota.com/publications/en/om-s/OM12F00U/pdf/OM12F00U.pdf&quot;&gt;2014 Corolla Owner&#39;s Manual&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://toyota.jp/&quot;&gt;toyota.jp&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;⚠️ &lt;a href=&quot;http://toyota.jp/&quot;&gt;toyota.jp&lt;/a&gt; didn&#39;t work for me in firefox, used Chrome 🤷&lt;/li&gt;
&lt;li&gt;registration + use of in-browser translation got me to &lt;a href=&quot;https://manual.toyota.jp/corollafielder/&quot;&gt;https://manual.toyota.jp/corollafielder/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;allowed me to find &lt;a href=&quot;https://manual.toyota.jp/pdf/corollafielder/corollafielder_hybrid_201503.pdf&quot;&gt;corollafielder_hybrid_201503.pdf&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Found some owner&#39;s manuals from &lt;a href=&quot;https://en.toyota-club.eu/manuals&quot;&gt;https://en.toyota-club.eu/manuals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Other PDFs I picked up along the way:
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://chris.bur.gs/media/fielder-2015/2010-corolla-axio-owners-manual-jpn.pdf&quot;&gt;2010-corolla-axio-owners-manual-jpn.pdf&lt;/a&gt; - 2010 Corolla Axio&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chris.bur.gs/media/fielder-2015/2012-toyota-corolla-fielder-jpn.pdf&quot;&gt;2012-toyota-corolla-fielder-jpn.pdf&lt;/a&gt; is a product brochure - has details of the Fielder spec variations (eg G, Aerotourer, WXB)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://manual.toyota.jp/pdf/corollafielder/corollafielder_hybrid_201308.pdf&quot;&gt;corollafielder_hybrid_201308.pdf&lt;/a&gt; - 2013 manual from &lt;a href=&quot;http://toyota.jp/&quot;&gt;toyota.jp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chris.bur.gs/media/fielder-2015/2014-corolla-owners-manual-us.pdf.pdf&quot;&gt;2014-corolla-owners-manual-us.pdf.pdf&lt;/a&gt; from &lt;a href=&quot;https://s3.amazonaws.com/toyota.site.p01/toyota/manuals/OM12F00U/OM12F00U.pdf&quot;&gt;here&lt;/a&gt; is an owner&#39;s manual for the US 2014 model&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chris.bur.gs/media/fielder-2015/corolla-2015-owners-manual-us-canada.pdf&quot;&gt;corolla-2015-owners-manual-us-canada.pdf&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.toyota.com/owners/warranty-owners-manuals/vehicle/corolla/2015/&quot;&gt;https://www.toyota.com/owners/warranty-owners-manuals/vehicle/corolla/2015/&lt;/a&gt; not much of use here&lt;/li&gt;
&lt;li&gt;Various &lt;em&gt;somewhat&lt;/em&gt; helpful manuals sites that didn&#39;t have the correct model docs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Reading the PDFs in English&lt;/h2&gt;
&lt;p&gt;All the docs I had so far were in Japanese. As for the dashboard and head unit, I could read the text on screen using my mobile + Google Translate app in camera mode, but for a 388 page PDF that&#39;s not practical.&lt;/p&gt;
&lt;p&gt;I wanted an English translation that was passably readable. My first attempt to use Google Translate didn&#39;t work out, producing a PDF with garbage emoji where I hoped for text.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Byaidu/PDFMathTranslate&quot;&gt;https://github.com/Byaidu/PDFMathTranslate&lt;/a&gt; is a translation tool for PDFs. Followed Docker instructions to bring it up at &lt;a href=&quot;http://localhost:7860/&quot;&gt;http://localhost:7860/&lt;/a&gt; and used that interface to convert - this worked tidily, and showed me what I&#39;d done wrong the first time with Google Translate.&lt;/p&gt;
&lt;p&gt;I went back for a second pass at Google Translate (mostly for comparison now). Google Translate has a 300 page limit, so I split the original into two files:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pdftk corollafielder_hybrid_201503.pdf cat 1-200 output corollafielder_hybrid_201503.part1.pdf
pdftk corollafielder_hybrid_201503.pdf cat 201-388 output corollafielder_hybrid_201503.part2.pdf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then I used &lt;a href=&quot;https://translate.google.com/?sl=ja&amp;amp;tl=en&amp;amp;op=docs&quot;&gt;Google Translate&lt;/a&gt; to translate the two files from Japanese to English, combining them back into a single PDF.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pdftk ~/Downloads/corollafielder_hybrid_201503.part1-1.pdf  ~/Downloads/corollafielder_hybrid_201503.part2.pdf cat output src/media/fielder-2015/corollafielder_hybrid_201503.google_translate.pdf
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://chris.bur.gs/media/fielder-2015/corollafielder_hybrid_201503.google_translate.pdf&quot;&gt;corollafielder_hybrid_201503.google_translate.pdf&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;corollafielder_hybrid_201503&lt;/strong&gt; is my car&#39;s manual. I wanted to read this but it&#39;s in Japanese.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the car I had been using my mobile with Google Translate app in camera mode to read menus, and this works for viewing the PDF manual also (but slow to read without search).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;File reads as &amp;quot;encrypted&amp;quot; to &lt;code&gt;ocrmypdf&lt;/code&gt; so I first opened it in Evince and printed to PDF. This gave me &lt;a href=&quot;https://chris.bur.gs/media/fielder-2015/corollafielder_hybrid_201503.printed.pdf&quot;&gt;corollafielder_hybrid_201503.printed.pdf&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then I was able to use OCRmyPDF to OCR the PDF: &lt;code&gt;ocrmypdf --redo-ocr --language jpn 2010-corolla-axio-owners-manual-jpn.printed.pdf 2010-corolla-axio-owners-manual-ocr.pdf&lt;/code&gt;) to obtain &lt;a href=&quot;https://chris.bur.gs/media/fielder-2015/corollafielder_hybrid_201503.ocr.pdf&quot;&gt;corollafielder_hybrid_201503.orc.pdf&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Also Tesseract? &lt;code&gt;tesseract 2010-corolla-axio-owners-manual-jpn.pdf 2010-corolla-axio-owners-manual-eng.pdf -l jpn&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next step to try and translate the PDF to English,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Byaidu/PDFMathTranslate&quot;&gt;https://github.com/Byaidu/PDFMathTranslate&lt;/a&gt; is a translation tool for PDFs. Followed Docker instructions to bring it up at &lt;a href=&quot;http://localhost:7860/&quot;&gt;http://localhost:7860/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Also Google Translate. won&#39;t do PDFs of more than 300 pages so we split it into two files.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;ocrmypdf --redo-ocr --language jpn corollafielder_hybrid_201503.printed.pdf corollafielder_hybrid_201503.ocr.pdf
pdftk corollafielder_hybrid_201503.ocr.pdf cat 1-200 output corollafielder_hybrid_201503.ocr-part1.pdf
pdftk corollafielder_hybrid_201503.ocr.pdf cat 201-388 output corollafielder_hybrid_201503.ocr-part2.pdf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This output PDFs which looked like this (PDFTranslate first, then Google Translate):&lt;/p&gt;
&lt;div class=&quot;grid grid-cols-1 gap-4 lg:grid-cols-2&quot;&gt;
  &lt;img src=&quot;https://chris.bur.gs/media/fielder-2015/translation%20errors%202026-01-26%2007-49-20.png&quot; class=&quot;w-full&quot; alt=&quot;Translated via PDFTranslate&quot; /&gt;
  &lt;img src=&quot;https://chris.bur.gs/media/fielder-2015/translation%20errors%202026-01-26%2007-50-21.png&quot; class=&quot;w-full&quot; alt=&quot;Translated via Google Translate&quot; /&gt;
&lt;/div&gt;
&lt;p&gt;Maybe no available fonts for the English translation?&lt;/p&gt;
&lt;p&gt;Tried PDFMathTranslate again with the original PDF, without my &lt;code&gt;ocrmypdf&lt;/code&gt; excursion, that worked great.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://chris.bur.gs/media/fielder-2015/corollafielder_hybrid_201503-mono.pdf&quot;&gt;corollafielder_hybrid_201503-mono.pdf&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Nanoe&lt;/h2&gt;
&lt;h2&gt;Head unit&lt;/h2&gt;
&lt;p&gt;When I drove the car first, my notes included &amp;quot;seems ok, stereo yuck&amp;quot;. I withdraw the latter - I think the NSZN-W64T head unit is fine. It plays CDs, radio, bluetooth audio, and has AUX input and microSD and SD card slots. (Haven&#39;t tested the cards; CDs work fine.)&lt;/p&gt;
&lt;p&gt;The stereo works fine, and I would love to know what else is possible. I like the stereo&#39;s vibe as-is, can deal with (appreciate, even) labels in Japanese, and will not be rushing out to get a fancy Android head unit.&lt;/p&gt;
&lt;p&gt;The head unit connects to WiFi (&amp;quot;AISIN CORPORATION&amp;quot; NIC), and has a Gracenote logo when playing CDs. I see that the stereo makes some HTTP requests when WiFi is configured, so one next step will be to capture the traffic to look at the request (eg user agent clues). Here&#39;s an nmap of the head unit connected to home wifi, with a host of &amp;quot;a.b.c.d&amp;quot;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Nmap scan report for a.b.c.d
Host is up (0.078s latency).
Not shown: 997 filtered tcp ports (no-response)
PORT     STATE  SERVICE
9001/tcp closed tor-orport
9002/tcp closed dynamid
9003/tcp closed unknown
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Head unit: locked, update&lt;/h2&gt;
&lt;p&gt;After three months, the head unit got into a locked mode - looks something like the below. This was annoying. I did not want to buy an Android head unit, I just wanted my factory unit back. It took two weeks for me to get around to doing some research then taking a laptop out to the car.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/NSZN-W64T-locked.jpg&quot; alt=&quot;NSZN-W64T - locked state&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Found a solution (amidst a LOT of YouTube videos selling services that would do the same):&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Power on the car, so the head unit is powered.&lt;/li&gt;
&lt;li&gt;Hold the PWR button down.&lt;/li&gt;
&lt;li&gt;Turn the headlights on and off three times.&lt;/li&gt;
&lt;li&gt;Follow the menu items in &lt;a href=&quot;https://www.youtube.com/watch?v=QywTr9Kp4mU&quot;&gt;this video&lt;/a&gt; to access the ERC input screen.&lt;/li&gt;
&lt;li&gt;Use &lt;a href=&quot;https://cyrus07424.github.io/ercCalculator/&quot;&gt;Cyrus&#39;s ERC calculator&lt;/a&gt; to obtain an ERC code for the head unit&#39;s serial (displayed onscreen).&lt;/li&gt;
&lt;li&gt;Enter the code on the ERC input screen.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once this was done I navigated through the &amp;quot;Quick Setup&amp;quot; menu (translating what I saw using my phone) to configure the stereo. Boom!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>More noise</title>
    <link href="https://chris.bur.gs/more-noise/"/>
    <updated>2025-10-19T00:00:00Z</updated>
    <id>https://chris.bur.gs/more-noise/</id>
    <content xml:lang="en" type="html">&lt;p&gt;A music-playing weekend after enjoying Lines of Flight gig on Friday. Shoulda seen more of the events!&lt;/p&gt;
&lt;h2&gt;2025 ten one-nine&lt;/h2&gt;
&lt;p&gt;A rough drum pattern I&#39;ve been messing with, in Strudel.&lt;/p&gt;
&lt;iframe src=&quot;https://strudel.cc/#Ly8gR3Jvb3Z5IHZhcmlhdGluZyBiZWF0cy4KLy8gMjAyNS0xMAoKc2V0Y3BtKDEyMC80KQoKJHNuYXJlOiBzb3VuZCgic2Qgc2QiKQogIC5scGYoIjwxMjAwIDE2MDAgOTAwPiIpCiAgLmhwZigiPDEwMCAyMDAgODAwIDQwMD4iKQoKJGJhc3Nfc25hcmVfaGl0czogc291bmQoIn4gfiB%2BIDx%2BIDxiZCozIHNkKjI%2BPiBiZCB%2BIH4gfiB%2BIH4gfiB%2BIGJkIH4gc2QgfiIpCgokcm9ja19iZWF0OiBhcnJhbmdlKAogIFs1LCBgCltiZCAtICBiZCoyIC0gXSBbc2QqMiBiZCoyIHNkIC0gXQpgXSwKICBbMywgYApbYmQgYmQgPGJkKjQgYmQqMj4gLSBdIFtiZCBzZCA8YmQqMiBiZCo0PiAtIF0KYF0KKQogIC5zb3VuZCgpCiAgLmJhbmsoIlJvbGFuZFRSODA4IikKICAuZGVsYXkoIjwwLjI1IDAuMSAwIDAuND4iKQoKJHJvY2tfaGF0czogYXJyYW5nZSgKICBbNSwgYApbaGggaGggaGggaGhdIFtoaCBoaCA8fiBoaD4gaGhdCmBdLAogIFszLCBgCltoaCBoaCBoaCBoaF0qMiBbaGggPH4gaGg%2BIDx%2BIGhoPiBoaF0qNApgXQopCiAgLnNvdW5kKCkKICAuZ2FpbigwLjMpCiAgLmJhbmsoIlJvbGFuZFRSODA4IikKCi8vICRiYXNpY19yb2NrMjogYXJyYW5nZSgKLy8gICBbNiwgYAovLyBbYmQgLSAgYmQqMiAtIF0gW2JkKjIgYmQqMiBiZCAtIF0KLy8gYF0sCi8vICAgWzIsIGAKLy8gW2JkIGJkIDxiZCo0IGJkKjI%2BIC0gXSBbYmQgYmQgPGJkKjIgYmQqND4gLSBdCi8vIGBdCi8vICkKLy8gICAuc291bmQoKQovLyAgIC5iYW5rKCJSb2xhbmRUUjkwOSIpCi8vICAgLmxwZigiPDIwMCAxMjAwIDgwMD4iKQo%3D&quot; width=&quot;100%&quot; height=&quot;500&quot; frameborder=&quot;0&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;I plugged in the kid&#39;s old synth and got that talking to &lt;a href=&quot;https://vmpk.sourceforge.io/&quot;&gt;VMPK&lt;/a&gt;. Tried a couple of other synths, this one appeared to not want Jack, which suited me best.&lt;/p&gt;
&lt;p&gt;Recorded in Audacity, I could see the levels were too high to begin with, no surprises how this sounds, haha now learn about levels Chris.&lt;/p&gt;
&lt;p&gt;&lt;audio controls=&quot;&quot; src=&quot;https://chris.bur.gs/media/2025-10-19-1.m4a&quot;&gt;&lt;a href=&quot;https://chris.bur.gs/media/2025-10-19-1.m4a&quot;&gt;Recording&lt;/a&gt;&lt;/audio&gt;&lt;/p&gt;
&lt;p&gt;Looking like I have a bit to learn about audio on this system. Kid tells me the audio sounds &amp;quot;deep fried&amp;quot; 🤣&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>DCC &amp; ORC candidate slogans, 2025</title>
    <link href="https://chris.bur.gs/dcc-orc-candidate-slogans/"/>
    <updated>2025-10-05T00:00:00Z</updated>
    <id>https://chris.bur.gs/dcc-orc-candidate-slogans/</id>
    <content xml:lang="en" type="html">&lt;p&gt;It is local government elections 2025, here are some candidate slogans, do you know who they belong to?&lt;/p&gt;
&lt;p&gt;Slogans below collected from riding the bus and taking notes, all errors my own!&lt;/p&gt;
&lt;p&gt;More info:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.dunedin.govt.nz/council/electoral-information/confirmed-candidates&quot;&gt;DCC candidates&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.orc.govt.nz/your-council/local-body-elections/2025-elections/&quot;&gt;ORC candidates&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Slogans&lt;/h2&gt;
&lt;p&gt;Click a slogan to see who has deployed it:&lt;/p&gt;
&lt;style&gt;
details {
  min-height: 200px;
  display: flex;
  flex-direction: column;
}
summary {
  cursor: pointer;
  font-size: 1.5rem;
  font-weight: 500;
  color: black;
  text-transform: uppercase;
  list-style: none;
  margin-bottom: 0.5rem;
}
summary::-webkit-details-marker {
  display: none;
}
summary:hover {
  text-decoration: underline;
}
&lt;/style&gt;
&lt;div class=&quot;not-prose max-w-none&quot; style=&quot;display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1rem;&quot;&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;Less waste, more listening&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Andrew Sutton&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;Delivering for Dunedin&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Jules Radich&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;The real deal.&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Sophie Barker&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;Turn this ship around!&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Captain Hugh O&#39;Neill&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;Care for tamariki&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Marie Laufiso&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;Transformational leadership&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Sophie Barker&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;Open. Honest. Fair.&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Carmen Houlahan&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;Vote for the REEL deal&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;O&#39;Neill &amp; Chambers (double bill ad!)&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;You&#39;d be barking mad not to have XXXXXX on council!&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Brent Weatherall&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;Manaakitaka&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Anna Knight&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;Proven, practical, committed&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Bill Acklin&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;A breath of fresh air&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Green Party candidates&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;Positive Action&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Jules Radich&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;Follow your dog!&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Jen Olsen&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;Sensible. Strong. Smart.&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Sophie Barker&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;Strong leadership, proven track record&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Andrew Simms&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;A compassionate approach to eliminating homelessness. No rates increases.&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Daniel Rooney&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;Financial and ecological stability&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Doug Hall Jr&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;Let&#39;s fix what&#39;s broken. Build what matters.&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Lee Vandervis&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;What&#39;s news Dunedin&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Richard Knights&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;Aroha ki te tākata&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Marie Laufiso (/ Building Kotahitaka crew)&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;Committed to the well being of our city&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Marita Johnson&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;For a healthy environment and future proof region&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Tim Morgan&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;A beautiful Ōtepoti for all&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Mandy Mayhem-Bullock&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;A Strong Voice for Dunedin&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Jo Galer&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;Prosperous People, Thriving Environment&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Chanel Gardner&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;I believe Dunedin can be better for all of us.&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Lee Vandervis&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;I will bring more than classic Ford Falcons to Dunedin from across the Tasman&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Benedict Ong&lt;/p&gt;
&lt;/details&gt;
&lt;details class=&quot;border border-gray-300 rounded-lg p-4&quot;&gt;
  &lt;summary class=&quot;posts text-2xl font-medium text-black uppercase cursor-pointer&quot;&gt;When Does It Stop&lt;/summary&gt;
  &lt;p class=&quot;mt-2 text-base normal-case&quot;&gt;Lync Aronson&lt;/p&gt;
&lt;/details&gt;
&lt;/div&gt;
&lt;p&gt;Submissions welcome, &lt;a href=&quot;https://chris.bur.gs/contact/&quot;&gt;see contact page&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;Also,&lt;/h2&gt;
&lt;p&gt;Thanks to all the folk who stuck googly-eyes on billboards across the city. Your work is appreciated.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Stupid small indie web</title>
    <link href="https://chris.bur.gs/stupid-smallweb/"/>
    <updated>2025-09-13T00:00:00Z</updated>
    <id>https://chris.bur.gs/stupid-smallweb/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Why write a silly page rating drink preferences?&lt;/p&gt;
&lt;p&gt;It&#39;s throwing weird crumbs to crawlers. It&#39;s playing around with dumb content. It&#39;s a fancy that took me. It&#39;s a small git towards a weirder, smaller internet.&lt;/p&gt;
&lt;iframe src=&quot;https://strudel.cc/#c2V0Y3BtKDEyNS80KQoKJHNuYXJlOiBzb3VuZCgic2Qgc2QiKQoKJGJhc2ljX3JvY2s6IGFycmFuZ2UoCiAgWzYsIGAKW2hoIGhoIGhoICAgaGhdIFtoaCAgIGhoICAgaGggaGhdLApbYmQgLSAgYmQqMiAtIF0gW2JkKjIgYmQqMiBiZCAtIF0KYF0sCiAgWzIsIGAKW2hoIGhoIGhoICAgaGhdIFtoaCBoaCBoaCAgIGhoXSwKW2JkIGJkIDxiZCo0IGJkKjI%2BIC0gXSBbYmQgYmQgPGJkKjIgYmQqND4gLSBdCmBdCikuc291bmQoKQoKJGJhc3NfZHJ1bV84MDg6IHNvdW5kKCJ%2BIH4gfiA8fiA8YmQqMyBiZCoyPj4gYmQgfiB%2BIH4gfiB%2BIH4gfiBiZCB%2BIH4gfiIpCiAgLmxwZigiPDIwMCAxMjAwIDgwMD4iKQogIC5kZWxheSgiPDAuMjUgMC4xIDAgMC40PiIp&quot; width=&quot;100%&quot; height=&quot;500&quot; frameborder=&quot;0&quot;&gt;&lt;/iframe&gt;
</content>
  </entry>
  <entry>
    <title>Mogu Mogu</title>
    <link href="https://chris.bur.gs/mogu-mogu/"/>
    <updated>2025-09-11T00:00:00Z</updated>
    <id>https://chris.bur.gs/mogu-mogu/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Mogu Mogu is a coco de nata (fermented coconut jelly) drink. It is quite sweet and feels super hydrating. Unimart has the best range in Ōtepoti.&lt;/p&gt;
&lt;h2&gt;Flavours&lt;/h2&gt;
&lt;h3&gt;Blackcurrant&lt;/h3&gt;
&lt;p&gt;8/10. Sweet and refreshing. Looked unappealing, but turned out to be Blue Powerade flavour. Blackcurrant.&lt;/p&gt;
&lt;h3&gt;Coconut&lt;/h3&gt;
&lt;p&gt;7/10. OK sweetness, milky. Smokin taste, it&#39;s lovely.&lt;/p&gt;
&lt;h3&gt;Grape&lt;/h3&gt;
&lt;p&gt;5/10, not a huge fan of this one.&lt;/p&gt;
&lt;h3&gt;Lychee&lt;/h3&gt;
&lt;p&gt;10/10 or too much. Very sweet and rosy.&lt;/p&gt;
&lt;h3&gt;Mango&lt;/h3&gt;
&lt;p&gt;6/10. Okay. It&#39;s hard to do fake mango well.&lt;/p&gt;
&lt;h3&gt;Peach&lt;/h3&gt;
&lt;p&gt;7/10, love this.&lt;/p&gt;
&lt;h3&gt;Pineapple&lt;/h3&gt;
&lt;p&gt;8/10, sweet and refreshing. Love this.&lt;/p&gt;
&lt;h3&gt;Raspberry&lt;/h3&gt;
&lt;p&gt;4/10, somehow too sweet and too watery.&lt;/p&gt;
&lt;h3&gt;Strawberry&lt;/h3&gt;
&lt;p&gt;8/10, sweet &amp;amp; delicious.&lt;/p&gt;
&lt;h3&gt;Watermelon&lt;/h3&gt;
&lt;p&gt;6/10, maybe the same challenge as mango.&lt;/p&gt;
&lt;h2&gt;Untried&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Apple&lt;/li&gt;
&lt;li&gt;Orange&lt;/li&gt;
&lt;li&gt;Pink Guava&lt;/li&gt;
&lt;li&gt;Pina Colada&lt;/li&gt;
&lt;li&gt;Yoghurt&lt;/li&gt;
&lt;li&gt;Four Seasons&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Strudel, a live coding music REPL</title>
    <link href="https://chris.bur.gs/strudel/"/>
    <updated>2025-09-06T00:00:00Z</updated>
    <id>https://chris.bur.gs/strudel/</id>
    <content xml:lang="en" type="html">&lt;p&gt;&lt;a href=&quot;https://strudel.cc/&quot;&gt;Strudel&lt;/a&gt; is a music live coding environment for the browser, porting the TidalCycles pattern language to JavaScript. It&#39;s fun, thanks Simon for tipping me onto it!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://strudel.cc/&quot;&gt;https://strudel.cc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Check out the tutorial and, if it&#39;s your thing, enjoy - I am 🙂‍↕️ Here&#39;s a few early strudel baking results!&lt;/p&gt;
&lt;h2&gt;Temptation&lt;/h2&gt;
&lt;iframe src=&quot;https://strudel.cc/#Ly8gVGVtcHRhdGlvbgoKc2V0Y3BtKDEyNS80KQoKJDogc291bmQoInNkIHNkIikKICAuYmFuaygiUm9sYW5kVFI4MDgiKQogIC5scGYoIjw0MDAgMjAwIDgwMD4iKQogIC5yb29tKCIwLjUiKQoKJDogc291bmQoIn4gfiB%2BIDx%2BIDxiZCozIGJkKjI%2BPiBiZCB%2BIH4gfiB%2BIH4gfiB%2BIGJkIH4gfiB%2BIikKICAuYmFuaygiUm9sYW5kVFI4MDgiKQogIC5scGYoIjw0MDAgMTIwMCA4MDA%2BIikKICAuZGVsYXkoIjwwLjI1IDAuMSAwIDAuOD4iKQoKJDogbm90ZSgifiB%2BIH4gfiA8fiB%2BIDxjOjQgZzoyPj4gfiBjOjMvMyB%2BIH4gfiB%2BIH4gfiB%2BIH4gfiIpCiAgLnNvdW5kKCJzYXd0b290aCIpCiAgLmxwZigiPDQwMCA4MDA%2BIikK&quot; width=&quot;100%&quot; height=&quot;500&quot; frameborder=&quot;0&quot;&gt;&lt;/iframe&gt;
&lt;h2&gt;Sweetest Taco&lt;/h2&gt;
&lt;p&gt;Simon asked me to do the Sweetest Taboo intro drums&lt;/p&gt;
&lt;iframe src=&quot;https://strudel.cc/#Ly8gU3dlZXRlc3QgVGFjbyAoZm9yIFNpbW9ubW9uKQoKc2V0Y3BtKDkwLzQpCgokOiBzb3VuZChgClt%2BICB%2BIHJpbSB%2BIF0gW34gcmltICB%2BIH5dIFtyaW0gfiAgfiB%2BXSBbfiAgICAgIHJpbSB%2BICB%2BXSwKW2JkIH4gfiAgIGJkXSBbfiB%2BICAgYmQgfl0gW34gICBiZCB%2BIH5dIFs8YmQgfj4gfiAgIH4gIH5dLApbfiAgfiB%2BICAgfiBdIFt%2BIH4gICB%2BICB%2BXSBbfiAgIH4gIH4gfl0gWzx%2BIHNkPiB%2BICAgc2Qgfl0KYCkKICAuYmFuaygiUm9sYW5kVFI3MDciKQoKJDogc291bmQoIltoaCoxNiBoaDoyKjhdIikKICAubHBmKCI8NDAwMCAyNDAwMCA4MDAwPiIpCiAgLmhwZigiPDAgNDAwMCAyMDAwIDA%2BIikK&quot; width=&quot;100%&quot; height=&quot;500&quot; frameborder=&quot;0&quot;&gt;&lt;/iframe&gt;
&lt;h2&gt;Basic Rocket&lt;/h2&gt;
&lt;p&gt;I feel like this is a lot of rhythmic complexity for such a terse annotation? Basic sounds here.&lt;/p&gt;
&lt;iframe src=&quot;https://strudel.cc/#c2V0Y3BtKDEyNS80KQoKJHNuYXJlOiBzb3VuZCgic2Qgc2QiKQoKJGJhc2ljX3JvY2s6IGFycmFuZ2UoCiAgWzYsIGAKW2hoIGhoIGhoICAgaGhdIFtoaCAgIGhoICAgaGggaGhdLApbYmQgLSAgYmQqMiAtIF0gW2JkKjIgYmQqMiBiZCAtIF0KYF0sCiAgWzIsIGAKW2hoIGhoIGhoICAgaGhdIFtoaCBoaCBoaCAgIGhoXSwKW2JkIGJkIDxiZCo0IGJkKjI%2BIC0gXSBbYmQgYmQgPGJkKjIgYmQqND4gLSBdCmBdCikuc291bmQoKQoKJGJhc3NfZHJ1bV84MDg6IHNvdW5kKCJ%2BIH4gfiA8fiA8YmQqMyBiZCoyPj4gYmQgfiB%2BIH4gfiB%2BIH4gfiBiZCB%2BIH4gfiIpCiAgLmxwZigiPDIwMCAxMjAwIDgwMD4iKQogIC5kZWxheSgiPDAuMjUgMC4xIDAgMC40PiIp&quot; width=&quot;100%&quot; height=&quot;500&quot; frameborder=&quot;0&quot;&gt;&lt;/iframe&gt;
</content>
  </entry>
  <entry>
    <title>Disabling data reporting in Lando</title>
    <link href="https://chris.bur.gs/lando-disable-data-reporting/"/>
    <updated>2025-08-20T00:00:00Z</updated>
    <id>https://chris.bur.gs/lando-disable-data-reporting/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I looked today for instructions on disabling data reporting in Lando, and thought to capture some notes on doing so.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.lando.dev/data.html&quot;&gt;Lando docs: Privacy Policy for Humans&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/lando/lando/issues/1205&quot;&gt;lando/lando#1205: Metrics reporting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/lando/lando/issues/746&quot;&gt;lando/lando#746: Developing without an internet connection&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&#39;s what worked for me:&lt;/p&gt;
&lt;h2&gt;Disabling usage reporting&lt;/h2&gt;
&lt;p&gt;Usage reporting is enabled by default, and lets the Lando team know how you&#39;re using Lando. At various Lando events, a report &amp;quot;ping&amp;quot; is sent to metrics.lando.dev. Here&#39;s an example of data reported, as shown by running &lt;code&gt;lando restart -vvv&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;example reporting stop action to &lt;span class=&quot;token assign-left variable&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;https://metrics.lando.dev, &lt;span class=&quot;token assign-left variable&quot;&gt;report&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;true +31ms&lt;br /&gt;example reporting data &lt;span class=&quot;token assign-left variable&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;lando restart, &lt;span class=&quot;token assign-left variable&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;local, &lt;span class=&quot;token assign-left variable&quot;&gt;devMode&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;false,&lt;br /&gt;                       &lt;span class=&quot;token assign-left variable&quot;&gt;instance&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;abcdef, &lt;span class=&quot;token assign-left variable&quot;&gt;nodeVersion&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;v18.19.1, &lt;span class=&quot;token assign-left variable&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;cli,&lt;br /&gt;                       &lt;span class=&quot;token assign-left variable&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;Linux, &lt;span class=&quot;token assign-left variable&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;linux, &lt;span class=&quot;token assign-left variable&quot;&gt;landoPlatform&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;linux,&lt;br /&gt;                       &lt;span class=&quot;token assign-left variable&quot;&gt;release&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;6.8&lt;/span&gt;.0-71-generic, &lt;span class=&quot;token assign-left variable&quot;&gt;arch&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;x64, &lt;span class=&quot;token assign-left variable&quot;&gt;isWsl&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;false,&lt;br /&gt;                       &lt;span class=&quot;token assign-left variable&quot;&gt;isWslInterop&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;false, &lt;span class=&quot;token assign-left variable&quot;&gt;product&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;lando, &lt;span class=&quot;token assign-left variable&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3.23&lt;/span&gt;.26,&lt;br /&gt;                       &lt;span class=&quot;token assign-left variable&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;0123abc, &lt;span class=&quot;token assign-left variable&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;drupal7,&lt;br /&gt;                       &lt;span class=&quot;token assign-left variable&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;                         &lt;span class=&quot;token assign-left variable&quot;&gt;api&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;drupal-php, &lt;span class=&quot;token assign-left variable&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;7.4&lt;/span&gt;,&lt;br /&gt;                         &lt;span class=&quot;token assign-left variable&quot;&gt;api&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;mariadb, &lt;span class=&quot;token assign-left variable&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10.3&lt;/span&gt;,&lt;br /&gt;                         &lt;span class=&quot;token assign-left variable&quot;&gt;api&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;compose, &lt;span class=&quot;token assign-left variable&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;custom,&lt;br /&gt;                         &lt;span class=&quot;token assign-left variable&quot;&gt;api&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;token assign-left variable&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;mailhog, &lt;span class=&quot;token assign-left variable&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;v1.0.0&lt;br /&gt;                       &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,&lt;br /&gt;                       &lt;span class=&quot;token assign-left variable&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;stop, &lt;span class=&quot;token assign-left variable&quot;&gt;created&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2025&lt;/span&gt;-08-20T10:26:55.784Z +1ms&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To disable it, add the following to your &lt;code&gt;~/.lando/config.yml&lt;/code&gt; file, as documented in &lt;a href=&quot;https://docs.lando.dev/data.html&quot;&gt;https://docs.lando.dev/data.html&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;stats&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;report&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;false&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; https&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//metrics.lando.dev&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might read that yaml and think &lt;code&gt;url&lt;/code&gt; might be redundant. It&#39;s not - and the reporting URL changed at some point in the past, so you might even have a different URL (&lt;code&gt;https://metrics.devwithlando.io&lt;/code&gt;) disabled in your configuration, and be surprised to find that Lando has resumed reporting to its new URL.&lt;/p&gt;
&lt;p&gt;I think it would be an improvement to permit a clearer global disable there (eg &lt;code&gt;stats: false&lt;/code&gt;), to avoid a future change to the default reporting URL restoring stats reporting without signal to the user.&lt;/p&gt;
&lt;h2&gt;Disabling crash reports&lt;/h2&gt;
&lt;p&gt;Disabling crash reporting takes a slightly different format: &lt;code&gt;~/.lando/cache/report_errors&lt;/code&gt; will either not exist (prompts on next run), or contain &amp;quot;true&amp;quot; or &amp;quot;false&amp;quot;. To disable:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;false&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; ~/.lando/cache/report_errors&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;About that data&lt;/h2&gt;
&lt;p&gt;Data is reported to &lt;a href=&quot;https://amplitude.com/&quot;&gt;Amplitude&lt;/a&gt; and crash information stored in &lt;a href=&quot;https://www.bugsnag.com/&quot;&gt;BugSnaq&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While it&#39;s really helpful to developers to have access to crash data and usage metrics, it&#39;s a decision to consider. I&#39;m wary of the level of data reported from systems - in 2025 I&#39;m far more cautious about, say, webserver access logs than I was in 2015 or 2005. The safest option is to minimise data gathered.&lt;/p&gt;
&lt;p&gt;I still expect to use the excellent Lando toolkit, and to report detailed bug information manually.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Discovering local devices using HASS</title>
    <link href="https://chris.bur.gs/hass-api-devices/"/>
    <updated>2025-04-22T00:00:00Z</updated>
    <id>https://chris.bur.gs/hass-api-devices/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I&#39;ve been working with Observability and the Grafana stack lately, and while having a re-organise got thinking about surveying and harvesting information about our home network.&lt;/p&gt;
&lt;p&gt;Home network is a dynamic environment - devices come and go, are forgotten. I had observed that Home Assistant (HASS) was quite good at &amp;quot;discovering&amp;quot; new devices on the network, and got thinking about how I could utilise this myself.&lt;/p&gt;
&lt;p&gt;Information recorded manually gets outdated and unreliable fast, while information retrieved by surveying gives you a current view but can lack historic context. The trick is combining what&#39;s discovered with knowledge you&#39;d like to preserve; discovery can give you &amp;quot;a device is connected, named X and has details Y&amp;quot;, while preserving knowledge can tell you who it belonged to or why it was introduced.&lt;/p&gt;
&lt;p&gt;First question was, how to find hosts HASS sees. HASS has an API! Let&#39;s explore - we will need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;HASS_URL&lt;/code&gt; The URL of your Home Assistant instance&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HASS_TOKEN&lt;/code&gt; A &lt;a href=&quot;https://www.home-assistant.io/docs/authentication/&quot;&gt;long lived token from Home Assistant&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let&#39;s see what the states are in HASS.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X GET -H &amp;quot;Authorization: Bearer ${HASS_TOKEN}&amp;quot; &#92;
  -H &amp;quot;Content-Type: application/json&amp;quot; &#92;
  ${HASS_URL}/api/states
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I get a response like:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;entity_id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;person.chris_burgess&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;state&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;home&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;attributes&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;editable&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;chris_burgess&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;device_trackers&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token string&quot;&gt;&quot;device_tracker.chris_oneplus&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token string&quot;&gt;&quot;device_tracker.chriss_ipad_2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token string&quot;&gt;&quot;device_tracker.nexus_5x&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token string&quot;&gt;&quot;device_tracker.cph2529&quot;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;latitude&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;-45.00&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;longitude&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;170.00&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;gps_accuracy&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;source&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;device_tracker.abcdef1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;aabbccdd&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;friendly_name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Chris Burgess&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;last_changed&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2025-04-21T19:11:33.996324+00:00&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;last_reported&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2025-04-21T22:36:42.920973+00:00&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;last_updated&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2025-04-21T22:36:42.920973+00:00&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;context&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;01234&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;parent_id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token null keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token null keyword&quot;&gt;null&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;entity_id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;device_tracker.ubuntu_server_2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;state&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;home&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;attributes&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;source_type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;router&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;ip&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;10.0.0.2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;mac&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;AB:CD:11:22:33:44&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;host_name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ahem&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;last_time_reachable&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2025-04-21T22:43:16+00:00&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;connected_to&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;fritz.box&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;connection_type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;LAN&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;icon&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;mdi:lan-connect&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;friendly_name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ahem&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;last_changed&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2025-04-21T19:12:32.411093+00:00&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;last_reported&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2025-04-21T22:43:18.691542+00:00&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;last_updated&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2025-04-21T22:43:18.691542+00:00&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;context&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;01234&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;parent_id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token null keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token null keyword&quot;&gt;null&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;entity_id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;device_tracker.the_nas&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;state&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;home&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;attributes&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;source_type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;router&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;ip&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;10.0.0.3&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;mac&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;AB:CD:11:22:33:44&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;host_name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;the-nas&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;last_time_reachable&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2025-04-21T22:43:16+00:00&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;connected_to&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;fritz.box&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;connection_type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;LAN&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;icon&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;mdi:lan-connect&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;friendly_name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;nas&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;last_changed&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2025-04-21T19:12:32.429386+00:00&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;last_reported&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2025-04-21T22:43:18.692900+00:00&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;last_updated&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2025-04-21T22:43:18.692900+00:00&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;&quot;context&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;01234&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;parent_id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token null keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;&quot;user_id&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token null keyword&quot;&gt;null&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&#39;s filter that down to hosts we&#39;re interested in:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-X&lt;/span&gt; GET &lt;span class=&quot;token parameter variable&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Authorization: Bearer &lt;span class=&quot;token variable&quot;&gt;${HASS_TOKEN}&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Content-Type: application/json&quot;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;${HASS_URL}&lt;/span&gt;/api/states  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; jq &lt;span class=&quot;token string&quot;&gt;&#39;.[] | select(.state == &quot;home&quot; and .attributes.host_name != null) | {entity_id, state, host_name: .attributes.host_name}&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;How about as a Markdown list:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-X&lt;/span&gt; GET &lt;span class=&quot;token parameter variable&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Authorization: Bearer &lt;span class=&quot;token variable&quot;&gt;${HASS_TOKEN}&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Content-Type: application/json&quot;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;${HASS_URL}&lt;/span&gt;/api/states  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; jq &lt;span class=&quot;token parameter variable&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;.[] | select(.state == &quot;home&quot; and .attributes.host_name != null) | &quot;- &#92;(.attributes.host_name) (&#92;(.attributes.ip))&quot;&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sort&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or as a table:&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;| friendly name | host name | ip | notes |&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;|--|--|--|--|&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-X&lt;/span&gt; GET &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token parameter variable&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Authorization: Bearer &lt;span class=&quot;token variable&quot;&gt;${HASS_TOKEN}&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token parameter variable&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Content-Type: application/json&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token variable&quot;&gt;${HASS_URL}&lt;/span&gt;/api/states  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; jq &lt;span class=&quot;token parameter variable&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;.[] | select(.state == &quot;home&quot; and .attributes.host_name != null) | &quot;| &#92;(.attributes.friendly_name) | `&#92;(.attributes.host_name)` | `&#92;(.attributes.ip)` |  |&quot;&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The output of the last might look like (after comments added):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;friendly name&lt;/th&gt;
&lt;th&gt;host name&lt;/th&gt;
&lt;th&gt;ip&lt;/th&gt;
&lt;th&gt;notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;9b7eb8b9-dff1-d401-5507-09a8aaaea195&lt;/td&gt;
&lt;td&gt;&lt;code&gt;9b7eb8b9-dff1-d401-5507-09a8aaaea195&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.0.0.59&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;a1f6389c-bb13-c10f-3af0-cd147e51a662&lt;/td&gt;
&lt;td&gt;&lt;code&gt;a1f6389c-bb13-c10f-3af0-cd147e51a662&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.0.0.52&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aooga&lt;/td&gt;
&lt;td&gt;&lt;code&gt;aooga&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.0.0.101&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;VM running some services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;baluga&lt;/td&gt;
&lt;td&gt;&lt;code&gt;baluga&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.0.0.102&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Android-2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Android-2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.0.0.51&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Visitor phone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;laptop-1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;work-laptop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.0.0.54&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Work laptop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chromecast-Audio&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Chromecast-Audio&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.0.0.59&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Best device&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chromecast&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Chromecast&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.0.0.103&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NAS-1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;NAS device&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.0.0.53&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DSP-W123&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DSP-W118&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.0.0.104&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ESP-ABCDE1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ESP-ABCDE1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.0.0.55&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ESP-123ABC&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ESP-123ABC&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.0.0.56&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ESP-DEF432&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ESP-DEF432&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.0.0.57&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;esphome-web-809081&lt;/td&gt;
&lt;td&gt;&lt;code&gt;esphome-web-809081&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.0.0.58&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This is useful, I now have a list of devices to review, and a table to keep notes in!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Our Friend the Computer</title>
    <link href="https://chris.bur.gs/our-friend-the-computer/"/>
    <updated>2025-04-08T00:00:00Z</updated>
    <id>https://chris.bur.gs/our-friend-the-computer/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I am currently devouring the &lt;a href=&quot;https://www.ourfriendthe.computer/&quot;&gt;Our Friend The Computer podcast&lt;/a&gt;, &lt;em&gt;&amp;quot;a podcast exploring alternative computing histories and their relationship to society&amp;quot;&lt;/em&gt;. It&#39;s interesting, engaging, and provides lots of stimulating avenues to consider the aspirations of early networks, their development, and their impact on society at the time.&lt;/p&gt;
&lt;p&gt;I appreciate the social, cultural and art lenses that it applies. The first several episodes I&#39;ve listened to - I&#39;m approaching the end of the &amp;quot;Pre-Internet Networks&amp;quot; season - discuss and consider the social impacts and experiences of several networks, including CyberSyn, OGAS, Minitel, Bildschirmtext, NABU and Beltel.&lt;/p&gt;
&lt;p&gt;Unlike today&#39;s internet, each of those networks was situated quite specifically to one particular nation. It&#39;s really interesting here to investigate the state-architected&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://chris.bur.gs/our-friend-the-computer/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; development of early computer networks, and the strong design impact of the specific culture and social priorities of the country this was happening in.&lt;/p&gt;
&lt;p&gt;It&#39;s fascinating to see the impact of each nation&#39;s culture and language lead to different qualities in each of the networks discussed. With Bildschirmtext we explore the development of a network in Germany as the country re-integrates after the cold war separation.&lt;/p&gt;
&lt;p&gt;Let&#39;s not assume that&#39;s always for good, either: in the Beltel episode we her of early computing networks used as tools of apartheid. The Beltel episode hit pretty hard, to be honest; as a worker in the tech industry, I am mindful of tech companies participating in apartheid and genocide. That was true then, and sadly it is true today. We must use our power to see this, to resist and undo the damage of colonisation.&lt;/p&gt;
&lt;p&gt;I enjoy the exploration of how each culture led to the development of a network for each environment. I&#39;m sure this eventually resulted in interoperability challenges and those gradually contributed to the less culturally situated, more globalised internet we have in 2025.&lt;/p&gt;
&lt;p&gt;For me this brings a reflection on the development of non-state networks, also. If a group is not represented within the state/power systems in play, they might have to subvert available systems to develop a non-state network (eg BBS usage by resistance groups in South Africa as a response to government control of Beltel), or somehow create a network of their own. The internet hasn&#39;t solved this.&lt;/p&gt;
&lt;p&gt;It&#39;s also interesting to apply this consideration to the development of government supported or sponsored platforms in current times. Technologies such as Common Web Platforms, or technology preferences as expressed through purchasing boards or panels, express the desires of states in modern technology. These may be technologies controlled by a single vendor, which tend to increase dependency and reduce agency, or they may be platforms open to participation, which offer at least an opportunity for self-determination and agency to the state, its component organisations, and the citizens within it.&lt;/p&gt;
&lt;p&gt;The podcast hosts, Camila Galaz and Ana Meisel, explore somewhat familiar tech spaces through a lens that&#39;s richer and more human than would typically be applied. I&#39;m really grateful for their work and it&#39;s inspiring me to consider how I can apply creative and more emotive expression to my own understanding of our work and impacts in society. Thanks Camila and Ana! Your reflections are delightful.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot; /&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Worth considering that &amp;quot;state architected&amp;quot; here likely often unpacks with &amp;quot;by a small team of geeks within the relevant state telephony provider&amp;quot;. &lt;a href=&quot;https://chris.bur.gs/our-friend-the-computer/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  <entry>
    <title>GlitchTip API CLIent</title>
    <link href="https://chris.bur.gs/glitchtip-client/"/>
    <updated>2025-02-23T00:00:00Z</updated>
    <id>https://chris.bur.gs/glitchtip-client/</id>
    <content xml:lang="en" type="html">&lt;p&gt;In GlitchTip&#39;s community chat a few days ago, a user asked about filtering event logging and issue reports in GlitchTip.&lt;/p&gt;
&lt;p&gt;Depending on your GlitchTip / Sentry client library, you have various options to control what gets reported to Sentry or GlitchTip. The Python and Drupal Raven integrations support Sentry&#39;s &lt;code&gt;before_send&lt;/code&gt; pattern, while other client libraries may or may not allow you to &amp;quot;silence&amp;quot; reports that might generate a lot of noise in your Sentry.&lt;/p&gt;
&lt;p&gt;An example of this I&#39;ve seen is reports for each 404 hit in some applications, which can result in a bunch of needless events and eventually issues in GlitchTip that are associated with the 404s.&lt;/p&gt;
&lt;p&gt;There was a question raised &lt;a href=&quot;https://matrix.to/#/!qJfEetDzNUrAFBcqyw:gitter.im/$BRIB-qbOSK5s09MdSVvj9_t0NtTI0cTiKTx77oOUa9g?via=gitter.im&amp;amp;via=matrix.org&amp;amp;via=othermo.de&quot;&gt;in the GlitchTip channel&lt;/a&gt; about how to automatically silence messages in GlitchTip. The user asking didn&#39;t have access to filter clientside, so they were looking for a solution to remove entries within GlitchTip.&lt;/p&gt;
&lt;p&gt;My take is that the preferred order of solving this is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If at all possible, prevent issues being recorded from the client. Eliminating the event report means less data is gathered in GlitchTip. This is optimal.&lt;/li&gt;
&lt;li&gt;If you can&#39;t handle it in the client configuration, you have to do it at the GlitchTip side.
&lt;ol&gt;
&lt;li&gt;A WAF-style filter in front of GlitchTip may be an option to prevent the submission reaching GlitchTip at all. This might be a configuration in a reverse proxy that GlitchTip sits behind.&lt;/li&gt;
&lt;li&gt;Filtering &lt;em&gt;could&lt;/em&gt; be introduced in GlitchTip to provide configurable filters - but event reporting is a performance critical pathway in GlitchTip, so this would need to be kept light.&lt;/li&gt;
&lt;li&gt;Filtering can also be implemented in GlitchTip&#39;s ingest path - this was the maintainer&#39;s recommendation in thread. This is also a performance-sensitive path in GlitchTip so needs to be carefully tested.&lt;/li&gt;
&lt;li&gt;Once the event is ingested and the item created, there&#39;s capacity to &lt;em&gt;remove&lt;/em&gt; the item from GlitchTip.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It struck me that you could do something like:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Get a token from /profile/auth-tokens in GlitchTip.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;GLITCHTIP_API_TOKEN&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;ABCDEF&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Accept: application/json&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token parameter variable&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Authorization: Bearer &lt;span class=&quot;token variable&quot;&gt;$GLITCHTIP_API_TOKEN&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&#39;https://glitchtip.example.org/api/0/organizations/some-org/issues/?query=is:unresolved%20level:warning&amp;amp;project=2&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; jq &lt;span class=&quot;token string&quot;&gt;&#39;.[].id&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;read&lt;/span&gt; ISSUE_ID &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Accept: application/json&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token parameter variable&quot;&gt;-X&lt;/span&gt; DELETE &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token parameter variable&quot;&gt;-H&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Authorization: Bearer &lt;span class=&quot;token variable&quot;&gt;$GLITCHTIP_API_TOKEN&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&#39;https://glitchtip.example.org/api/0/organizations/some-org/issues/$ISSUE_ID&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I figured I&#39;d test this out, and the one-liner grew into a little bash script to show or delete issues from GlitchTip. Deleting issues should delete related events.&lt;/p&gt;
&lt;p&gt;This is not the ideal solution - we want to reduce waste as a first step, recycle second; similarly we want to reduce event capture as a first step, and then as a second strategy remove events that we shouldn&#39;t have captured. (Maybe that wanted a fishing analogy, IDK.)&lt;/p&gt;
&lt;p&gt;But, as a strategy for periodically clearing out events we don&#39;t want captured ... it might work. And that might be useful to me, or you. The tool is now available as &lt;a href=&quot;https://gitlab.com/xurizaemon/gt-cli&quot;&gt;xurizaemon/gt-cli&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&#39;m wondering at how I named it now, as I don&#39;t at all expect this to become &lt;em&gt;the&lt;/em&gt; GlitchTip CLI. The Django app already has a local CLI, &lt;code&gt;./manage.py&lt;/code&gt;, and this has a couple commands only for the remote API. It&#39;s an experiment which I&#39;m sharing just to share. If you find it useful, all the better!&lt;/p&gt;
&lt;p&gt;A more efficient approach would likely be to PR some additional &lt;code&gt;./manage.py&lt;/code&gt; subcommands to filter out issues/events.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Drop your copyright ranges</title>
    <link href="https://chris.bur.gs/copyright-end-date/"/>
    <updated>2025-01-03T00:00:00Z</updated>
    <id>https://chris.bur.gs/copyright-end-date/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Day after New Years Day 2025, I saw a PR opened on a project I maintain. For historic reasons the &lt;a href=&quot;https://github.com/hubotio/hubot&quot;&gt;hubotio/hubot&lt;/a&gt; project has a copyright statement with a date &lt;em&gt;range&lt;/em&gt;, ex &amp;quot;Copyright 2011-2024, Github Inc&amp;quot;.&lt;/p&gt;
&lt;p&gt;Lots of projects have this format in their licensing, and I question if it&#39;s &lt;em&gt;useful&lt;/em&gt;. I do not believe it has meaning - the project copyright wouldn&#39;t lapse if we missed updating the year, would it? I believe that in some media the second date signals that the last date that the work was substantitively modified (say, you re-edited and re-released a book text).&lt;/p&gt;
&lt;p&gt;With an open source project, the change history of the project is highly visible, and a license date may even be less accurate than the commit history already visible. It&#39;s also a weird (to me) duplication, a kind of manual last-modified date that by being updated ensures the document has a last-modified date anyway.&lt;/p&gt;
&lt;p&gt;I think projects would be better off dropping the end date. Your commit history should show when the project was modified, but doesn&#39;t need to show trivia like &amp;quot;Updated copyright year&amp;quot;; this is extra work for your contributors, and can be additional cognitive load&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://chris.bur.gs/copyright-end-date/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; for anyone working with your codebase.&lt;/p&gt;
&lt;p&gt;Your project probably don&#39;t need additional MRs/PRs that make trivia changes, &lt;em&gt;unless&lt;/em&gt; they serve some other purpose. Trivia changes might be useful as new contributor practice tasks, but if that&#39;s the aim, you won&#39;t want a once-a-year ritual for it.&lt;/p&gt;
&lt;p&gt;Worse, if your project includes copyright in each file (as &lt;a href=&quot;http://gnu.org/&quot;&gt;GNU.org&lt;/a&gt;&#39;s GPL guidance recommends&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://chris.bur.gs/copyright-end-date/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;), it is increasingly hard to discern &lt;em&gt;meaningful&lt;/em&gt; changes in a file&#39;s history from trivia updates. I think &lt;a href=&quot;http://gnu.org/&quot;&gt;GNU.org&lt;/a&gt;&#39;s advice is impractical here for most projects, and jurisdictions vary anyway.&lt;/p&gt;
&lt;p&gt;Anyway - back to the PR opened on Hubot. I saw the PR, and thought &amp;quot;huh, why do we still have this copyright range here&amp;quot;. The PR description said,&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Yearly copyright bump. Padding my changed LOC! 😛&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Uhoh. And yep, looking at the contributor&#39;s history, the account had no commits prior to this date, and had 1.5K similar open PRs across a bunch of projects ... 🙄 ... I searched for patience ... Perhaps they think it&#39;s helpful - but they do know what they&#39;re doing; they were capable of automating thousands of PRs, from a fresh Github account. I commented on the PR noting the user&#39;s PRs to other projects, suggesting that we should drop the end date instead. Joey closed the PR. Good.&lt;/p&gt;
&lt;p&gt;Thing is, even if it was entirely well intentioned, this action probably wasted the time of hundreds of people. Even if they did no more than scan the email to decide whether it was worth their attention, that time cost in making bulk PRs multiplies out across hundreds or thousands of volunteers. Many of the PRs would be ignored, but even then it stacks up in the PR queue for that project, and will add to friction for future maintenance.&lt;/p&gt;
&lt;p&gt;So, my messages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Please &lt;strong&gt;don&#39;t make bulk contributions&lt;/strong&gt; without a lot of care. I fully support new contributors making tentative PRs, even trivial ones, if they feel they are contributing something meaningful. I do not have patience for automated activity that is low-value and disregards related volunteer labour costs&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://chris.bur.gs/copyright-end-date/#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;li&gt;If you maintain a project and have a &amp;quot;Copyright 2020-2024&amp;quot; which needs updating right now, &lt;strong&gt;consider removing the end date&lt;/strong&gt;. If it&#39;s not useful to your project, removal may eliminate a busywork task for the future, and reduce cognitive load for developers.&lt;/li&gt;
&lt;li&gt;If your project is languishing and only getting that annual license year update, perhaps stop by the oasis of &lt;a href=&quot;https://unmaintained.tech/&quot;&gt;No Maintenance Intended&lt;/a&gt; while you&#39;re at it. Nothing stopping you setting it down and picking it up again later :)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Update! This article discusses this and more, and makes a solid case for using the year of first publication for each file: &lt;a href=&quot;https://matija.suklje.name/how-and-why-to-properly-write-copyright-statements-in-your-code&quot;&gt;https://matija.suklje.name/how-and-why-to-properly-write-copyright-statements-in-your-code&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;None of this is legal advice.&lt;/p&gt;
&lt;p&gt;I probably don&#39;t live in your jurisdiction anyway.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot; /&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Some prior art - years back in the CiviCRM project, we discussed (&lt;a href=&quot;https://lab.civicrm.org/dev/core/-/issues/878&quot;&gt;dev/core#878&lt;/a&gt;) and agreed to simplify the copyright headers including removing the year ranges - there was a long-form GPL license in each file&#39;s header with dates that required periodic updates. Simplifying the license in the files removed 47K of license text across 3.2K files (&lt;a href=&quot;https://github.com/civicrm/civicrm-core/pull/15843&quot;&gt;⚠️civicrm/civicrm-core#15843&lt;/a&gt;), making it much, much easier to read through future changes to any given piece of code&#39;s history by eliminating a class of changes which were not significant. &lt;a href=&quot;https://chris.bur.gs/copyright-end-date/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I see that &lt;a href=&quot;http://gnu.org/&quot;&gt;GNU.org&lt;/a&gt;&#39;s &lt;a href=&quot;https://www.gnu.org/licenses/gpl-howto.en.html&quot;&gt;GPL HOWTO&lt;/a&gt; advises to introduce a year marker in the copyright for each release (guidance which I question, but I&#39;ll include here as it may explain why this pattern is widely observed). &lt;/p&gt;&lt;blockquote&gt;The copyright notice should include the year in which you finished preparing the release (so if you finished it in 1998 but didn&#39;t post it until 1999, use 1998). You should add the proper year for each past release; for example, “Copyright 1998, 1999 Terry Jones” if some releases were finished in 1998 and some were finished in 1999. If several people helped write the code, use all their names.&lt;br /&gt;
For software with several releases over multiple years, it&#39;s okay to use a range (“2008-2010”) instead of listing individual years (“2008, 2009, 2010”) if and only if every year in the range, inclusive, really is a “copyrightable” year that would be listed individually; and you make an explicit statement in your documentation about this usage.&lt;/blockquote&gt; &lt;a href=&quot;https://chris.bur.gs/copyright-end-date/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;p&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;What happened to that account and its PRs? I see today that the account, and &lt;em&gt;almost&lt;/em&gt; all history of its activity (except signing this CLA! &lt;a href=&quot;https://github.com/conda/infrastructure/pull/1087&quot;&gt;conda/infrastructure#1087&lt;/a&gt;), has been eliminated from Github. That&#39;s why this post mostly lacks URLs for the PRs and activity discussed. There&#39;s &lt;a href=&quot;https://mas.to/@davidism/113754041290679759&quot;&gt;some&lt;/a&gt; &lt;a href=&quot;https://bsky.app/profile/thajeztah.bsky.social/post/3lepcbigdns2p&quot;&gt;mentions&lt;/a&gt; of their activity elsewhere. &lt;a href=&quot;https://chris.bur.gs/copyright-end-date/#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  <entry>
    <title>⭐ to relay</title>
    <link href="https://chris.bur.gs/fave-to-relay-with-skymoth/"/>
    <updated>2024-12-30T00:00:00Z</updated>
    <id>https://chris.bur.gs/fave-to-relay-with-skymoth/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I have mixed feelings about relaying between social networks: it&#39;s okay, but needs consideration.&lt;/p&gt;
&lt;p&gt;For:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I own my rants, blorts, toots, skeets, posts micro, minor and major, and I will post them in multiple places.&lt;/li&gt;
&lt;li&gt;I have connections on many networks and want to participate.&lt;/li&gt;
&lt;li&gt;I&#39;m against busywork, which includes copypasting between systems.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Against:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Relayed posts often hit badly when they are written with network A in mind and show up on network B.&lt;/li&gt;
&lt;li&gt;That&#39;s especially true if the relayed account is not &amp;quot;active&amp;quot; - now you have a broadcast setup. (Is this how @bsky.brid.gy accounts work? Not sure.)&lt;/li&gt;
&lt;li&gt;Special mention: No-consent relays such as bird.makeup suck &lt;em&gt;hard&lt;/em&gt;. Do not do this. &lt;a href=&quot;https://todo.sr.ht/~cloutier/bird.makeup/9&quot;&gt;Set yourself a higher bar than &amp;quot;it&#39;s technically possible&amp;quot;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(Probably missing some bits there. Anyway.)&lt;/p&gt;
&lt;p&gt;Currently I&#39;m active in Fediverse (preferred, via the excellent, small, &lt;a href=&quot;https://toot.cafe/&quot;&gt;https://toot.cafe&lt;/a&gt; Mastodon instance) and Bluesky. I wanted the capacity to continue posting laundry appreciation, tech snark and life reflections to the Fediverse, and make it easy to circulate those posts from Fediverse to Bluesky when I wanted to&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://chris.bur.gs/fave-to-relay-with-skymoth/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;. Since I&#39;d be making that choice &lt;em&gt;when posting&lt;/em&gt;, I control using the available interface - typically my mobile Mastodon client, Tusky, or the web interface for my home toot.cafe instance.&lt;/p&gt;
&lt;p&gt;We can&#39;t extend the UI of apps we don&#39;t control ... usually. With ingenuity, we can re-use an existing UI element for our requirements: use the ⭐ &amp;quot;Fave&amp;quot; UI to flag a post from the posting UI for our own purposes, then read the post&#39;s faved status to determine whether the post should be relayed.&lt;/p&gt;
&lt;p&gt;I had previously implemented this same functionality for a Fediverse / Twitter relay service, moa.party (&lt;a href=&quot;https://gitlab.com/fedstoa/moa/-/issues/53&quot;&gt;fedstoa/moa#53&lt;/a&gt;). That project was no longer active but the solution could be reworked with a new service. &lt;a href=&quot;https://github.com/thilobillerbeck/skymoth&quot;&gt;Skymoth&lt;/a&gt; looked viable, and while it didn&#39;t share any DNA from Moa, I was able to implement a similar solution.&lt;/p&gt;
&lt;p&gt;Taking a look at Skymoth, I could see notes in the README already relating to user control. The first step would be to see if this system was a fit for my usage. I ran up a copy locally using Docker Compose and tested it out; aside from the addition I planned to add, it seemed to work fine.&lt;/p&gt;
&lt;p&gt;Next I opened an issue (&lt;a href=&quot;https://github.com/thilobillerbeck/skymoth/issues/55&quot;&gt;https://github.com/thilobillerbeck/skymoth#55&lt;/a&gt;) to let the maintainer/community know I was interested in making this happen, posted some first thoughts on approach, and outlined my understanding of the existing implementation. My goal here was to avoid conflict with any existing WIP and to seek initial input.&lt;/p&gt;
&lt;p&gt;Then I used the project docs to bring up a local development environment. This was interesting as the project uses a toolkit I hadn&#39;t tried before, Cachix DevEnv. It&#39;s pretty swish! It was super fast to bring up the local development environment, and this setup had a tidy TUI interface, &lt;a href=&quot;https://github.com/F1bonacc1/process-compose&quot;&gt;process-compose&lt;/a&gt;, to manage and observe running processes.&lt;/p&gt;
&lt;p&gt;Skymoth is written in TypeScript, using &lt;a href=&quot;https://fastify.dev/&quot;&gt;Fastify&lt;/a&gt; to provide the web app interface and &lt;a href=&quot;https://www.prisma.io/&quot;&gt;Prisma&lt;/a&gt; + &lt;a href=&quot;https://www.postgresql.org/&quot;&gt;Postgres&lt;/a&gt; for storage. Of these, only Postgres was familiar territory. The DevEnv setup made it really easy to get up and running, and the toolchain was accessible and straightforward to get hacking on.&lt;/p&gt;
&lt;p&gt;I mocked up some local changes to show the UX I would propose, and shared them on the issue queue. Then I set about implementing. The existing query structure made it pretty easy to work with. While I only wanted fave-to-relay for my own needs, I recalled that moa.party (and/or other relaying tools?) had supported relaying based on the presence or absence of a hashtag, so I included that functionality as well.&lt;/p&gt;
&lt;p&gt;I liked that Skymoth&#39;s call to Mastodon API fetched 50 recent posts and iterated over them; that eliminated an ungainly part of how this worked in moa.party, where your post might not be relayed if you didn&#39;t fave it quickly enough, as the most recent post ID would be used as a high-water mark whether or not that post was relayed at the time of the poll.&lt;/p&gt;
&lt;p&gt;The most challenging part was trying to follow some existing implementation - there was storage for user settings in the DB schema, but that didn&#39;t seem to be wired up in the application. I gave it a shot, but eventually left it and stored the details against the user; I think what I ran into was some incompletely implemented settings storage, not yet in a state to be used, making for a bit of a red herring! Once I decided to ignore that entirely, things progressed fine :)&lt;/p&gt;
&lt;p&gt;The changes did what I wanted and the end result is great for me - I am happily posting to Fediverse (my preferred network), hitting ⭐ if I want to circulate it over to Bluesky, and able to engage with replies on both networks. Relayed posts handle the details of each network fine so far, including media, alt text, and splitting post bodies if the Mastodon post is too long for a single Bluesky post. I don&#39;t have to fave right away, self-replies work as desired, it&#39;s grand. And now that the PR is merged, the users of &lt;a href=&quot;https://skymoth.app/&quot;&gt;https://skymoth.app&lt;/a&gt; have the same functionality. Delighted!&lt;/p&gt;
&lt;p&gt;I picked up some TypeScript knowledge along the way, and appreciated the simplicity of this tidy little app and the toolchain Thilo has selected for it. Nix, DevEnv, Fastify and Prisma all look interesting tools to use again in future if opportunity arises.&lt;/p&gt;
&lt;p&gt;If you want to use this yourself, you can either use the hosted version at &lt;a href=&quot;https://skymoth.app/&quot;&gt;https://skymoth.app&lt;/a&gt; which now does this, or use the &lt;code&gt;example.compose.yml&lt;/code&gt; in &lt;a href=&quot;https://github.com/thilobillerbeck/skymoth/&quot;&gt;https://github.com/thilobillerbeck/skymoth/&lt;/a&gt; to run your own copy locally.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot; /&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Bluesky can join the Fediverse any time they like, of course, and save me the effort, but hey. &lt;a href=&quot;https://chris.bur.gs/fave-to-relay-with-skymoth/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  <entry>
    <title>Sunset over Wakari</title>
    <link href="https://chris.bur.gs/sunset-over-wakari/"/>
    <updated>2024-12-07T00:00:00Z</updated>
    <id>https://chris.bur.gs/sunset-over-wakari/</id>
    <content xml:lang="en" type="html">&lt;p&gt;&lt;canvas id=&quot;canvas&quot; aria-label=&quot;Wakari viewed from Pine Hill Road, 7 December 2024&quot;&gt;&lt;/canvas&gt;&lt;/p&gt;
&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://chris.bur.gs/media/sunset.css&quot; /&gt;
&lt;script src=&quot;https://chris.bur.gs/media/sunset.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;Te Whanaupaki / Flagstaff, Wakari, and Saddle Hill&lt;br /&gt;
As viewed from Pine Hill Road, 7 December 2024.&lt;br /&gt;
(Source: &lt;a href=&quot;https://chris.bur.gs/media/sunset.js&quot;&gt;JS&lt;/a&gt;, &lt;a href=&quot;https://chris.bur.gs/media/sunset.css&quot;&gt;CSS&lt;/a&gt;)&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I appreciated finding the &lt;a href=&quot;https://christchurchartgallery.org.nz/exhibitions/one-oclock-jump&quot;&gt;One O&#39;Clock Jump: British Linocuts from the Jazz Age&lt;/a&gt; exhibition at Christchurch Art Gallery, and even timed it right to watch the curator intro - lucky!&lt;/p&gt;
&lt;p&gt;One thing that caught my eye was artists highlighting distant hillside edges with a stroke of sunlight - might have been Rhona Haszard&#39;s &amp;quot;Spring in the Marne Valley&amp;quot; or some other piece, I was all eyes and agog in the exhibition - go see it if you can :)&lt;/p&gt;
&lt;p&gt;The exhibition website above has a good selection of the pieces online but not all when I checked, and I couldn&#39;t find the piece I exactly recall seeing this on.&lt;/p&gt;
&lt;p&gt;Arriving back in Dunedin we were out collecting elderflowers for cordial and I saw a similar effect looking across towards Wakari as the sun set.&lt;/p&gt;
&lt;p&gt;I thought the image would be cool to try and produce a sketch of that using the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API&quot;&gt;Web Canvas API&lt;/a&gt; in the style of such a lino cut; I hadn&#39;t had a play with Canvas API before and I figured that this style would lend itself well to a first attempt. The time will be well spent getting familiar with some javascript interfaces and geometry.&lt;/p&gt;
&lt;p&gt;Also in mind for me was the natural shapes which start to emerge when you have a Grafana dashboard in the corner of your eye.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/grafana-cpu-usage-mountains.png&quot; alt=&quot;Grafana CPU usage chart looking like mountains&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I worked out how to draw a line across the screen and a linear gradient vertically from that to shade the hillside, then how to make the line randomly walk up and down a few pixels to give it an organic shape.&lt;/p&gt;
&lt;p&gt;I then removed the jitter and shaped the lines with a couple of crude mathematical functions that could approximate two distant hills, one peeking from behind the other. I started with simple functions to define these shapes at first, then introduced &lt;code&gt;wave&lt;/code&gt; and &lt;code&gt;cubic&lt;/code&gt; functions so I could lift nicer shapes from maths magic.&lt;/p&gt;
&lt;p&gt;I repackaged things so that I could paste &lt;code&gt;drawHill()&lt;/code&gt; calls into the browser console to try different strokes. You can do this: open your browser console (&lt;kbd&gt;Ctrl + Shift + K&lt;/kbd&gt; / &lt;kbd&gt;Option + ⌘ + J&lt;/kbd&gt;) and paste this to add a new hill in the foreground of the image:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token function&quot;&gt;drawHill&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;initialY&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;325&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;stepSize&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;shadowY&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;800&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;strokeStyle&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;#cfc&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;lineWidth&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token literal-property property&quot;&gt;shape&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wave&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2.5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0.6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pasting again will draw a new stroke with a new hill in the foreground. If the canvas is redrawn (you can kinda do that by hitting reload now) it produces an animation not unlike a 2D flight sim valley.&lt;/p&gt;
&lt;p&gt;Then I added a couple more functions to paint a gradient in the background for the atmosphere and fill a circle for the sun.&lt;/p&gt;
&lt;p&gt;Along the way I ran into some interesting and amusing unexpected outputs when I tried numbers that didn&#39;t output normal-looking scenery, that was fun too :)&lt;/p&gt;
&lt;p&gt;I hadn&#39;t had a phone with me when I saw the sunset, which meant I didn&#39;t have a reference for the colours of the scene I saw yesterday. Attempts to find it by nudging a colour selector about were unsatisfactory, and I like the reduced if flat greyscale palette I&#39;d kicked off with, so I&#39;m leaving it there. It has a 1990s Classic Mac feel I appreciate, even if it&#39;s an impossibly wide (to the smaller Mac displays of that era) 720 pixels.&lt;/p&gt;
&lt;p&gt;Is it Wakari as seen from Pine Hill? It&#39;s far more a gestural sketch from my imagination, and it&#39;s a little mathematical in arrangement and tone, but I am happy with it!&lt;/p&gt;
&lt;p&gt;This is the first piece of procedural art I&#39;ve made in a long time and it&#39;s been fun.&lt;/p&gt;
&lt;p&gt;Thinking forward:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Make the shading on the hillside account for the sunlight rather than vertical&lt;/li&gt;
&lt;li&gt;Birds?&lt;/li&gt;
&lt;li&gt;Trees &amp;amp; other shapes on the horizon&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Reporting from Composer to Dependency Track from Gitlab CI</title>
    <link href="https://chris.bur.gs/dependency-track-gitlab-ci/"/>
    <updated>2024-11-25T00:00:00Z</updated>
    <id>https://chris.bur.gs/dependency-track-gitlab-ci/</id>
    <content xml:lang="en" type="html">&lt;p&gt;For organisations using open source to build software, I believe a high-level view of the components being consumed is critical to maintenance and vulnerability response. Tools like this offer not just vulnerability detection, but also risk reduction, opportunity to manage dependencies more efficiently across projects, risk reduction, and license and policy compliance.&lt;/p&gt;
&lt;p&gt;Dependency Track is a tool which organisations can use to gather and analyse their component usage. It does this by permitting submission of software bill of materials (SBOM) reports into a store, and combining this with automated  import of vulnerability and component data alongside organisational policies.&lt;/p&gt;
&lt;p&gt;I&#39;d demoed Dependency Track in &lt;a href=&quot;https://drupalsouth.org/events/drupalsouth-shorts-2021-drupal20/sessions/drupal-security-lighting-talks-discussion-panel&quot;&gt;Drupal South Shorts in 2021&lt;/a&gt;, and figured it was worth another look as part of a project I&#39;m working on.&lt;/p&gt;
&lt;h2&gt;Bringing up a Dependency Track instance&lt;/h2&gt;
&lt;p&gt;I used Dependency Track&#39;s instructions to deploy an instance using Docker Compose per their &lt;a href=&quot;https://docs.dependencytrack.org/getting-started/deploy-docker/&quot;&gt;Deploying with Docker&lt;/a&gt; instructions. This works great - but do pay attention to the system requirements; Dependency Track looks lightweight on the surface, but behind the scenes it is importing and analysing a fair bit of data.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/dependency-track-grafana-cpu-usage-install.png&quot; alt=&quot;CPU usage bringing up Dependency Track&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Dependency Track has a frontend and an API service. For reporting to Dependency Track, the API endpoint needs to be externally accessible.&lt;/p&gt;
&lt;p&gt;Note that there&#39;s a short period after you bring the environment up when it&#39;s chugging away yoinking data from NIST and various other sources. Allow that time to ingest before expecting useful output.&lt;/p&gt;
&lt;p&gt;The default creds are admin/admin, which you&#39;ll change before accessing the admin interface. Post-login, you probably want to set up a few things in the admin section; I recommend configuring &lt;strong&gt;Administration &amp;gt; Email&lt;/strong&gt; for notifications. As I would be analysing Drupal projects for my instance, under &lt;strong&gt;Administration &amp;gt; Repositories &amp;gt; Composer&lt;/strong&gt;, I added Drupal&#39;s Packagist endpoint &lt;code&gt;https://packages.drupal.org/8&lt;/code&gt; as well.&lt;/p&gt;
&lt;p&gt;I then added a couple of &lt;strong&gt;Projects&lt;/strong&gt; to test with. One of these is &lt;a href=&quot;https://gitlab.com/xurizaemon/commerce-demo/&quot;&gt;xurizaemon/commerce-demo&lt;/a&gt;, which is a public repository.&lt;/p&gt;
&lt;h2&gt;Generating SBOM in CI&lt;/h2&gt;
&lt;p&gt;I use &lt;a href=&quot;https://github.com/CycloneDX/cyclonedx-php-composer&quot;&gt;CycloneDX/cyclonedx-php-composer&lt;/a&gt; for this. There are other tools, such as &lt;a href=&quot;https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium&quot;&gt;Gemnasium&lt;/a&gt;, and they might work equally well. In the Gitlab CI job, I&#39;ll install &lt;code&gt;CycloneDX/cyclonedx-php-composer&lt;/code&gt; using &lt;code&gt;composer global require&lt;/code&gt; (to keep it separate from the project-specific dependencies), then execute the new Composer command.&lt;/p&gt;
&lt;p&gt;The job depends on some configuration, which I apply in Gitlab&#39;s CI/CD Variables settings. These are:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable key&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DEPENDENCYTRACK_API_URL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;https://dt-api.example.org&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Base URL of the Dependency Track API service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DEPENDENCYTRACK_API_KEY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;abc_aaaabbbbccccddddEEEEFFFFGGGGHHHH&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;API key for reporting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DEPENDENCYTRACK_PROJECT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;615683fe-abcd-1234-abcd-a04a7514e7f5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Project identifier&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;For the &lt;strong&gt;API Key&lt;/strong&gt;, in Dependency Track navigate &lt;strong&gt;Administration &amp;gt; Access Management &amp;gt; Teams&lt;/strong&gt;; the &amp;quot;Automation&amp;quot; team should have an API key already assigned. For the &lt;strong&gt;Project ID&lt;/strong&gt;, in Dependency Track navigate &lt;strong&gt;Projects &amp;gt; {selected project} &amp;gt; View Details&lt;/strong&gt; and copy the &lt;strong&gt;Object Identifier&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;If these aren&#39;t configured, the job will bail out with a configuration warning.&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;report dependencies&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; check&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; composer&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH&#39;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;$CI_PIPELINE_SOURCE == &quot;merge_request_event&quot;&#39;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;needs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; composer install&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;before_script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;# Verify that the required variables have been configured in Gitlab CI/CD Variables before proceeding.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;&lt;br /&gt;      if [ -z &quot;${DEPENDENCYTRACK_API_KEY}&quot; -o -z &quot;${DEPENDENCYTRACK_API_URL}&quot; -o -z &quot;${DEPENDENCYTRACK_PROJECT}&quot; ]; then&lt;br /&gt;        echo &quot;Configure DEPENDENCYTRACK_API_KEY, DEPENDENCYTRACK_API_URL and DEPENDENCYTRACK_PROJECT to report to Dependency Track.&quot;&lt;br /&gt;        exit 1&lt;br /&gt;      fi&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; composer global config &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;no&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;plugins allow&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;plugins.cyclonedx/cyclonedx&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;php&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;composer true&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; composer global require cyclonedx/cyclonedx&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;php&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;composer&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;# Generate an SBOM in XML format for submission.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; composer CycloneDX&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;make&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;sbom &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;format=XML &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;file=&quot;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;CI_PROJECT_DIR&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;/composer&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;sbom.cdx.xml&quot;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;# JSON for submission requires DT project identifier and b64encoded BOM XML.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;&lt;br /&gt;      echo &quot;{&lt;br /&gt;        &#92;&quot;project&#92;&quot;: &#92;&quot;${DEPENDENCYTRACK_PROJECT}&#92;&quot;,&lt;br /&gt;        &#92;&quot;bom&#92;&quot;: &#92;&quot;$( base64 --wrap=0 ${CI_PROJECT_DIR}/composer-sbom.cdx.xml )&#92;&quot;&lt;br /&gt;      }&quot; &gt; ${CI_PROJECT_DIR}/report-bom.json&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;# PUT the JSON to Dependency Track endpoint.&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;&lt;br /&gt;      curl -X &quot;PUT&quot; &quot;${DEPENDENCYTRACK_API_URL}/api/v1/bom&quot; &#92;&lt;br /&gt;        -H &quot;Content-Type: application/json&quot; &#92;&lt;br /&gt;        -H &quot;X-API-Key: ${DEPENDENCYTRACK_API_KEY}&quot; &#92;&lt;br /&gt;        -d @${CI_PROJECT_DIR}/report-bom.json&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;artifacts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; composer&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;sbom.cdx.xml&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; report&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;bom.json&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;allow_failure&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this correctly configured, we should see output from the Gitlab CI job (&lt;a href=&quot;https://gitlab.com/xurizaemon/commerce-demo/-/jobs/8454221727&quot;&gt;job from Commerce Demo project mentioned above&lt;/a&gt;) which shows the report being submitted to our Dependency Track service.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ composer CycloneDX:make-sbom --output-format=XML --output-file=&amp;quot;${CI_PROJECT_DIR}/composer-sbom.cdx.xml&amp;quot;
$ echo &amp;quot;{ # collapsed multi-line command
$ curl -X &amp;quot;PUT&amp;quot; &amp;quot;${DEPENDENCYTRACK_API_URL}/api/v1/bom&amp;quot; &#92; # collapsed multi-line command
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  825k  100    48  100  825k      8   152k  0:00:06  0:00:05  0:00:01  151k
{&amp;quot;token&amp;quot;:&amp;quot;9090c0d5-6257-4b23-ae14-64d05fb4f349&amp;quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then in Dependency Track, we should see our gathered data.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/dependency-track-commerce-demo-report.png&quot; alt=&quot;Vulnerabilities displayed for a project in Dependency Track&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I&#39;ll return to &amp;quot;what can we do with this information&amp;quot; in a later post maybe - until now, enjoy exploring Dependency Track.&lt;/p&gt;
&lt;p&gt;Related: &lt;a href=&quot;https://chris.bur.gs/composer-changes-from-ci/&quot;&gt;Showing Composer changes from a Gitlab CI job&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Showing Composer dependency changes on MRs from a CI job</title>
    <link href="https://chris.bur.gs/composer-changes-from-ci/"/>
    <updated>2024-11-12T00:00:00Z</updated>
    <id>https://chris.bur.gs/composer-changes-from-ci/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I am a fan of &lt;a href=&quot;https://github.com/davidrjonas/composer-lock-diff&quot;&gt;davidrjonas/composer-lock-diff&lt;/a&gt; for making visible the dependency changes in a change, and often use its &lt;code&gt;--md&lt;/code&gt; output to make this highly visible in a merge request.&lt;/p&gt;
&lt;p&gt;I decided to make a quick job that would give me a one-click run of the tool in the merge request pipelines, so I could copy from there back to the MR.&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;composer changes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;needs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; check&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $CI_PIPELINE_SOURCE == &quot;merge_request_event&quot;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;changes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; composer.lock&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;allow_failure&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; PATH=$PATH&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;$( composer global config home )/vendor/bin&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; git fetch origin&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; composer global require davidrjonas/composer&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;lock&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;diff&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;^1.0&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; composer&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;lock&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;diff &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;from origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;md &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; tee composer&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;changes.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Catching an artifact with &lt;code&gt;tee&lt;/code&gt; isn&#39;t much help really, it&#39;s quicker to copy straight from the job output, but you can add that file as an artifact if you like.&lt;/p&gt;
&lt;p&gt;(I&#39;m still deciding whether this is best with or without a &lt;code&gt;when: manual&lt;/code&gt;.)&lt;/p&gt;
&lt;p&gt;Probably Gitlab has a more full-featured version of this in its fancy money tools - but this small addition is really handy.&lt;/p&gt;
&lt;p&gt;It would be nice to extend this to automatically add a &amp;quot;Dependency changes&amp;quot; section to the MR description, or in a comment.&lt;/p&gt;
&lt;p&gt;See also: &lt;a href=&quot;https://chris.bur.gs/dependency-track-gitlab-ci/&quot;&gt;Dependency Track from Gitlab CI&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Check Chrome connectivity in Gitlab CI</title>
    <link href="https://chris.bur.gs/chrome-info-gitlab-ci/"/>
    <updated>2024-11-12T00:00:00Z</updated>
    <id>https://chris.bur.gs/chrome-info-gitlab-ci/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Wiring up Behat in a project the other day, I wanted to validate Chrome connectivity in the job environment quickly - without waiting for all the steps that would ordinarily depend on, such as &lt;code&gt;composer install&lt;/code&gt; and other build processes.&lt;/p&gt;
&lt;p&gt;I stripped a more full featured CI job down to the bare minimum to do this and figured it&#39;s worth capturing. Here&#39;s what I used:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;zenika chrome&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; composer&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; test&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;needs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; zenika/alpine&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;chrome&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;latest&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; chrome&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;--disable-gpu&#39;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;--headless&#39;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;--remote-debugging-address=0.0.0.0&#39;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;--remote-debugging-port=9222&#39;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;--no-sandbox&#39;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;--disable-web-security&#39;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;--window-size=1080,1920&#39;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;variables&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;CHROME_URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://chrome:9222&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;HOST_PORT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;build:80&quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;&lt;br /&gt;      curl --silent --show-error --header &quot;Host: localhost&quot; ${CHROME_URL}/json/version | tee chrome-version.json  &lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;artifacts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; always&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;artifacts-${CI_JOB_NAME}-${CI_COMMIT_SHA}&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; chrome&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;version.json&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here&#39;s that same job again, but for Pantheon&#39;s build-tools image. This one &lt;a href=&quot;https://gitlab.com/xurizaemon/ci-play/-/pipelines/1537034789/builds&quot;&gt;took a full minute longer to run&lt;/a&gt; - the image contains much more than just Chrome, so it takes longer to pull.&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;pantheon build tools&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; composer&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; test&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;needs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; quay.io/pantheon&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;public/build&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;tools&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;ci&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;6.x&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; chrome&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;google-chrome&#39;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;--disable-gpu&#39;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;--headless&#39;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;--remote-debugging-address=0.0.0.0&#39;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;--remote-debugging-port=9222&#39;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;--no-sandbox&#39;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;--disable-web-security&#39;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;--window-size=1080,1920&#39;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;variables&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;CHROME_URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://chrome:9222&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;HOST_PORT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;build:80&quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;&lt;br /&gt;      curl --silent --show-error --header &quot;Host: localhost&quot; ${CHROME_URL}/json/version | tee chrome-version.json  &lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;artifacts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; always&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;artifacts-${CI_JOB_NAME}-${CI_COMMIT_SHA}&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; chrome&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;version.json&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>Lighthouse browser crashes</title>
    <link href="https://chris.bur.gs/lighthouse-browser-crashes/"/>
    <updated>2024-08-26T00:00:00Z</updated>
    <id>https://chris.bur.gs/lighthouse-browser-crashes/</id>
    <content xml:lang="en" type="html">&lt;p&gt;For this site I have a small CI job (based on &lt;a href=&quot;https://gitlab.com/gitlab-ci-utils/lighthouse&quot;&gt;gitlab-ci-utils/lighthouse&lt;/a&gt;) which checks the built site in Lighthouse.&lt;/p&gt;
&lt;p&gt;This job runs after the site has been built (using Eleventy) in &lt;code&gt;test build&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# https://gitlab.com/gitlab-ci-utils/lighthouse&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;lighthouse mr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;needs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; test build&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; registry.gitlab.com/gitlab&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;ci&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;utils/lighthouse&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;latest&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; post&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; mkdir &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;p tmp&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; serve ./public/ &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt; tmp/serve.log 2&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token important&quot;&gt;&amp;amp;1&lt;/span&gt; &amp;amp; wait&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;on http&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//localhost&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;3000/&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; lighthouse http&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//localhost&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;3000 &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;chrome&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;flags=&quot;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;headless&quot;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; lighthouse http&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//localhost&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;3000/photos &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;chrome&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;flags=&quot;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;headless&quot;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; lighthouse http&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//localhost&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;3000/projects &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;chrome&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;flags=&quot;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;headless&quot;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $CI_COMMIT_REF_NAME &lt;span class=&quot;token tag&quot;&gt;!=&lt;/span&gt; $CI_DEFAULT_BRANCH&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;artifacts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; always&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; localhost&lt;span class=&quot;token important&quot;&gt;*.html&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; tmp&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However, today it was failing. The final line in the job output showed the error:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;Runtime error encountered: Browser tab has unexpectedly crashed.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I had a look at the Lighthouse CLI help via &lt;code&gt;docker run -ti registry.gitlab.com/gitlab-ci-utils/lighthouse:latest bash&lt;/code&gt; and then &lt;code&gt;lighthouse --help&lt;/code&gt;. I&#39;ve seen memory issues pop up in CI before, so I was looking first for something to tell Lighthouse to adjust its memory expectations.&lt;/p&gt;
&lt;p&gt;I also had a dig around and found a few issues (&lt;a href=&quot;https://github.com/GoogleChrome/lighthouse-ci/issues/1052&quot;&gt;GoogleChrome/lighthouse-ci#1052: Browser crashes on lhci autorun in Docker #1052&lt;/a&gt;, &lt;a href=&quot;https://github.com/puppeteer/puppeteer/issues/1834&quot;&gt;puppeteer/puppeteer#1834: Add --disable-dev-shm-usage to default launch flags&lt;/a&gt;), but none that looked like an exact match for what I was seeing ... except it did just look like Chrome was crashing, and I&#39;ve seen that before as part of &lt;a href=&quot;https://gitlab.com/behat-chrome/chrome-mink-driver/&quot;&gt;behat-chrome/chrome-mink-driver&lt;/a&gt; use.&lt;/p&gt;
&lt;p&gt;While there wasn&#39;t a direct flag in the &lt;code&gt;lighthouse&lt;/code&gt; help, we have &lt;code&gt;--chrome-flags&lt;/code&gt;, so I tried Chrome&#39;s &lt;code&gt;--no-dev-shm-usage&lt;/code&gt; (ref &lt;a href=&quot;https://peter.sh/experiments/chromium-command-line-switches/&quot;&gt;Chromium command-line switches&lt;/a&gt;), which I&#39;ve frequently found makes Chrome behave better in CI:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# https://gitlab.com/gitlab-ci-utils/lighthouse&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;lighthouse mr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;needs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; test build&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; registry.gitlab.com/gitlab&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;ci&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;utils/lighthouse&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;latest&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; post&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; mkdir &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;p tmp&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; serve ./public/ &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt; tmp/serve.log 2&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token important&quot;&gt;&amp;amp;1&lt;/span&gt; &amp;amp; wait&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;on http&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//localhost&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;3000/&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; lighthouse http&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//localhost&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;3000 &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;chrome&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;flags=&quot;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;headless &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;no&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;dev&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;shm&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;usage&quot;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; lighthouse http&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//localhost&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;3000/photos &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;chrome&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;flags=&quot;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;headless &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;no&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;dev&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;shm&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;usage&quot;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; lighthouse http&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//localhost&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;3000/projects &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;chrome&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;flags=&quot;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;headless &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;no&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;dev&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;shm&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;usage&quot;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $CI_COMMIT_REF_NAME &lt;span class=&quot;token tag&quot;&gt;!=&lt;/span&gt; $CI_DEFAULT_BRANCH&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;artifacts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; always&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; localhost&lt;span class=&quot;token important&quot;&gt;*.html&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; tmp&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A little surprisingly, that worked, and the pipeline started passing ... huh!&lt;/p&gt;
&lt;p&gt;It could be a little tidier with the repetition removed:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# https://gitlab.com/gitlab-ci-utils/lighthouse&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;lighthouse mr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;needs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; test build&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; registry.gitlab.com/gitlab&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;ci&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;utils/lighthouse&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;latest&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; post&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;variables&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;BASE_URL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; http&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//localhost&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3000&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;CHROME_FLAGS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;--headless --disable-dev-shm-usage&quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; mkdir &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;p tmp&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; serve ./public/ &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt; tmp/serve.log 2&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token important&quot;&gt;&amp;amp;1&lt;/span&gt; &amp;amp; wait&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;on http&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//localhost&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;3000/&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; lighthouse $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;BASE_URL&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;chrome&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;flags=&quot;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;CHROME_FLAGS&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&quot;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; lighthouse $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;BASE_URL&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;/photos &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;chrome&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;flags=&quot;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;CHROME_FLAGS&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&quot;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; lighthouse $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;BASE_URL&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;/projects &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;chrome&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;flags=&quot;$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;CHROME_FLAGS&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&quot;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;rules&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $CI_COMMIT_REF_NAME &lt;span class=&quot;token tag&quot;&gt;!=&lt;/span&gt; $CI_DEFAULT_BRANCH&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;artifacts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; always&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;paths&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; localhost&lt;span class=&quot;token important&quot;&gt;*.html&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; tmp&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The job above, &lt;code&gt;lighthouse mr&lt;/code&gt;, runs against the changed version of the site in the CI build environment. There&#39;s a separate one which runs on deploy to record results for the published version, so I have a reference for what&#39;s live as well.&lt;/p&gt;
&lt;p&gt;As a side-effect of looking at this error, the site also has 10KB less CSS thanks to minification when the TailwindCSS job runs. 🎉&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Visidata, a CLI powertool for CSV</title>
    <link href="https://chris.bur.gs/visidata-hello/"/>
    <updated>2024-06-28T00:00:00Z</updated>
    <id>https://chris.bur.gs/visidata-hello/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I&#39;ve been messing around with spreadsheets a little more than usual the last week or two, and popping between shell environment and a spreadsheet was making me feel like I was shifting gears noisily. Spreadsheets are amazing tools, but when I&#39;m happy building in some kind of flow, switching in and out of spreadsheet interface harshes my buzz. My IDE meanwhile: great at many things, but not tabular data.&lt;/p&gt;
&lt;p&gt;I decided to give &lt;a href=&quot;https://www.visidata.org/&quot;&gt;Visidata&lt;/a&gt; a spin, and &lt;a href=&quot;https://toot.cafe/@xurizaemon/112681109468093980&quot;&gt;found it delivered what I wanted&lt;/a&gt; - a quick, terminal-based editing interface to the tabular data world that didn&#39;t feel like too much of a shift from the shell. I had to look up the command basics, but that was not dissimilar to when I&#39;m using a regular spreadsheet - I&#39;m an infrequent visitor!&lt;/p&gt;
&lt;p&gt;Soon enough I found an Interesting Task. &lt;strong&gt;I wanted to add an additional column filled with UUIDs&lt;/strong&gt;, so that I could create content entities with a known reference and then round trip data in and out of the CSV. With Visidata, I was delighted to find I could use familiar UNIX tools to add myself a column of UUID to a CSV. My favourite solution the one I found in Visidata&#39;s help:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;z; expr  create new column from bash expr, with $columnNames as variables
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I entered &lt;code&gt;z;&lt;/code&gt; then &lt;code&gt;uuidgen&lt;/code&gt;, and Visidata added two new columns: &lt;code&gt;uuidgen stdout&lt;/code&gt; (filled with UUIDs) and &lt;code&gt;uuidgen stderr&lt;/code&gt; (empty). I renamed &lt;code&gt;uuidgen stdout&lt;/code&gt; (select, &lt;code&gt;^&lt;/code&gt;, enter new value) and deleted the additional column (select, &lt;code&gt;-&lt;/code&gt; I think?) and was on my way.&lt;/p&gt;
&lt;p&gt;Nice and easy. Visidata looks like a great tool - will definitely keep it available, as it seems like it has a ton of potential for slicing and dicing data in this format and many more. Looking forward to exploring more!&lt;/p&gt;
&lt;p&gt;Fond memories of my Mum using &lt;a href=&quot;http://www.trs-80.org/visicalc/&quot;&gt;Visicalc on a TRS-80&lt;/a&gt; ...&lt;/p&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li&gt;A UUID generation function is available in both LibreOffice Calc (&lt;code&gt;=GUID()&lt;/code&gt;) and Google Sheets (&lt;code&gt;=UUID()&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;for ii in $( seq 20 ); do uuidgen ; done&lt;/code&gt;, copying the output and pasting into a blank column works fine too.&lt;/li&gt;
&lt;/ol&gt;
</content>
  </entry>
  <entry>
    <title>Oversharing with NPM</title>
    <link href="https://chris.bur.gs/npm-overshare/"/>
    <updated>2024-06-19T00:00:00Z</updated>
    <id>https://chris.bur.gs/npm-overshare/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Last weekend I released &lt;a href=&quot;https://www.npmjs.com/package/@xurizaemon/eleventy-immich&quot;&gt;@xurizaemon/eleventy-immich&lt;/a&gt;. I used Github Actions to trigger an NPM release from a pushed tag, and after making the initial release with an arbitrary version of v0.1.0, I reviewed &lt;a href=&quot;https://www.npmjs.com/package/@xurizaemon/eleventy-immich/v/0.1.0&quot;&gt;the @xurizaemon/eleventy-immich@v0.1.0 release page&lt;/a&gt; on NPMJS.&lt;/p&gt;
&lt;p&gt;The &amp;quot;unpacked size&amp;quot; value was 28.5MB, and I realised I&#39;d made a mistake.&lt;/p&gt;
&lt;p&gt;I&#39;m more familiar with Composer releases, and when you release a Composer package the built archive is constrained to the sourcecode of the tagged release. If you wanted to release a Composer package with some generated files - say, translations from an external source, or built/compiled CSS or JS, my understanding is that you need to build those additional files first &lt;em&gt;and commit them to your Composer codebase&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;NPM doesn&#39;t have this limitation; the release process can include build steps, and the release archive can include build-time results.&lt;/p&gt;
&lt;p&gt;My package&#39;s build steps included running tests, and the tests performed end-to-end integration tests against an Immich instance I host. That 28.5MB of files included about 5KB of sourcecode, and some additional files. This counts as &amp;quot;oversharing&amp;quot;, and I now needed to review whether I&#39;d inadvertently shared anything I shouldn&#39;t have.&lt;/p&gt;
&lt;p&gt;The test images weren&#39;t my concern. I&#39;ll document how the tests work in a &lt;a href=&quot;https://github.com/xurizaemon/eleventy-immich&quot;&gt;more appropriate location&lt;/a&gt;, but I had good confidence that the images themselves weren&#39;t sensitive; the tests retrieve specific images individually and from a specific album. My concern was other details, such as the API keys used for the integration, and any other details I might find in the built archive. Also, I find being &lt;em&gt;interested&lt;/em&gt; in problems is a really effective way to approach them, so my inclination was to learn more rather than to panic.&lt;/p&gt;
&lt;p&gt;By the time I&#39;d identified my mistake, I could also see that there were 9 downloads of my newly released package. How exciting! Do I think there were nine people that stumbled onto an interesting NPM package and typed &lt;code&gt;npm install @xurizaemon/eleventy-immich&lt;/code&gt; straight away? No, I don&#39;t. Automated systems are watching new releases (and new Github pushes) for &amp;quot;interesting&amp;quot; details all the time, and in this world &amp;quot;interesting&amp;quot; means &amp;quot;profitable&amp;quot;. I have to assume that any details shared will already have been seen by someone with potential for malicious use.&lt;/p&gt;
&lt;h3&gt;First action&lt;/h3&gt;
&lt;p&gt;My first action was to correct my mistake by adding some &lt;code&gt;.gitignore&lt;/code&gt; entries (which NPM respects) to the project, and releasing &lt;a href=&quot;https://github.com/xurizaemon/eleventy-immich/releases/tag/v0.1.1&quot;&gt;v0.1.1&lt;/a&gt; at a much more reasonable ~5KB in place of the previous 28MB.&lt;/p&gt;
&lt;h3&gt;Assessing the damage&lt;/h3&gt;
&lt;p&gt;With a replacement release out the door, my next step was to review what I&#39;d shared.&lt;/p&gt;
&lt;p&gt;Let&#39;s grab a copy of the files I published:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;chris@thip: ~$ &lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; /tmp/throwaway &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; /tmp/throwaway&lt;br /&gt;chris@thip:/tmp/throwaway$ &lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; init &lt;span class=&quot;token parameter variable&quot;&gt;-y&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# output skipped&lt;/span&gt;&lt;br /&gt;chris@thip:/tmp/throwaway$ &lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; @xurizaemon/eleventy-immich@v0.1.0&lt;br /&gt;&lt;br /&gt;added &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; package, and audited &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; packages &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; 2s&lt;br /&gt;&lt;br /&gt;found &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; vulnerabilities&lt;br /&gt;chris@thip:/tmp/throwaway$ &lt;span class=&quot;token function&quot;&gt;cat&lt;/span&gt; package.json&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;throwaway&quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.0.0&quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&quot;main&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;index.js&quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&quot;test&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;echo &lt;span class=&quot;token entity&quot; title=&quot;&#92;&amp;quot;&quot;&gt;&#92;&quot;&lt;/span&gt;Error: no test specified&lt;span class=&quot;token entity&quot; title=&quot;&#92;&amp;quot;&quot;&gt;&#92;&quot;&lt;/span&gt; &amp;amp;&amp;amp; exit 1&quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&quot;keywords&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&quot;author&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&quot;license&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ISC&quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;,&lt;br /&gt;  &lt;span class=&quot;token string&quot;&gt;&quot;dependencies&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string&quot;&gt;&quot;@xurizaemon/eleventy-immich&quot;&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^0.1.0&quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, what made it into the release?&lt;/p&gt;
&lt;p&gt;Firstly: does the downloaded files approximate what I think got released? Yes, it does.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;chris@thip:/tmp/throwaway$ &lt;span class=&quot;token function&quot;&gt;du&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-sh&lt;/span&gt; node_modules/@xurizaemon/eleventy-immich/&lt;br /&gt;28M	node_modules/@xurizaemon/eleventy-immich/&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Initial inspection&lt;/h3&gt;
&lt;p&gt;Let&#39;s have a look at what I need to explore. &lt;code&gt;tree -da&lt;/code&gt; will show me the directories of a directory:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;chris@thip:/tmp/throwaway/node_modules/@xurizaemon/eleventy-immich$ tree &lt;span class=&quot;token parameter variable&quot;&gt;-ad&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;&lt;br /&gt;├── .cache&lt;br /&gt;├── .github&lt;br /&gt;│   └── workflows&lt;br /&gt;├── .idea&lt;br /&gt;│   ├── codeStyles&lt;br /&gt;│   └── inspectionProfiles&lt;br /&gt;├── public&lt;br /&gt;│   └── media&lt;br /&gt;│       └── img&lt;br /&gt;└── &lt;span class=&quot;token builtin class-name&quot;&gt;test&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt; directories&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looking at the file contents as well: &lt;code&gt;tree -a&lt;/code&gt;&lt;/p&gt;
&lt;details&gt;&lt;summary&gt;Click to see output&lt;/summary&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;chris@thip:/tmp/throwaway/node_modules/@xurizaemon/eleventy-immich$ tree &lt;span class=&quot;token parameter variable&quot;&gt;-a&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt;&lt;br /&gt;├── .cache&lt;br /&gt;│   ├── eleventy-fetch-13d629a2fb0ab7cea5b05589667f7e&lt;br /&gt;│   ├── eleventy-fetch-13d629a2fb0ab7cea5b05589667f7e.buffer&lt;br /&gt;│   ├── eleventy-fetch-317aeed09661ed23b93071dff930bd&lt;br /&gt;│   ├── eleventy-fetch-317aeed09661ed23b93071dff930bd.json&lt;br /&gt;│   ├── eleventy-fetch-67adde77a89e4672d3ac77c7313615&lt;br /&gt;│   ├── eleventy-fetch-67adde77a89e4672d3ac77c7313615.json&lt;br /&gt;│   ├── eleventy-fetch-77be3b5f8c2ec56bb3890fa6619ba6&lt;br /&gt;│   ├── eleventy-fetch-77be3b5f8c2ec56bb3890fa6619ba6.json&lt;br /&gt;│   ├── eleventy-fetch-7c85f08df0509d9d7825bf367d12d3&lt;br /&gt;│   ├── eleventy-fetch-7c85f08df0509d9d7825bf367d12d3.json&lt;br /&gt;│   ├── eleventy-fetch-a1fb53576fce4640adb4176510b7af&lt;br /&gt;│   ├── eleventy-fetch-a1fb53576fce4640adb4176510b7af.buffer&lt;br /&gt;│   ├── eleventy-fetch-a4f2ce9274ea106198f56d6355e3cc&lt;br /&gt;│   ├── eleventy-fetch-a4f2ce9274ea106198f56d6355e3cc.json&lt;br /&gt;│   ├── eleventy-fetch-ab5c0e42fecc55d7316494c515354d&lt;br /&gt;│   ├── eleventy-fetch-ab5c0e42fecc55d7316494c515354d.buffer&lt;br /&gt;│   ├── eleventy-fetch-b2e62be056ad837dbc96ebca8fb4ac&lt;br /&gt;│   ├── eleventy-fetch-b2e62be056ad837dbc96ebca8fb4ac.buffer&lt;br /&gt;│   ├── eleventy-fetch-b486d1283d68c1996d39e5c71be87f&lt;br /&gt;│   ├── eleventy-fetch-b486d1283d68c1996d39e5c71be87f.json&lt;br /&gt;│   ├── eleventy-fetch-c4f8f26c84ede2b465b604152680b1&lt;br /&gt;│   ├── eleventy-fetch-c4f8f26c84ede2b465b604152680b1.buffer&lt;br /&gt;│   ├── eleventy-fetch-c8a1319796be03a4a3a8fb0f82bd02&lt;br /&gt;│   ├── eleventy-fetch-c8a1319796be03a4a3a8fb0f82bd02.json&lt;br /&gt;│   ├── eleventy-fetch-f37c2de21a89f407822b63fbaa5f95&lt;br /&gt;│   ├── eleventy-fetch-f37c2de21a89f407822b63fbaa5f95.json&lt;br /&gt;│   ├── eleventy-fetch-f85c0fb9f6a0fd9713efd6f89f2eee&lt;br /&gt;│   └── eleventy-fetch-f85c0fb9f6a0fd9713efd6f89f2eee.buffer&lt;br /&gt;├── eslint.config.mjs&lt;br /&gt;├── .github&lt;br /&gt;│   └── workflows&lt;br /&gt;│       ├── nodejs.yml&lt;br /&gt;│       └── npm-publish.yml&lt;br /&gt;├── .idea&lt;br /&gt;│   ├── codeStyles&lt;br /&gt;│   │   ├── codeStyleConfig.xml&lt;br /&gt;│   │   └── Project.xml&lt;br /&gt;│   ├── eleventy-immich.iml&lt;br /&gt;│   ├── GitLink.xml&lt;br /&gt;│   ├── inspectionProfiles&lt;br /&gt;│   │   └── Project_Default.xml&lt;br /&gt;│   ├── modules.xml&lt;br /&gt;│   ├── php.xml&lt;br /&gt;│   └── vcs.xml&lt;br /&gt;├── immich.js&lt;br /&gt;├── LICENSE&lt;br /&gt;├── package.json&lt;br /&gt;├── public&lt;br /&gt;│   └── media&lt;br /&gt;│       └── img&lt;br /&gt;│           ├── 6F6xc8RT4F-300.jpeg&lt;br /&gt;│           ├── 6F6xc8RT4F-600.jpeg&lt;br /&gt;│           ├── 8wDxzJOqow-300.jpeg&lt;br /&gt;│           ├── 8wDxzJOqow-600.jpeg&lt;br /&gt;│           ├── AitV6V6-2X-300.jpeg&lt;br /&gt;│           ├── AitV6V6-2X-600.jpeg&lt;br /&gt;│           ├── heH3xr0I7G-300.jpeg&lt;br /&gt;│           ├── heH3xr0I7G-600.jpeg&lt;br /&gt;│           ├── oupuEcpEIs-300.jpeg&lt;br /&gt;│           ├── oupuEcpEIs-600.jpeg&lt;br /&gt;│           ├── rDX8xf_N04-300.jpeg&lt;br /&gt;│           └── rDX8xf_N04-600.jpeg&lt;br /&gt;├── README.md&lt;br /&gt;└── &lt;span class=&quot;token builtin class-name&quot;&gt;test&lt;/span&gt;&lt;br /&gt;    └── test.js&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt; directories, &lt;span class=&quot;token number&quot;&gt;56&lt;/span&gt; files&lt;/code&gt;&lt;/pre&gt;
&lt;/details&gt;
&lt;h3&gt;Reviewing the files on the surface&lt;/h3&gt;
&lt;p&gt;What can I see from filenames alone?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.cache/&lt;/code&gt; contains filesystem caches generated by &lt;a href=&quot;https://github.com/11ty/eleventy-fetch&quot;&gt;eleventy-fetch&lt;/a&gt;. These requests were authenticated, so i need to review them for sensitive data - eg a cached request header.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.idea/&lt;/code&gt; indicates use of JetBrains tooling: My IDE (family) is revealed.
&lt;ul&gt;
&lt;li&gt;An IDE plugin&#39;s configuration is present, revealing the use of &lt;a href=&quot;https://plugins.jetbrains.com/plugin/8183-gitlink&quot;&gt;GitLink&lt;/a&gt;. (Great for right-click and share link to colleague, btw.)&lt;/li&gt;
&lt;li&gt;Some other configuration is present in the &lt;code&gt;.idea/&lt;/code&gt; directory, eg code formatting configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Inspecting file contents&lt;/h3&gt;
&lt;p&gt;And inspecting the files, what else can I see?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.cache/&lt;/code&gt; entries:
&lt;ul&gt;
&lt;li&gt;Each cache entry contains a pair of files: One with JSON metadata (eg &lt;code&gt;.cache/eleventy-fetch-7c85f08df0509d9d7825bf367d12d3&lt;/code&gt;) with type and timestamp, and another with the cached response (eg &lt;code&gt;.cache/eleventy-fetch-7c85f08df0509d9d7825bf367d12d3.json&lt;/code&gt;) with the retrieved data.&lt;/li&gt;
&lt;li&gt;For each of these pairs, the cached file is either a &lt;code&gt;.json&lt;/code&gt; (Immich API responses) or a &lt;code&gt;.buffer&lt;/code&gt; (Immich asset downloads, ie image data - these can be identified with &lt;code&gt;file&lt;/code&gt; command, then opened as that file type).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.cache/*.buffer&lt;/code&gt; file contents are already public; it&#39;s the same handful of images shared as an example from my page on the initial plugin concept.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.cache/*.json&lt;/code&gt; entries are copies of the Immich API response and do reveal additional data. I can see:
&lt;ul&gt;
&lt;li&gt;My Immich account email (predictable), personal name, avatar colour (purple!)&lt;/li&gt;
&lt;li&gt;Imported file filenames (filenames may leak metadata)&lt;/li&gt;
&lt;li&gt;Exif data, including geolocation data. Fortunately I selected film scans as my test images, and only location tagged a single photo taken at Long Beach.&lt;/li&gt;
&lt;li&gt;Immich &amp;quot;people&amp;quot; identification results for three people (myself and two family members), revealing the personal names I&#39;ve tagged those faces with.&lt;/li&gt;
&lt;li&gt;Immich &amp;quot;smartInfo&amp;quot; tags on a single image (&lt;code&gt;[&amp;quot;loupe&amp;quot;, jeweler&#39;s loupe&amp;quot;]&lt;/code&gt; on the Dunedin Farmer&#39;s Market selfie - it&#39;s taken with a 170-degree lens, and Immich sees the bulbous shape of a magnifying glass). Interesting, and &lt;em&gt;potentially&lt;/em&gt; revealing to see tags, but nothing to see here really.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;public/&lt;/code&gt; entries contains the set of images again, scaled to 300w and 600w.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Is any of this a worry to me?&lt;/h3&gt;
&lt;p&gt;Is there any of that I&#39;m concerned about? Not really. There&#39;s a little extra sharing, but very little of concern. It has got me wondering whether using different emails to sign into a service would add any additional security to my accounts, but not the best return for effort versus other account protection strategies.&lt;/p&gt;
&lt;h3&gt;What other options did I have?&lt;/h3&gt;
&lt;p&gt;I &lt;em&gt;could&lt;/em&gt; have &lt;a href=&quot;https://docs.npmjs.com/cli/v10/commands/npm-unpublish&quot;&gt;unpublished the release&lt;/a&gt; (&lt;code&gt;npm unpublish @xurizaemon/eleventy-immich@v0.1.0&lt;/code&gt;) from the registry. If I had been seriously concerned, I would have looked to that. However: at the time I knew of the error, I had already seen there were nine downloads. Unpublishing the release would not have addressed those, along with the fact that I&#39;d supplied the release tarball to a couple of organisations along the way: Github and npm, Inc. I assumed seven of the nine downloads were malicious actors and moved on to threat assessment.&lt;/p&gt;
&lt;h3&gt;Shared, not redacted&lt;/h3&gt;
&lt;p&gt;Instead, I figure I should add a copy of the released files here as a reference: &lt;a href=&quot;https://chris.bur.gs/media/eleventy-immich-0.1.0.tgz&quot;&gt;eleventy-immich-0.1.0.tgz&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Or, you can get the original URL via &lt;code&gt;npm view @xurizaemon/eleventy-immich@v0.1.0&lt;/code&gt;, or &lt;a href=&quot;https://www.npmjs.com/package/@xurizaemon/eleventy-immich?activeTab=code&quot;&gt;browse the v0.1.0 source files on npmjs.com&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Newcombe 35 - a custom theme for Eleventy</title>
    <link href="https://chris.bur.gs/newcombe-35/"/>
    <updated>2024-06-15T16:13:00Z</updated>
    <id>https://chris.bur.gs/newcombe-35/</id>
    <content xml:lang="en" type="html">&lt;p&gt;The image below shows &lt;a href=&quot;https://www.abebooks.co.uk/book-search/title/35-mm-photo-technique/author/h-s-newcombe/&quot;&gt;&amp;quot;35mm Photo Technique&amp;quot; by H.S. Newcombe&lt;/a&gt;, a photography technique book first published in 1946. It was a whimsical purchase from a second hand store, after having the plates for depth of field catch my eye. My copy is the fourteenth edition, from 1960. It&#39;s a delight.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://chris.bur.gs/media/newcombe-35-cover-shot.jpg&quot;&gt;&lt;img src=&quot;https://chris.bur.gs/media/newcombe-35-cover-shot.jpg&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This photo of the cover doesn&#39;t show well the giant embossed &amp;quot;35&amp;quot; that the book sports.&lt;/p&gt;
&lt;p&gt;The plates which first caught my eye in the store were the depth of field charts for various lense sizes - several pages of dense black ink with reversed white lines on paper.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://chris.bur.gs/media/newcombe-35-depth-of-field.jpg&quot;&gt;&lt;img src=&quot;https://chris.bur.gs/media/newcombe-35-depth-of-field.jpg&quot; alt=&quot;Plates: Depth of field&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;There&#39;s rich information through the book, presented clearly and at a good level of amateur technical detail, sufficiently that it&#39;s very relevant to using a film camera today. For a book first printed in 1946, I find its relevance and utility in 2024 impressive. The technology of film is explained clearly and in ways that I find reveal a clear explanation.&lt;/p&gt;
&lt;p&gt;For example, while using cameras recently I had been trying to envisage how an accurately timed exposure of up to 1/1000th of a second could be constructed using mechanical components in such a small space. The descriptions of various shutter mechanisms (pp 25-31) do a great job of outlining several common implementations in ways that I can understand by reading and thinking about the underlying technology. It&#39;s delightful and educating.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://chris.bur.gs/media/newcombe-35-film-characteristics.jpg&quot;&gt;&lt;img src=&quot;https://chris.bur.gs/media/newcombe-35-film-characteristics.jpg&quot; alt=&quot;Plates: Film characteristics&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Anyway.&lt;/em&gt; I&#39;m a bit in love with this book visually, and I decided it would be nice to use as the inspiration to style this site. Until now the site has been whatever the default look of a Tailwind CSS site is - and that didn&#39;t feel like much of a contribution. So!&lt;/p&gt;
&lt;h3&gt;Newcombe 35 is the style of this website&lt;/h3&gt;
&lt;p&gt;First things first: I&#39;m no typographer. This probably won&#39;t look polished. It also probably won&#39;t look much like the printed book, since this is a website. My style of writing is far less dense - I use plenty of single-sentence paragraphs, H. S. Newcombe didn&#39;t have emoji at his disposal, there&#39;s no fixed paper size ... it&#39;s very much an &amp;quot;inspired by ...&amp;quot; thing I&#39;m doing here. This design deviates substantially.&lt;/p&gt;
&lt;p&gt;I&#39;ve selected three fonts currently, all free fonts provided by &lt;a href=&quot;https://www.theleagueofmoveabletype.com/&quot;&gt;The League of Movable Type&lt;/a&gt; (go check them out):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.theleagueofmoveabletype.com/ostrich-sans&quot;&gt;Ostrich Sans&lt;/a&gt; for the title, navigation etc - similar to the typography of the author and press name on the book cover; the exterior branding.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.theleagueofmoveabletype.com/league-spartan&quot;&gt;League Spartan&lt;/a&gt; for section headings and the &amp;quot;table of contents&amp;quot; on the front page&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.theleagueofmoveabletype.com/fanwood&quot;&gt;Fanwood&lt;/a&gt; for body text&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&#39;ve implemented this where possible by customising the existing Tailwind-based theme I was using. I&#39;ve used Tailwind on a few projects, but largely as a way to &lt;em&gt;avoid&lt;/em&gt; doing much theming, by accepting a base design that was good enough. I&#39;m finding it works OK, with a definite balance of making some basic things (line height and text width overrides) slightly less direct, then finding that the rest of the design tends to hold together well as a result.&lt;/p&gt;
&lt;p&gt;A good time to ask myself then: Am I sold on Tailwind? It&#39;s ... working out, I guess. I do feel like I&#39;m flying under the envelope of where Tailwind might pay off - if you had a team of designers and needed to keep a coherent alignment with lots of moving components. That&#39;s definitely not a problem I face here!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Highly legible fonts</title>
    <link href="https://chris.bur.gs/highly-legible-fonts/"/>
    <updated>2024-06-13T17:13:00Z</updated>
    <id>https://chris.bur.gs/highly-legible-fonts/</id>
    <content xml:lang="en" type="html">&lt;p&gt;A conversation this evening reminded me that there are a few great fonts around that really assist legibility, that I appreciate having discovered these tools, and that others might appreciate them being shared on!&lt;/p&gt;
&lt;p&gt;Here are a few fonts I appreciate for a legibility boost. These are not the fonts I&#39;ve used on this site (see &lt;a href=&quot;https://chris.bur.gs/newcombe-35&quot;&gt;Newcombe 35&lt;/a&gt; for that). I often use the first font below, Atkinson Hyperlegible, as the font for reading in my web browser.&lt;/p&gt;
&lt;h2&gt;Atkinson Hyperlegible&lt;/h2&gt;
&lt;p&gt;This is my preferred font for reading. The font focuses on letterform distinction to increase character recognition, ultimately improving readability. It aims&lt;/p&gt;
&lt;p&gt;Where letters might otherwise be confusingly similar - especially in a reduced or low vision context - Atkinson Hyperlegible uses design techniques to ensure letter forms are more clearly distinguished.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/BIA_AtkinsonHyerlegible-recognizable-footprints-differentiated-letterforms-transparent.png&quot; alt=&quot;Atkinson Hyperlegible&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://brailleinstitute.org/freefont&quot;&gt;https://brailleinstitute.org/freefont&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;B612 by Airbus&lt;/h2&gt;
&lt;p&gt;Aircraft cockpits and displays demand minimal chance of confusion, for which Airbus created B612, a highly legible open source font family designed and tested to be used on aircraft cockpit screens. Airbus worked with &lt;a href=&quot;http://intactile.com/&quot;&gt;Intactile Design&lt;/a&gt; to produce an &amp;quot;Aeronautical font&amp;quot;, to improve the display of information on the cockpit screens, in particular in terms of legibility and comfort of reading, and to optimize the overall homogeneity of the cockpit.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/b612-dashboard-photo.png&quot; alt=&quot;Polarsys b612&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://b612-font.com/&quot;&gt;https://b612-font.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;OpenDyslexic&lt;/h2&gt;
&lt;p&gt;I&#39;ll be honest: this one doesn&#39;t work &lt;em&gt;for me&lt;/em&gt;. It does work for some, so I&#39;m including it here. OpenDyslexic is created to help with some of the symptoms of dyslexia.&lt;/p&gt;
&lt;p&gt;I see the design features which support those reading with Dyslexia. While I appreciate what it&#39;s doing, I find it too much for me, and that the font is &lt;em&gt;less&lt;/em&gt; legible - and that&#39;s fine, since I guess it&#39;s not designed for my needs. I&#39;m glad it exists and works for some folks.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/opendyslexic-font.png&quot; alt=&quot;Polarsys b612&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://opendyslexic.org/&quot;&gt;https://opendyslexic.org/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Extending your access - JWT validation failure and auth bypass</title>
    <link href="https://chris.bur.gs/extended-access-jwt-bypass/"/>
    <updated>2024-05-18T05:27:00Z</updated>
    <id>https://chris.bur.gs/extended-access-jwt-bypass/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I collected my first HackerOne bounty just recently, and it&#39;s disclosed now which means I can share here too!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://hackerone.com/reports/2472798&quot;&gt;Bug #2472798 on HackerOne&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The bug is fairly simple: the plugin implementation omitted to validate the signature of the provided JWT, which allowed supplying arbitrary data to authenticate as another user. There were a few mitigations to consider.&lt;/p&gt;
&lt;p&gt;I spotted this issue on Github and thought it would be fun to try out the HackerOne reporting process. I had fun establishing clear steps to repro and capturing a reasonably clear video walkthrough.&lt;/p&gt;
&lt;p&gt;Thanks to the Automattic security team for the quick turnaround and for rewarding the report with a bounty, which I intend to spend on some fun music toys!&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The report&lt;/h2&gt;
&lt;video id=&quot;video&quot; controls=&quot;&quot; preload=&quot;metadata&quot;&gt;
  &lt;source src=&quot;https://chris.bur.gs/media/extended-access.mp4&quot; type=&quot;video/mp4&quot; /&gt;
  &lt;track label=&quot;English&quot; kind=&quot;subtitles&quot; srclang=&quot;en&quot; src=&quot;https://chris.bur.gs/media/extended-access.vtt&quot; default=&quot;&quot; /&gt;
&lt;/video&gt;
&lt;h3&gt;Summary:&lt;/h3&gt;
&lt;p&gt;The Newspack Extended Access plugin omits to validate JWT signing on the registration and login JSON endpoint. This permits registration of accounts with arbitrary (user-supplied) details, and auth bypass and account hijack if a target account email is known.&lt;/p&gt;
&lt;h3&gt;Platform(s) Affected:&lt;/h3&gt;
&lt;p&gt;Any website using &lt;a href=&quot;https://github.com/Automattic/newspack-extended-access&quot;&gt;Newspack Extended Access plugin&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Steps To Reproduce:&lt;/h3&gt;
&lt;p&gt;Create an unsigned JWT containing payload value &lt;code&gt;{email: &amp;quot;target@example.org&amp;quot;}&lt;/code&gt;. Use a browser to supply this data to the Extended Access registration endpoint. Browser will be authenticated as the target user.&lt;/p&gt;
&lt;p&gt;Alternative attack path: use lack of validation to create new accounts with &amp;quot;Customer&amp;quot; role via same endpoint using untrusted inputs. Potential for malicious inputs or DoS through unprotected user creation endpoint.&lt;/p&gt;
&lt;h4&gt;Requirements&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Site installed and configured with Newspack Extended Access (+ dependencies incl WooCommerce, WC Memberships, Newspack).&lt;/li&gt;
&lt;li&gt;Target user email is known (for account hijack)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Notes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;For account hijack, target user must be registered via SwG / Extended Access initially&lt;/li&gt;
&lt;li&gt;For account hijack, target user is not Administrator or Editor (but may be any other privileged or unprivileged role)&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Reproduction (account hijack)&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Be logged out of the target website&lt;/li&gt;
&lt;li&gt;Create a JWT token with value &lt;code&gt;email&lt;/code&gt; set to the target account email. This can be done using the website &lt;a href=&quot;https://token.dev/&quot;&gt;https://token.dev&lt;/a&gt; - the &lt;code&gt;email&lt;/code&gt; value is the only significant input for account hijack. Copy the resulting token value.&lt;/li&gt;
&lt;li&gt;Visit the target website and use the following code in browser console to authenticate as the target user:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// Endpoint URL
let url = `${window.location.protocol}//${window.location.hostname}/wp-json/newspack-extended-access/v1/google/register`;
// JWT contents - this JWT contains email &amp;quot;test@example.org&amp;quot;.
let token = &amp;quot;eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZW1haWwiOiJ0ZXN0QGV4YW1wbGUub3JnIiwiaWF0IjoxNzEzNjY2NjQ5LCJleHAiOjE3MTM2NzAyNDl9.I8D18nWsn5H6AylwJdak8727APyiMCWkcnXH95vMF_k&amp;quot;;
// Provide token to authentication endpoint.
fetch(  
    url,  
    {  
       cache: &#39;no-store&#39;,  
       method: &#39;POST&#39;,  
       headers: {  
          &#39;Content-type&#39;: &#39;text/plain&#39;,  
       },  
       body: token  
    }  
).then(response =&amp;gt; {  
    console.log(response.json(), &#39;response&#39;);  
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The browser will now be logged into the target account. Personal data (eg the target user&#39;s additional account details, billing address etc) will be visible to the attacker.&lt;/p&gt;
&lt;h4&gt;Reproduction (account creation)&lt;/h4&gt;
&lt;p&gt;Submitting new accounts via the above method is also possible. Accepting untrusted user submitted input here may allow for additional attack paths when user details are displayed to administrator. Potential for abuse of unprotected submissions via the endpoint would likely permit additional attacks by creating excessive user accounts.&lt;/p&gt;
&lt;h3&gt;Supporting Material/References:&lt;/h3&gt;
&lt;p&gt;JWT is accepted without validation of JWT signature. Ref &lt;a href=&quot;https://github.com/Automattic/newspack-extended-access/blob/3daaae99d98d59dfed78ec68e6ba42c358ca7e82/includes/class-rest-controller.php#L81-L87&quot;&gt;https://github.com/Automattic/newspack-extended-access/blob/3daaae99d98d59dfed78ec68e6ba42c358ca7e82/includes/class-rest-controller.php#L81-L87&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Mitigation: Newspack Extended Access prevents sign-in via this method if the user has role &amp;quot;Editor&amp;quot; or &amp;quot;Administrator&amp;quot;, but does not prevent sign-in if the user has other privileged roles eg &amp;quot;Shop Administrator&amp;quot;. Ref &lt;a href=&quot;https://github.com/Automattic/newspack-extended-access/blob/3daaae99d98d59dfed78ec68e6ba42c358ca7e82/includes/class-rest-controller.php#L96-L99&quot;&gt;https://github.com/Automattic/newspack-extended-access/blob/3daaae99d98d59dfed78ec68e6ba42c358ca7e82/includes/class-rest-controller.php#L96-L99&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Mitigation: Newspack restricts authentication to user accounts which have metadata indicating that Signin with Google was used for initial sign-in. Ref &lt;a href=&quot;https://github.com/Automattic/newspack-plugin/blob/3daaae99d98d59dfed78ec68e6ba42c358ca7e82/includes/reader-activation/class-reader-activation.php#L681-L690&quot;&gt;https://github.com/Automattic/newspack-plugin/blob/3daaae99d98d59dfed78ec68e6ba42c358ca7e82/includes/reader-activation/class-reader-activation.php#L681-L690&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Impact&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Registration of accounts with arbitrary (user-supplied) details&lt;/li&gt;
&lt;li&gt;Authentication bypass if the target account email is known&lt;/li&gt;
&lt;li&gt;Injection of untrusted data into user profiles&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Preparing a video from OBS to MP4</title>
    <link href="https://chris.bur.gs/video-for-bug-report/"/>
    <updated>2024-05-18T05:27:00Z</updated>
    <id>https://chris.bur.gs/video-for-bug-report/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Filed a report, and the receiving party suggested to check for any sensitive details in the report before disclosing.&lt;/p&gt;
&lt;p&gt;Included a short screencast and sure enough on re-watching I saw my browser bookmark toolbar was visible. No disclosure beyond three letter project codes, but decided to obscure that.&lt;/p&gt;
&lt;p&gt;The location on screen to be redacted was static through the video, so I could use a static overlay to address it.&lt;/p&gt;
&lt;p&gt;I extracted a single frame from the video to capture the image I needed to overlay: &lt;code&gt;ffmpeg -i extended-access.mp4 -ss 00:00:05 -vframes 1 browser-chrome.png&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Then I found that if you need specific timing, VLC&#39;s &amp;quot;Video &amp;gt; Take Snapshot&amp;quot; is good for this too!&lt;/p&gt;
&lt;p&gt;I copied that image into an image editor, selected the toolbar and blurred it, inverted selection to delete the rest of the image, and saved it as a (mostly transparent) PNG.&lt;/p&gt;
&lt;p&gt;In OpenShot I added the PNG as a project resource and inserted it as a new, frontmost layer in the video timeline.&lt;/p&gt;
&lt;p&gt;I then scrubbed through the video and deleted (a little messily) the sections where the blurred overlay did not need to sit.&lt;/p&gt;
&lt;p&gt;Exported as &lt;code&gt;extended-access-source.mp4&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;By now I&#39;d realised that the original recording was SUPER QUIET, my apologies to the person who had to turn their speakers up to hear my voiceover. Let&#39;s fix that too! FFMpeg will do it using an audio filter (&amp;quot;af&amp;quot;).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ffmpeg -y -i extended-access-source.mp4 -af &amp;quot;volume=50&amp;quot; extended-access.mp4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From that I realised that I should learn how to make a set of subtitles for the video also.&lt;/p&gt;
&lt;p&gt;How do we feel about Whisper? It&#39;s OpenAI so it feels a bit gross, but I can use it to produce subs pretty smartly from the MP4, and they are not bad.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;whisper extended-access.mp4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I could use &lt;code&gt;--output-format srt&lt;/code&gt; but I can also omit that and Whisper will turn out several different formats, which is fine. Then I watched through the video with the .srt open, made a few small changes, and away we went!&lt;/p&gt;
&lt;p&gt;Here&#39;s the resulting video: &lt;a href=&quot;https://chris.bur.gs/extended-access-jwt-bypass/&quot;&gt;Extending your access - JWT validation failure and bypass&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Drupal South + Potato Week</title>
    <link href="https://chris.bur.gs/drupal-south-2024/"/>
    <updated>2024-04-01T10:45:00Z</updated>
    <id>https://chris.bur.gs/drupal-south-2024/</id>
    <content xml:lang="en" type="html">&lt;p&gt;A couple weeks ago I took leave to travel to Sydney for Drupal South, present a session on Recipes, and enjoy the community and culture that Drupal South makes possible.&lt;/p&gt;
&lt;p&gt;I do feel so lucky to be part of this community. Especially these days, when travel is less frequent and things feel more isolated down here in Dunedin, I value highly the chance to be there, have personal conversations with people I interact with online, and soak in the ideas and vibes of the people who I share the space with. I also miss the folks who aren&#39;t there, or have even moved on. That&#39;s all a good feeling.&lt;/p&gt;
&lt;p&gt;Often I&#39;ve come back from a conference excited, tired, and dropped myself straight back into everyday workload. That is a terrible idea! What happens is all the amazing ideas and realisations get washed over by day to day work challenges, and between post-conf exhaustion and whatever&#39;s in your inbox on return, I find a lot can be lost. This year, the week following the conference was a short one in my region - Otago Anniversary Day and Good Friday had collaborated to set up a three day week. I wrote &amp;quot;Potato Week&amp;quot;&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://chris.bur.gs/drupal-south-2024/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; on the calendar, booked that as leave, and took the time to let myself relax and reflect afterwards. (And to improve a few things around the house. It&#39;s been a great break!)&lt;/p&gt;
&lt;p&gt;So - hmm, some thoughts on the conference.&lt;/p&gt;
&lt;h2&gt;A talk on Drupal Recipes&lt;/h2&gt;
&lt;p&gt;Recipes is a new tool for delivering configuration (more to come) to Drupal sites. It&#39;s exciting as it targets both a recurring need for site operation, and addresses a challenging specific detail of Drupal&#39;s current stack. I wanted opportunity to understand this better and even contribute to its progress, but didn&#39;t have a work opportunity to explore it in depth. I assigned myself some &lt;em&gt;Conference Driven Development&lt;/em&gt;, where you propose a talk on a topic you don&#39;t &lt;em&gt;yet&lt;/em&gt; know much about, then are obliged to learn enough to deliver a talk on the day. That was my first in-person conference presentation in a few years, and it was great - the room was nearly full and the feedback was engaged and positive.&lt;/p&gt;
&lt;p&gt;Recipes aims to address a challenging feature of profile development, and I was happy to see Heike and Dave from Sparks were interested to see what that might offer the &lt;a href=&quot;https://sector.nz/&quot;&gt;Sector Drupal distribution&lt;/a&gt;. If you&#39;re keen to explore Recipes more, let&#39;s chat!&lt;/p&gt;
&lt;h2&gt;Leaders dinner&lt;/h2&gt;
&lt;p&gt;There was a Drupal Leaders Dinner the Tueday before the conference, and I&#39;d had opportunity to attend this the previous year in Wellington as well, due to my role on the Drupal South Steering Committee. The attendees were all leaders or decision makers in their respective organisations, including representation from lots of local agencies as well as Tim Doyle of the Drupal Association and Dries Buytaert the  It&#39;s a great idea, based I think on the Drupal CEO dinners of Europe, and there&#39;s a really good energy of enthusiastic sharing and collaborative exploration between businesses. The discussions were interesting, the menu was amazing, and Mike kept things rolling with the Drupal Agency Survey results presented to the group. Valuable insights!&lt;/p&gt;
&lt;p&gt;Something I noticed here especially, and which was tangible at the conference as well, is the strength of collaboration between industry businesses in Australia - where Drupal is doing great things! Australia&#39;s contribution game is strong, and the industry there seems really healthy. I love that, and I&#39;m interested to know what we can do to develop this further in Aotearoa as well. (If you have thoughts, &lt;a href=&quot;https://chris.bur.gs/contact&quot;&gt;get in touch&lt;/a&gt;! Are there ways that the community could facilitate this better?)&lt;/p&gt;
&lt;h2&gt;Contribution cultivation&lt;/h2&gt;
&lt;p&gt;DrupalSouth has a sprint day, and while I was only able to be there the first half day, it was a great opportunity to share knowledge, assist debugging, and spin the flywheel a little on the tasks in my contribution queue. This spurred a few folks to respond in kind on the issues touched, which is a good return; recognising the multiplier effect available here, some reflections for me to study on cultivating contribution from community.&lt;/p&gt;
&lt;h2&gt;Resonances&lt;/h2&gt;
&lt;p&gt;I&#39;m reflecting on the folks I know in the community who I resonate with, and on what.&lt;/p&gt;
&lt;h2&gt;What am I focused on? What tech did I come away with in my mind?&lt;/h2&gt;
&lt;p&gt;Areas I&#39;m enjoying myself in development at the moment are -&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Data transformations and Migration - I enjoy the &amp;quot;puzzle&amp;quot; aspects of sourcing and data transformations, and I have a fun personal project that I&#39;m regularly knocking out new migrations to feed data into. I&#39;ve also (post-conf) been checking out some additional tools such as Airbyte, Meltano, and Puppeteer with a mind to treating the web as a programmable entity - as it ought to be.&lt;/li&gt;
&lt;li&gt;Testing and automation - I love making things reproducible, it makes my inner six year old very happy - and have been exploring a few tools outside my usual toolkit to survey the landscape. I enjoyed the session on Cypress and Drupal at DrupalSouth, and I&#39;ve been testing out Puppeteer and n8n since then, comparing them with my more familiar toolkit. I really liked Huginn in the past as well, and I might spin up an instance of that soon.&lt;/li&gt;
&lt;li&gt;For similar reasons, I&#39;m keen to get a better handle on the various testing functionality available in core and contrib. I can usually muck my way through but I want to feel more confident and at home with it. Especially when Gitlab CI is something I&#39;m really comfortable and confident with!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;And more ...&lt;/h2&gt;
&lt;p&gt;There&#39;s much more to say, but I feel like I&#39;ve infodumped a lot here already and I can&#39;t cover all of it! Personal reflections such as the resonances we discover in realising that someone else just is really into that thing you&#39;re into, huge and important topics such as how institutions can find deep trust in how their data is shaped and stored by the systems they select, and complicated things such as what it means for a software community to be community led, or to find common goals. Maybe I&#39;ll come back to those.&lt;/p&gt;
&lt;p&gt;I&#39;m grateful to have had the chance to be there - thanks team! Looking forward to next time - and to all the connections in between.&lt;/p&gt;
&lt;p&gt;If any of this hits a note for you, reach out - you know &lt;a href=&quot;https://chris.bur.gs/contact&quot;&gt;where to find me&lt;/a&gt; :)&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot; /&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I really don&#39;t know where &amp;quot;Potato Week&amp;quot; came from but I may hang on to that too 🥔 &lt;a href=&quot;https://chris.bur.gs/drupal-south-2024/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  <entry>
    <title>Let&#39;s get cooking: Drupal Recipes</title>
    <link href="https://chris.bur.gs/drupal-recipes/"/>
    <updated>2024-03-22T03:45:00Z</updated>
    <id>https://chris.bur.gs/drupal-recipes/</id>
    <content xml:lang="en" type="html">&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/DS2024-Recipes-0.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This blog post has images of the slides and my notes. The format isn&#39;t perfect but I hope it&#39;s helpful to follow along or catch up later!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/DS2024-Recipes-1.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Tena koutou, thanks Kelvin for that presentation, thanks everyone for bringing yourselves along. I hope you’re all enjoying a great first day of DrupalSouth 2024!&lt;/p&gt;
&lt;p&gt;Kia ora, I’ve come from Ōtepoti Dunedin, in the south of Te Waipounamu, Aotearoa New Zealand where I live. That’s my family in the top right - Saira, Hunter and Rowan.&lt;br /&gt;
I’m really grateful to be here today and part of this community, Drupal’s made a big difference to us.&lt;/p&gt;
&lt;p&gt;I love data, and I think a lot about how we care for our own data. I also love throwing balls, and drawing, and learning music at the moment.&lt;/p&gt;
&lt;p&gt;I’m a Senior Developer at Catalyst, where I enjoy some interesting and challenging projects in publishing, education and government as part of my work.&lt;/p&gt;
&lt;p&gt;My name is Chris Burgess and you’ll find me online as @xurizaemon.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/DS2024-Recipes-2.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;First up - some caveats.&lt;/p&gt;
&lt;p&gt;I think the Recipes project work is really exciting! It’s an opportunity to expand what Drupal is to a broader pool of people.&lt;/p&gt;
&lt;p&gt;Recipes is unfinished, not incomplete, things may change. And it’s open source, so things may change.&lt;/p&gt;
&lt;p&gt;Recipes also does not try to deliver everything you can do with a Profile. That’s intentional.&lt;/p&gt;
&lt;p&gt;I am just learning about Recipes myself, and I don’t want you to get the impression I think I’m an expert. Let me know.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/DS2024-Recipes-3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Recipes is a way to apply composable configurations (and eventually content) to Drupal sites.&lt;/p&gt;
&lt;p&gt;Drupal has twenty years of - a long history of success and lessons in empowering creators to re-use functionality with tools such as distributions, profiles, modules, and more.&lt;/p&gt;
&lt;p&gt;Recipes are a new take on that, based on what we’ve learned so far.&lt;/p&gt;
&lt;p&gt;Recipes promise to introduce less complexity to a site, and to be a less significant investment.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/DS2024-Recipes-4.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;What was before Recipes? Lots, especially Profiles.&lt;/p&gt;
&lt;p&gt;Profiles have been a cornerstone of Distributions, a great tool, a really valuable contribution to Drupal. Profiles are not the only method to deliver configuration to a suite of sites, but they are one of the leading solutions.&lt;/p&gt;
&lt;p&gt;One challenge of Profiles in Drupal was they turned out to be a significant commitment for site operators - and profile maintainers - in lots of ways.&lt;/p&gt;
&lt;p&gt;Profiles can be effective but their design locks derived sites and the profile together. They can require additional maintenance and affect decisions on both the site operator and maintainer sides, and generally complicate site operation.&lt;/p&gt;
&lt;p&gt;So - Recipes are a response to that experience of wanting to deliver configuration to sites, usually a set of sites, without the commitment of tying them together.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/DS2024-Recipes-5.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;How do Recipes offer this? They do less, and they do things differently.&lt;/p&gt;
&lt;p&gt;Recipes are declarative: they say “do these things”, without reference to the current state of the system. By design, a Recipe is not informed of the state of the site.&lt;/p&gt;
&lt;p&gt;Recipes can do several things.&lt;/p&gt;
&lt;p&gt;Recipes may install modules and themes.&lt;/p&gt;
&lt;p&gt;This implies they can add requirements for Drupal modules or themes they will install.&lt;/p&gt;
&lt;p&gt;Recipes may apply a module’s default configuration, selectively, which means you can opt into some or all of the module’s out of the box config.&lt;/p&gt;
&lt;p&gt;Recipes also can provide their own configuration.&lt;/p&gt;
&lt;p&gt;And, Recipes can perform config actions. We’ll touch on that shortly.&lt;/p&gt;
&lt;p&gt;All this configuration functionality brings new possibilities.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/DS2024-Recipes-6.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Most of those are features you know from earlier implementations.&lt;/p&gt;
&lt;p&gt;What’s most different about Recipes is what it chooses not to do.&lt;/p&gt;
&lt;p&gt;There’s no provision for code in a Recipe. It’s declarative, it won’t respond differently if module X is or isn’t installed.&lt;/p&gt;
&lt;p&gt;It’s not dynamic at install time, and it can’t deliver behaviour changes at runtime except through the modules and configuration it applies. No hooks.&lt;/p&gt;
&lt;p&gt;You can limit the configuration applied - you can opt into some, all or none of an installed module’s config. The Recipe - and the person using it - is responsible for a valid result!&lt;/p&gt;
&lt;p&gt;And the killer feature is that after the Recipe is applied, it’s gone. The changes are in the site’s configuration and the Recipe can be forgotten.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/DS2024-Recipes-7.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;At its core, a Recipe is a yaml file to define the recipe. There may also be a composer json file, and a directory of provided configuration.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/DS2024-Recipes-8.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Here’s a recipe configuration. This is our recipe dot yaml.&lt;/p&gt;
&lt;p&gt;At the top we can see the recipe metadata - the name is required, the type (that can be “Site” for a distribution-tier Recipe), a description.&lt;/p&gt;
&lt;p&gt;This recipe is a small example, showing an example of a small component which you might combine with others into a more fully featured recipe.&lt;/p&gt;
&lt;p&gt;“Install” defines which modules to install; in this Recipe we’re installing Autologout module.&lt;/p&gt;
&lt;p&gt;“Config” defines which configuration to apply.&lt;/p&gt;
&lt;p&gt;The “import autologout star” means we’re opting to install all available configuration that Autologout module provides. That would also include config in a config/optional directory, if the module has it.&lt;br /&gt;
Instead of a single “star” value, we could explicitly provide an array of specific config items to import from that module. So we might say autologout dot settings there.&lt;br /&gt;
If we omitted that autologout module from the config entries, we should expect only the simple configuration items from autologout to be added, by default omitting configuration entities and optional configuration.&lt;/p&gt;
&lt;p&gt;Then we’re using a config action - via the new Config Actions API - to apply a simple config update that changes the autologout timeout from the module’s default half hour to a full hour.&lt;/p&gt;
&lt;p&gt;This recipe uses the module defaults as a start point, and changes the single value we want changed. Tidier.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/DS2024-Recipes-9.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;A Recipe may also contain a directory of configuration to be imported.&lt;/p&gt;
&lt;p&gt;If you want to replace or preconfigure an entire configuration, this is the way to do it.&lt;/p&gt;
&lt;p&gt;If you aren’t changing much, I suggest using simple config update action to set only the values required, and accept the defaults elsewhere.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/DS2024-Recipes-10.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Let’s look at the recipe’s composer.json next.&lt;/p&gt;
&lt;p&gt;After unpacking this - using a composer unpack command - into the project, the project’s composer.json takes responsibility for these requirements and the recipe can be removed.&lt;/p&gt;
&lt;p&gt;We see a type of drupal-recipe here, similar to drupal-module and friends, this tells Composer where the recipe should be placed in the project.&lt;/p&gt;
&lt;p&gt;We’ve added a requirement on Drupal Autologout module here as that’s the module we enable. I’ve specified a version constraint because I want to know the config I apply will be valid for the installed version.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/DS2024-Recipes-11.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;That’s a single recipe, kept intentionally small. But I think that’s the right size for that component.&lt;/p&gt;
&lt;p&gt;Then you can use these in higher level recipes by stacking them. This tree shows a recipe with components depending on components.&lt;/p&gt;
&lt;p&gt;In this way you can build out your preferred start point, and own the lower level details too, and mix and match as appropriate.&lt;/p&gt;
&lt;p&gt;One of several ways we’ll use this is to make the initial configuration of a Drupal site much quicker and easier. This puts the options - currently a choice between Minimal, Standard or a distribution - into the hands of the site owner.&lt;/p&gt;
&lt;p&gt;It’s nice to save a few hours of clicking, but having a consistent start point will be a bigger benefit for agencies using this than just saving that setup time.&lt;/p&gt;
&lt;p&gt;And Recipes can be applied later in the piece too. The pitch there is, if your blog takes off and you need to sell merch, you’ll apply a merch shop recipe.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/DS2024-Recipes-12.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Recipes is still in development. You can try it out today!&lt;/p&gt;
&lt;p&gt;More complete instructions are in that link on the slide.&lt;/p&gt;
&lt;p&gt;You’ll want a site to start with. It’s simplest to start with a minimal install, or you can test on an existing site.&lt;/p&gt;
&lt;p&gt;The version-specific core patches from the project will make your site Recipes capable.&lt;/p&gt;
&lt;p&gt;Composer configuration is required to install Recipes with patches.&lt;/p&gt;
&lt;p&gt;When your site is patched and you have a recipe to test, the drupal CLI is used to apply recipes.&lt;/p&gt;
&lt;p&gt;After applying a recipe, the changes can be captured from Composer and config export.&lt;/p&gt;
&lt;p&gt;With some confidence in juggling config changes, this can be used on a local copy of a real site.&lt;/p&gt;
&lt;p&gt;Remember that Recipes are applied, so the core patch and Composer changes don’t need to be retained once you’ve finished applying them.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/DS2024-Recipes-13.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Recipes does less, and allows Drupal to do more, together with other new functionality.&lt;/p&gt;
&lt;p&gt;The Project Browser will be a big part of this. It’ll allow ambitious site builders to discover and build with community provided recipes.&lt;/p&gt;
&lt;p&gt;Automated Updates I think is also a big part of what can allow Drupal to be managed by the broad pool of ambitious builders I’d like to see.&lt;/p&gt;
&lt;p&gt;And I think that persona of the Ambitious Site Builder is something Recipes fits so well with.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/DS2024-Recipes-14.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/DS2024-Recipes-15.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Unreliable narrator</title>
    <link href="https://chris.bur.gs/unreliable-narrator/"/>
    <updated>2024-03-09T19:55:00Z</updated>
    <id>https://chris.bur.gs/unreliable-narrator/</id>
    <content xml:lang="en" type="html">&lt;ul&gt;
&lt;li&gt;&lt;button class=&quot;toggle-animation&quot;&gt;Animation&lt;/button&gt;&lt;/li&gt;
&lt;li&gt;&lt;button class=&quot;toggle-visibility&quot;&gt;Visibility&lt;/button&gt;&lt;/li&gt;
&lt;li&gt;&lt;button class=&quot;change-effect&quot;&gt;Effect&lt;/button&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have been putting together a set of ETL migrations&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://chris.bur.gs/unreliable-narrator/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; which capture data points from online systems I interact with day to day. I think of this as a narrator, but rather than telling a coherent story, it observes events that occur without understanding them in context.&lt;/p&gt;
&lt;p&gt;Many of the applications and systems we use - social media websites, health and fitness trackers, finance systems, business tools, conveniences and entertainments, all interoperate with us online via data endpoints.&lt;/p&gt;
&lt;p&gt;By capturing a stream of data from various sources, we can observe the digital/data footprint that these sources relate to, and visualise it.&lt;/p&gt;
&lt;p&gt;Why (re)capture these data points? I became interested thinking of the record of each day which could be reconstructed from services I use as part of regular daily process or daily habits and associated data streams. I do enjoy the challenge of &amp;quot;solving&amp;quot; ETL transforms (puzzles, think Sudoku), so it was easy to keep building from there. The results are entertaining!&lt;/p&gt;
&lt;p&gt;I&#39;m really interested in the portability and visibility of data as it relates to our ownership and responsibilities to the same. Consciousness of &amp;quot;our data&amp;quot; may feel like new territory to be plundered, but our stories are what make us who we are. How do these streams of personal digital record interplay with each of our stories?&lt;/p&gt;
&lt;p&gt;Whatever we might collect with a handful of sources and transformations will be only a tiny fraction of available data in a sea of analytics and tracking information.&lt;/p&gt;
&lt;p&gt;Beneath the surface, in deep, dark data lakes, swamps and oceans, digital systems dream targetable entities of each of us. Computer toucher guardians spill data from place to place, confident in their ability to extract value without harm, echoing the errors of their ancestors.&lt;/p&gt;
&lt;p&gt;Physical and digital devices study us to determine how best to feed their appetites. We exist in their realm, perhaps unaware of, perhaps uncomfortably disregarding their watchful gaze.&lt;/p&gt;
&lt;p&gt;The animation on this page decontextualises a subset of my own recently captured data. I wouldn&#39;t dare expose the feed directly ... here I&#39;m teasing, baiting those predatory devices which crawl online spaces with a crude depiction of event data I hold about myself.&lt;/p&gt;
&lt;p&gt;A sample of my most recent events are depicted, represented by an emoji per service type. What do these images represent, what might they tell you about my day? Why would you care? Machines are hungry for this knowledge - why?&lt;/p&gt;
&lt;p&gt;🌅&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot; /&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Events are harvested in a Drupal 10 site via Migrate, stored as entities. JSON API is used to expose these to the build process for the Eleventy site. The Eleventy site retrieves the entities and embeds an associated service emoji and UUID here. There&#39;s no need for the UUID. Animations are applied in javascript + CSS but use no source data beyond the symbol animated. &lt;a href=&quot;https://chris.bur.gs/unreliable-narrator/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  <entry>
    <title>Automating commit messages better</title>
    <link href="https://chris.bur.gs/automated-commit-message/"/>
    <updated>2024-03-08T18:56:00Z</updated>
    <id>https://chris.bur.gs/automated-commit-message/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I use Git&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://chris.bur.gs/automated-commit-message/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; on the daily and in most contexts&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://chris.bur.gs/automated-commit-message/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt; I care a lot about the commit messages being meaningful.&lt;/p&gt;
&lt;p&gt;I also have a small handful of repositories where commits are automated (automated data feeds; daily timesheets; my lab journal, etc).&lt;/p&gt;
&lt;p&gt;In the case of these repos, I want to track the incoming changes, but do not want the effort of composing a message. There&#39;s no benefit to me in manually composing a history entry, either because I&#39;m not present, or because a descriptive change wouldn&#39;t add to the record.&lt;/p&gt;
&lt;p&gt;(Sometimes that&#39;s not true; when I backfilled hundreds of dates into old journal entries, I did write a commit message for the change. But not for when it&#39;s happening from &lt;code&gt;cron&lt;/code&gt;.)&lt;/p&gt;
&lt;p&gt;For a long time I used a &lt;code&gt;git commit -m &amp;quot;journal update on ${HOSTNAME} @ ${DATE}&amp;quot;&lt;/code&gt; to automate; it gave me visibility on the origin and recency of the changes, and I could check the history for more information if necessary.&lt;/p&gt;
&lt;p&gt;But I want more. I want to be able to look at a commit message and see something more like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Changed: 2024/03/09.md, Presentations/DS2024/Recipes.md

- 2024/03/09.md:
  - Modified section &amp;quot;Tasks&amp;quot;
  - Added section &amp;quot;Git commit message&amp;quot;
- Presentations/DS2024/Recipes.md
  - Added section &amp;quot;Config validation&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That seems to match what Martin Fowler has referred to as &lt;a href=&quot;https://martinfowler.com/bliki/SemanticDiff.html&quot;&gt;Semantic Diff&lt;/a&gt; (like Martin, I ask that if you know a better name, please &lt;a href=&quot;https://chris.bur.gs/contact&quot;&gt;tell me&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;I don&#39;t have that part yet. I expect it wouldn&#39;t be terrible to implement; in Markdown you&#39;d be looking for the parent &amp;quot;element&amp;quot; (ie, heading), and maybe reporting back on the parent of &lt;em&gt;that&lt;/em&gt; if you wanted it.&lt;/p&gt;
&lt;p&gt;I see also that are some LLM-based tools which will generate a commit message for you based on staged changes, which is perhaps nice for some but I&#39;ll give a hard no on feeding my daily work notes to an LLM for the provider to train against.&lt;/p&gt;
&lt;p&gt;But to improve on the lack of information in &amp;quot;journal update on ${HOSTNAME} @ ${DATE}&amp;quot; I decided to at least capture a high-level view. Here&#39;s what I came up with.&lt;/p&gt;
&lt;p&gt;A Git &lt;code&gt;prepare-message-hook&lt;/code&gt; with the following content:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/usr/bin/env bash&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;COMMIT_MSG_FILE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;COMMIT_SOURCE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$2&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;SHA1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$3&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;ADDED&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; status &lt;span class=&quot;token parameter variable&quot;&gt;--porcelain&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;^A&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wc&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-l&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;DELETED&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; status &lt;span class=&quot;token parameter variable&quot;&gt;--porcelain&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;^D&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wc&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-l&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;MODIFIED&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; status &lt;span class=&quot;token parameter variable&quot;&gt;--porcelain&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;^M&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wc&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-l&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;RENAMED&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; status &lt;span class=&quot;token parameter variable&quot;&gt;--porcelain&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;^R&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wc&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-l&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;OTHER&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; status &lt;span class=&quot;token parameter variable&quot;&gt;--porcelain&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-Ev&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;^[ADMR]&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;wc&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-l&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;&lt;span class=&quot;token environment constant&quot;&gt;HOSTNAME&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hostname&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;SUBJECT&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Files changed on &lt;span class=&quot;token environment constant&quot;&gt;$HOSTNAME&lt;/span&gt;: &quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;ORIG_SUBJECT&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$SUBJECT&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-name function&quot;&gt;push_subject&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$SUBJECT&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$ORIG_SUBJECT&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$SUBJECT&lt;/span&gt;, &lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$SUBJECT&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$ADDED&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token assign-left variable&quot;&gt;SUBJECT&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt; push_subject &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$ADDED&lt;/span&gt; added&quot;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$DELETED&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token assign-left variable&quot;&gt;SUBJECT&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt; push_subject &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$DELETED&lt;/span&gt; removed&quot;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$MODIFIED&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token assign-left variable&quot;&gt;SUBJECT&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt; push_subject &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$MODIFIED&lt;/span&gt; modified&quot;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$RENAMED&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token assign-left variable&quot;&gt;SUBJECT&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt; push_subject &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$RENAMED&lt;/span&gt; renamed&quot;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$OTHER&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token assign-left variable&quot;&gt;SUBJECT&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt; push_subject &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$OTHER&lt;/span&gt; other&quot;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$SUBJECT&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$COMMIT_MSG_FILE&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$COMMIT_MSG_FILE&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;diff&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--cached&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--stat&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$COMMIT_MSG_FILE&lt;/span&gt;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Will generate commit messages like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;commit 7f9cb0ff06afaa5db8733869ca5552c86aa7fc99 (HEAD -&amp;gt; main, origin/main, origin/HEAD)
Author: Chris Burgess &amp;lt;chris@giantrobot.co.nz&amp;gt;
Date:   Sat Mar 9 07:16:04 2024 +1300

    Files changed on thip:  44 added, 1 removed, 23 modified, 1 renamed
    
     2021/03/23/Some Notes.md          | 374 +++++++++++++++++++++
     2023/09/04.md                     |   2 +-
     2024/02/04.md                     | 260 +++++++++++++-
     2024/02/05.md                     | 121 +++++++
     ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;commit 36a61c68c8b0031ad556c6be15ffe8399c2903a9 (HEAD -&amp;gt; main, origin/main, origin/HEAD)
Author: Chris Burgess &amp;lt;chris@giantrobot.co.nz&amp;gt;
Date:   Sat Mar 9 07:26:37 2024 +1300

    Files changed on thip:  1 added
    
     2024/03/09.md | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
     1 file changed, 82 insertions(+)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&#39;s not perfect. It doesn&#39;t handle &lt;code&gt;git commit -m &amp;lt;msg&amp;gt;&lt;/code&gt;, for one thing, and I hope to explore further a diff that is capable of precis-ing a set of changes to Markdown files. But it&#39;ll improve the history I see when working with those repos where I&#39;m not writing commit messages for each change.&lt;/p&gt;
&lt;p&gt;I&#39;ll report back on whatever annoys me about this first quick implementation 😀&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot; /&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;If you use another SCM, please substitute yours for &amp;quot;Git&amp;quot; in this. I&#39;m not saying &amp;quot;SCM&amp;quot; here, picking clarity over correctness. &lt;a href=&quot;https://chris.bur.gs/automated-commit-message/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;On a WIP branch, I will push any and all manner of rubbish commit messages, and rebase later. &lt;a href=&quot;https://chris.bur.gs/automated-commit-message/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  <entry>
    <title>GlitchTip filtering by log levels</title>
    <link href="https://chris.bur.gs/glitchtip-levels/"/>
    <updated>2024-02-25T00:00:00Z</updated>
    <id>https://chris.bur.gs/glitchtip-levels/</id>
    <content xml:lang="en" type="html">&lt;p&gt;This morning I was looking at some log entries in GlitchTip and went to filter by log severity to see what was being recorded.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://glitchtip.com/&quot;&gt;GlitchTip, by the way, is an &lt;strong&gt;open source error tracking tool&lt;/strong&gt;&lt;/a&gt;. It&#39;s pretty fancy, I have a lightweight instance I run to capture debug output from a handful of projects I operate personally. Errors or log messages from applications can be relayed to GlitchTip for review, notification, and other similar features.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://chris.bur.gs/media/glitchtip-filters.png&quot;&gt;&lt;img src=&quot;https://chris.bur.gs/media/glitchtip-filters.png&quot; alt=&quot;Screenshot: GlitchTip UI for filtering issues&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I was surprised to see that there wasn&#39;t any UI for how to filter down to eg &amp;quot;error&amp;quot; or &amp;quot;debug&amp;quot; level entries. I thought this was existing functionality but was (perhaps of course) thinking of other tools. I also noticed that the level of each message (or issue) was not represented textually, instead showing only as a colour bar to the side of the entry in the UI.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/glitchtip-levels.png&quot; alt=&quot;Screenshot: GlitchTip UI for different issue / event levels&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I opened the browser&#39;s inspector and copied the API requests out as a &lt;code&gt;curl&lt;/code&gt; command, then tried some variations for filtering syntax - &lt;code&gt;?level=info&lt;/code&gt;, &lt;code&gt;?query=level:info&lt;/code&gt; - to see if the API supported it. It didn&#39;t appear to. There was no indication of support in the front end either, while I saw other UX hints from autocomplete on &lt;code&gt;environment:&lt;/code&gt; etc.&lt;/p&gt;
&lt;p&gt;This now had my interest, and I checked out the GlitchTip project Gitlab to see if I could find an issue where this was being requested or worked on. Searching for &amp;quot;severity&amp;quot;, &amp;quot;level&amp;quot;, &amp;quot;type&amp;quot; led me to &lt;a href=&quot;https://gitlab.com/glitchtip/glitchtip-frontend/-/issues/161&quot;&gt;glitchtip/glitchtip-frontend#161: Add UI for error levels to issue list page&lt;/a&gt; which at least confirmed for me that the colours I&#39;d noticed earlier did relate to the levels of the entries.&lt;/p&gt;
&lt;p&gt;I checked into GlitchTip&#39;s chat to &lt;a href=&quot;https://matrix.to/#/!qJfEetDzNUrAFBcqyw:gitter.im/$cqAK3P8X0e52hIld7BN1pEuyY6MhtfLsofqAbOyOo2E?via=gitter.im&quot;&gt;ask if this was an existing feature request&lt;/a&gt;, and was fortunate to have a few pointers from one of GlitchTip&#39;s core contributors, David Burke, including an existing issue &lt;a href=&quot;https://gitlab.com/glitchtip/glitchtip-frontend/-/issues/185&quot;&gt;glitchtip/glitchtip-frontend#185: Feature Request: Filter &amp;quot;message events&amp;quot; out of the issues list&lt;/a&gt;, which the keywords I&#39;d tried searching for hadn&#39;t surfaced.&lt;/p&gt;
&lt;p&gt;Since I didn&#39;t know Django at all, I figured the best first step would be to write test coverage for the desired functionality. I made a first attempt at a test, and ran the tests with &lt;code&gt;docker compose exec web ./manage.py test apps.issue_events&lt;/code&gt;. I expected my changes to have a heavy foreign accent, not being much familiar with Django or even Python. To my incredulity not only did the proposed code pass GlitchTip&#39;s lint checks, but the tests passed as well. I was pretty confident this meant my tests were flawed.&lt;/p&gt;
&lt;p&gt;David kindly showed me I had implemented a test which tested the wrong thing - whether the issues were tagged with named levels, rather than whether the issues had levels assigned.&lt;/p&gt;
&lt;p&gt;I already had the backend component running locally, and wanted to be able to poke at the API directly with these changes in place to test. Getting an API key however looked like it would require attaching the frontend service component. I had the feeling it might be a diversion but was told this was the easiest way to get an authentication token. Then I found that the frontend setup steps didn&#39;t work for me, exiting with a &amp;quot;bus error&amp;quot; each time I ran &lt;code&gt;npm install&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This had me stuck for a little bit, so after talking with David some more about the error I saw, I went and tidied up the yard for a bit, trimmed some edges and mowed some grass. When I came back I had realised that it was unnecessary to set up a build environment at all ... I could download a &lt;a href=&quot;https://gitlab.com/glitchtip/glitchtip-frontend/-/jobs/artifacts/master/browse?job=build-assets&quot;&gt;fresh set of built files from the frontend project&#39;s &lt;code&gt;build-assets&lt;/code&gt; CI job&lt;/a&gt;! So I grabbed those, copied them into the local backend development instance I had available, and was finally able to see the GlitchTip UI.&lt;/p&gt;
&lt;p&gt;Now I needed to get a login to the instance. It had come up with an unknown admin email address. A bit of research led me to Django&#39;s &lt;code&gt;createsuperuser&lt;/code&gt; command, which I used via &lt;code&gt;./manage.py&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;$ docker compose exec web ./manage.py createsuperuser&lt;br /&gt;Email: test@example.com&lt;br /&gt;Password: &lt;br /&gt;Password (again): &lt;br /&gt;Bypass password validation and create user anyway? [y/N]: y&lt;br /&gt;Superuser created successfully.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once this was done I could log into my local GlitchTip instance and configure it with the required data to get access to a token for testing the behaviour of the client.&lt;/p&gt;
&lt;p&gt;I now rewrote the test so it now (correctly, I believe) failed. I then added a few lines to the Issue model to implement the filter, and once more, to my pleasant surprise, the test now passed. I tried a few additional test changes, confirming various changes or inputs could fail the test, to confirm things were working as intended, which it seems like it does. Wow. That was too easy?&lt;/p&gt;
&lt;p&gt;The GlitchTip codebase has a rewrite under way to make event and issues APIs more efficient, and David had pointed me at that branch (with some special instructions on how to test it). The tests were passing both locally and in CI, locally with the rewrite changes active and in CI without. I was ready to push the changes for consideration and did so. The resulting MR is at &lt;a href=&quot;https://gitlab.com/glitchtip/glitchtip-backend/-/merge_requests/767&quot;&gt;glitchtip/glitchtip-backend!767&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I really enjoyed this first brush with the GlitchTip codebase. I was impressed by the project&#39;s clear guidelines in &lt;a href=&quot;https://gitlab.com/glitchtip/glitchtip-backend/-/blob/master/CONTRIBUTING.md&quot;&gt;CONTRIBUTING.md&lt;/a&gt;, with a good section on code style and philosophy to align contributions. I especially liked -&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Economical over completeness. Make running GlitchTip as easy and simple as possible, especially for small and medium sized projects. Be wary of introducing additional external dependencies. The entire project must be maintained on a budget of 4 person-hours per week. When introducing a large new feature, offer to help with maintenance in addition.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I also thought that guidance like what to avoid was a really helpful step, eg:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Avoid: Inefficient database calls - GlitchTip must work for both small self hosters and 100 million event projects. Always assume scale. If you need to edit every user, assume there are 100 million users and the queries much be chunked in batches. Neither one query per user nor one giant query that takes too long to execute.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(See the full &lt;a href=&quot;https://gitlab.com/glitchtip/glitchtip-backend/-/blob/master/CONTRIBUTING.md&quot;&gt;CONTRIBUTING.md&lt;/a&gt; for other guidance - it&#39;s a sign of a healthy setup to me that this has been reflected on, and is at the fore.)&lt;/p&gt;
&lt;p&gt;I enjoyed working with the project today and found the project layout and Django&#39;s experience really well formed. I&#39;m be interested to learn more about this framework I&#39;ve long heard of and not engaged until now 🐍 and excited to look forward to a second round of feedback on the MR.&lt;/p&gt;
&lt;p&gt;A positive reflection was that when the missing functionality caught my eye, my initial response was one of &lt;em&gt;interest&lt;/em&gt; - both that the feature wasn&#39;t there, and how it might be implemented. That attitude supported me to take the step to trying a commit in a system outside my usual toolkits, and I appreciate the experience and outcome. If that first mental/emotional had been disappointment, annoyance, frustration or even resignation ... I think I wouldn&#39;t have tried half of the steps I did, and that would have limited the outcomes. It&#39;s not &lt;em&gt;all&lt;/em&gt; attitude, but it sure helps.&lt;/p&gt;
&lt;p&gt;Update: A day later, &lt;a href=&quot;https://gitlab.com/glitchtip/glitchtip-backend/-/merge_requests/767&quot;&gt;glitchtip/glitchtip-backend!767&lt;/a&gt; was accepted and will be available in the first release of GlitchTip 4.0! 🎉&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Eleventy 🤝 Immich</title>
    <link href="https://chris.bur.gs/eleventy-immich/"/>
    <updated>2024-02-12T00:00:00Z</updated>
    <id>https://chris.bur.gs/eleventy-immich/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Lately I&#39;ve been exploring self-hosting personal data, and photo storage, management, discovery and sharing is one service I&#39;m keen to move from a megacorp-hosted option to self-hosted. Several weeks back I gave PhotoPrism a trial, and I&#39;m now checking out &lt;a href=&quot;https://immich.app/&quot;&gt;Immich&lt;/a&gt;. I&#39;m using &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt; for my personal website and one thing I wanted to test was the ability to integrate the two.&lt;/p&gt;
&lt;blockquote&gt;
&lt;h3&gt;June 2024 update ✨ ⭐&lt;/h3&gt;
&lt;p&gt;The plugin referred to in this post is now available via&lt;br /&gt;
&lt;code&gt;npm install @xurizaemon/eleventy-immich&lt;/code&gt; or&lt;br /&gt;
&lt;a href=&quot;https://github.com/xurizaemon/eleventy-immich&quot;&gt;https://github.com/xurizaemon/eleventy-immich&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;h3&gt;February 2026 update ✨ ⭐&lt;/h3&gt;
&lt;p&gt;I&#39;m not using the plugin below now, I found the benefits of dynamically updating content in this site from that&lt;br /&gt;
remote source weren&#39;t as great as copying images to this repo&#39;s media directory, generating frontmatter, and then&lt;br /&gt;
working with markdown here.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Immich permits album sharing, but for public display I prefer to selectively pull some albums or images into my static build pipeline - I&#39;d rather put my faith in a static publishing output than take on the maintenance and securing of a complex service - life is too precious and short. I want to be able to reference photos from my image collection into my writing.&lt;/p&gt;
&lt;p&gt;This last weekend I&#39;ve got a prototype up and running of embedding Immich assets in Eleventy, and here&#39;s an update! All images in this post are film photos taken Summer 2023/2024 using a Lomo Fisheye and Orca black and white 110 film.&lt;/p&gt;
&lt;h2&gt;Shortcodes for &lt;code&gt;immich_image&lt;/code&gt; and &lt;code&gt;immich_album&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Here&#39;s an image I&#39;m embedding which was sourced from Immich when building this site. A custom shortcode has been used to generate an image tag using the UUID of the image in Immich.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{% immich_image &#39;3d0bd5db-a237-4a5e-8994-6ff91fa62878&#39; %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;[ removed ]&lt;/p&gt;
&lt;p&gt;Here&#39;s an album via the &lt;code&gt;immich_album&lt;/code&gt; shortcode. I haven&#39;t attempted to format this in a meaningful way, only to emit some details about the album and then a series of photos. This part will call for templates that can be overridden in the project.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{% immich_album &#39;ef43ecca-f88d-4f5a-b590-533d0611e045&#39; %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;[ removed ]&lt;/p&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;I implemented a collection so I could iterate through Immich photo albums - that was a start point, but it&#39;s removed now as I didn&#39;t want to leave example code in place that would reveal updates to the photo albums over time. I will come back to that to implement filtering (ie &amp;quot;show me albums that are marked public or shared&amp;quot;).&lt;/li&gt;
&lt;li&gt;It would be interesting to provide front matter such as &lt;code&gt;immich_header_image&lt;/code&gt; to pull an image in for the page&#39;s feature image?&lt;/li&gt;
&lt;li&gt;I am keen to experiment with turning the output into &lt;code&gt;.webc&lt;/code&gt; components instead of shortcodes.&lt;/li&gt;
&lt;li&gt;This can become an installable Eleventy plugin ... but it&#39;s not ready yet! Watch &lt;a href=&quot;https://github.com/xurizaemon/eleventy-immich&quot;&gt;https://github.com/xurizaemon/eleventy-immich&lt;/a&gt; for updates.&lt;/li&gt;
&lt;li&gt;Performance wants review. While I&#39;m using Eleventy Fetch and I &lt;em&gt;think&lt;/em&gt; it&#39;s caching, the build time has spiked from ~30s to ~2min, and we only have 24 images in this post. The &lt;code&gt;.cache/&lt;/code&gt; directory seems to be getting populated and staying about the same size.&lt;/li&gt;
&lt;li&gt;Direct embeds are demonstrated above, calling an image or album into a post by some reference. I&#39;d like to explore configuring a set of albums and having Eleventy build out a section of my website with those pages based on the data retrieved, and with URLs for each album or image media.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Implementation&lt;/h2&gt;
&lt;p&gt;Here&#39;s what I currently have.&lt;/p&gt;
&lt;h3&gt;Configuration&lt;/h3&gt;
&lt;p&gt;Right now, the plugin supports only a single Immich instance and user account, via environment variables. Your build environment must be able to make HTTP requests to your Immich API URL. You&#39;ll also need an API key from your Immich instance - see &lt;code&gt;/user-settings&lt;/code&gt; there to create a key for your account. Set the required variables in your build environment:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;IMMICH_BASE_URL=https://immich.example.org&lt;br /&gt;IMMICH_API_KEY=ABCD1abcd1ABCD1abcd1ABCD1abcd1ABCD1abcd1AB&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&#39;m running this by adding &lt;code&gt;./immich.js&lt;/code&gt; to my codebase, next to the Eleventy configuration file. I&#39;ve copied the code to &lt;a href=&quot;https://github.com/xurizaemon/eleventy-immich&quot;&gt;https://github.com/xurizaemon/eleventy-immich&lt;/a&gt; as well - but I haven&#39;t even switched my own use to that yet.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;./immich.js&lt;/code&gt; contents:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;/**&lt;br /&gt; * A plugin to provide Immich shortcodes for Eleventy.&lt;br /&gt; */&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; EleventyFetch &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;@11ty/eleventy-fetch&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; Image &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;@11ty/eleventy-img&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;immich&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;eleventyConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; required_vars &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;IMMICH_BASE_URL&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;IMMICH_API_KEY&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; required_vars&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Immich plugin requires setting the following env vars: &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;required_vars&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;, &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token comment&quot;&gt;// @TODO Can I bail out here somehow?&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; immichUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;IMMICH_BASE_URL&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; apiKey &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;IMMICH_API_KEY&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; defaultFetchOptions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token literal-property property&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token string-property property&quot;&gt;&#39;x-api-key&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; apiKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;/**&lt;br /&gt;     * Fetches an album and renders each asset.&lt;br /&gt;     *&lt;br /&gt;     * @param uuid&lt;br /&gt;     * @returns {Promise&amp;lt;string&gt;}&lt;br /&gt;     */&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;immichAlbumShortcode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;uuid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; albumUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;immichUrl&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/api/album/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;uuid&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; albumData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;EleventyFetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;albumUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token literal-property property&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;10m&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token literal-property property&quot;&gt;fetchOptions&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;defaultFetchOptions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;application/json&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; html &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;lt;div class=&quot;immich-album&quot;&gt;&amp;lt;h2&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;albumData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;albumName&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;lt;/h2&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;albumData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;description&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            html &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;lt;p&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;albumData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;description&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;lt;/p&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;albumData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assets&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            html &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&amp;lt;div class=&quot;immich-album-assets&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;asset &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; albumData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;assets&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                html &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;immichImageShortcode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;asset&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;            html &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&amp;lt;/div&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        html &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&amp;lt;/div&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; html&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;/**&lt;br /&gt;     * Fetches image data and file from a given UUID and generates HTML for the image element with specified attributes.&lt;br /&gt;     *&lt;br /&gt;     * @param {string} uuid - The UUID of the image asset.&lt;br /&gt;     * @returns {Promise&amp;lt;string&gt;} - A promise that resolves to the generated HTML for the image element.&lt;br /&gt;     */&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;immichImageShortcode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;uuid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; assetDataUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;immichUrl&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/api/asset/assetById/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;uuid&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; assetFileUrl &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;immichUrl&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/api/asset/file/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;uuid&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; fetchOptions &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; defaultFetchOptions&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        fetchOptions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;accept &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;application/json&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; assetData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;EleventyFetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;assetDataUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token literal-property property&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;10m&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token literal-property property&quot;&gt;fetchOptions&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; fetchOptions&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        fetchOptions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;accept &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;application/octet-stream&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; assetFile &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;EleventyFetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;assetFileUrl&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token literal-property property&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1w&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;buffer&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token literal-property property&quot;&gt;fetchOptions&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; fetchOptions&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; alt &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; assetData&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exifInfo&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;description &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;No image description available&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; metadata &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;assetFile&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token literal-property property&quot;&gt;widths&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;300&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;600&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token literal-property property&quot;&gt;formats&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;jpeg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token literal-property property&quot;&gt;outputDir&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;public/media/img/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token literal-property property&quot;&gt;urlPath&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/media/img/&#39;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; attributes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token literal-property property&quot;&gt;alt&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; alt&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token literal-property property&quot;&gt;sizes&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token literal-property property&quot;&gt;loading&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;lazy&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token literal-property property&quot;&gt;decoding&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;async&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Promise&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Image&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;generateHTML&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; attributes&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;/**&lt;br /&gt;     * Retrieves a collection of Immich albums.&lt;br /&gt;     *&lt;br /&gt;     * @returns {Promise&amp;lt;Array&gt;} A promise that resolves to an array of albums.&lt;br /&gt;     *                            Each album is represented as an object in the array.&lt;br /&gt;     *                            If an error occurs, an empty array is returned.&lt;br /&gt;     */&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;immichAlbumsCollection&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; albums &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;EleventyFetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;immichUrl&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/api/album&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token literal-property property&quot;&gt;duration&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1d&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token literal-property property&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;json&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token literal-property property&quot;&gt;fetchOptions&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;defaultFetchOptions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;                    &lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;accept&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;application/json&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;                &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;album &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt; albums&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;                collection&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;album&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; collection&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Failed getting Immich albums&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;            console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;exception&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// eleventyConfig.addCollection(&quot;immich_albums&quot;, immichAlbumsCollection);&lt;/span&gt;&lt;br /&gt;    eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addShortcode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;immich_album&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; immichAlbumShortcode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addShortcode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;immich_image&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; immichImageShortcode&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We add the plugin to Eleventy by modifying the &lt;a href=&quot;https://www.11ty.dev/docs/config/#default-filenames&quot;&gt;eleventy configuration file&lt;/a&gt;, eg this &lt;code&gt;.eleventy.js&lt;/code&gt; addition:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;eleventyConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addPlugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./immich&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;PS. Hopefully you saw the update at the top - this plugin is now available as an NPM module, see &lt;a href=&quot;https://github.com/xurizaemon/eleventy-immich&quot;&gt;https://github.com/xurizaemon/eleventy-immich&lt;/a&gt; or &lt;code&gt;npm install @xurizaemon/eleventy-immich&lt;/code&gt; - it&#39;s still WIP and contributions are welcome!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Yakfilling dates</title>
    <link href="https://chris.bur.gs/yakfilling-markdown-day-property/"/>
    <updated>2024-02-04T00:00:00Z</updated>
    <id>https://chris.bur.gs/yakfilling-markdown-day-property/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I have a journal / lab book markdown collection with many entry file paths by date (&lt;code&gt;2015/01/23.md&lt;/code&gt;). Only the more recent files also had inline date metadata.&lt;/p&gt;
&lt;p&gt;I wanted to backfill missing &lt;code&gt;day&lt;/code&gt; properties for these markdown files into the yaml frontmatter. Initially my notes were just text, and over time recorded additional metadata into the entries, so making the date records consistent adds context to metadata already in the files.&lt;/p&gt;
&lt;p&gt;Tools such as &lt;a href=&quot;https://markdowndb.com/&quot;&gt;MarkdownDB&lt;/a&gt;, &lt;a href=&quot;https://blacksmithgu.github.io/obsidian-dataview/&quot;&gt;DataView&lt;/a&gt;, or SSGs such as &lt;a href=&quot;https://www.11ty.dev/&quot;&gt;Eleventy&lt;/a&gt; and &lt;a href=&quot;https://gohugo.io/&quot;&gt;Hugo&lt;/a&gt; permit querying a markdown corpus for data. To see records for those dates, migrating day &amp;quot;data&amp;quot; from file path into yaml frontmatter made access more consistent.&lt;/p&gt;
&lt;p&gt;Thinking lots about data preservation and the maintenance costs of running dynamically generating systems at the moment ...&lt;/p&gt;
&lt;p&gt;Here&#39;s the bash script I came up with to sort this quickly and not prettily. I tested it out in a copy of the repo (I&#39;m not stupid!) and was delighted with the results.&lt;/p&gt;
&lt;p&gt;This really accounts for a pretty simple markdown structure only, making assumptions about the structure of the documents. I know what this specific corpus mess is and isn&#39;t and accept the T&amp;amp;C of any breakages.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/usr/bin/env bash&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;SOURCE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$SOURCE&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Usage: add-dates.sh &amp;lt;path&gt;&quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$SOURCE&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-name&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;*.md&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;read&lt;/span&gt; FILE &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token assign-left variable&quot;&gt;DAY&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; $FILE &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sed&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;s|^.*&#92;([0-9]&#92;{4&#92;}&#92;)/&#92;([0-9]&#92;{2&#92;}&#92;)/&#92;([0-9]&#92;{2&#92;}&#92;).*|&#92;1-&#92;2-&#92;3|p&#39;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$DAY&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;File &lt;span class=&quot;token variable&quot;&gt;$FILE&lt;/span&gt; does not match date pattern&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token builtin class-name&quot;&gt;continue&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;# Use -Pzo to see matched output for testing.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-Pzl&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^---&lt;span class=&quot;token entity&quot; title=&quot;&#92;n&quot;&gt;&#92;n&lt;/span&gt;(.|&lt;span class=&quot;token entity&quot; title=&quot;&#92;n&quot;&gt;&#92;n&lt;/span&gt;)*&lt;span class=&quot;token entity&quot; title=&quot;&#92;n&quot;&gt;&#92;n&lt;/span&gt;---&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$FILE&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; /dev/null&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;# echo &quot;$FILE has frontmatter.&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-E&lt;/span&gt; -- &lt;span class=&quot;token string&quot;&gt;&#39;^day:&#39;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$FILE&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; /dev/null&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$FILE&lt;/span&gt; has no day entry, should be &lt;span class=&quot;token variable&quot;&gt;$DAY&lt;/span&gt;.&quot;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token assign-left variable&quot;&gt;PROPERTY&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;day: &lt;span class=&quot;token variable&quot;&gt;$DAY&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Inserting: &lt;span class=&quot;token variable&quot;&gt;$PROPERTY&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token function&quot;&gt;sed&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0,/^---/a &lt;span class=&quot;token variable&quot;&gt;$PROPERTY&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$FILE&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;# else&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;#   # echo &quot;$FILE has day entry.&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;#   grep -E -- &#39;^day:&#39; &quot;$FILE&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$FILE&lt;/span&gt; has no frontmatter, should be &lt;span class=&quot;token variable&quot;&gt;$DAY&lt;/span&gt;.&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;METADATA&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;---&lt;span class=&quot;token entity&quot; title=&quot;&#92;n&quot;&gt;&#92;n&lt;/span&gt;day: &lt;span class=&quot;token variable&quot;&gt;$DAY&lt;/span&gt;&lt;span class=&quot;token entity&quot; title=&quot;&#92;n&quot;&gt;&#92;n&lt;/span&gt;---&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Inserting: &lt;span class=&quot;token variable&quot;&gt;$METADATA&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token function&quot;&gt;sed&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-i&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1i &lt;span class=&quot;token variable&quot;&gt;$METADATA&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$FILE&lt;/span&gt;&quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;done&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Usage: &lt;code&gt;add-dates.sh /path/to/journal&lt;/code&gt;. Because of the layout of my journal (folders with and without date-based naming) I targetted each year one by one, working only on a separate copy so I could review changes and merge via Git when happy.&lt;/p&gt;
&lt;p&gt;I hope this script may be useful or interesting for you to build on. This script is &lt;a href=&quot;http://unmaintained.tech/&quot;&gt;No Maintenance Intended&lt;/a&gt; :)&lt;/p&gt;
&lt;p&gt;It&#39;s a pretty crude approach, but it suffices nicely. I was happy to quickly see results like this:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/backfilled-dates.png&quot; alt=&quot;Screenshot of git commit message showing backfilled dates in journal entries from years ago&quot; /&gt;&lt;/p&gt;
&lt;p&gt;A happy accident of this implementation was that some notes with paths like &lt;code&gt;2019/02/03/Some topic.md&lt;/code&gt; were also correctly dated. A win for UNIX-y simplicty I guess. I&#39;d forgotten that at one point I tried such a layout briefly ... it didn&#39;t really work for me, so I&#39;d dropped it.&lt;/p&gt;
&lt;p&gt;Feeling grateful for the workshop on the Atom Journal plugin at OS//OS in 2015 which got me to start using date-based filenames!&lt;/p&gt;
&lt;p&gt;Whew, 882 modified files!&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/backfilled-pr.png&quot; alt=&quot;Screenshot of the PR in Github&quot; /&gt;&lt;/p&gt;
&lt;p&gt;On review, I found one case that hadn&#39;t been handled and which wanted tidying. Where the original markdown file had an empty frontmatter block -&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
---
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;the result was a duplicate frontmatter entry would be introduced.&lt;/p&gt;
&lt;p&gt;I used &lt;code&gt;grep -riPzl -- &amp;quot;---&#92;n---&amp;quot; &amp;lt;path&amp;gt;&lt;/code&gt; to locate seven affected files like that, and fixed it by hand-editing. All were from a similar date range ... I didn&#39;t bother handling that in the script above.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Gull feet</title>
    <link href="https://chris.bur.gs/gull-feet/"/>
    <updated>2024-01-28T00:00:00Z</updated>
    <id>https://chris.bur.gs/gull-feet/</id>
    <content xml:lang="en" type="html">&lt;p&gt;&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; src=&quot;https://chris.bur.gs/media/gull-2.mp4&quot;&gt;A gull walks on a canvas roof, seen from below; the gull&#39;s feet are orange, while its silhouette against the canvas moves smoothly above the shape of the feet.&lt;/video&gt; &lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; src=&quot;https://chris.bur.gs/media/gull-1.mp4&quot;&gt;A second recording of a gull walking on a canvas roof, seen from below; the gull&#39;s feet are orange, while its silhouette against the canvas moves smoothly above the shape of the feet.&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;We stopped at Punakaiki on our Toastie Coastie road trip. I loved this gull&#39;s feet through the canvas roof and plan to make some kind of animation based on it.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Send shrugs 🤷</title>
    <link href="https://chris.bur.gs/shruggies/"/>
    <updated>2023-12-10T00:00:00Z</updated>
    <id>https://chris.bur.gs/shruggies/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I find Matrix&#39;s &lt;code&gt;/confetti&lt;/code&gt;, &lt;code&gt;/fireworks&lt;/code&gt;, &lt;code&gt;/hearts&lt;/code&gt;, &lt;code&gt;/rainfall&lt;/code&gt;, &lt;code&gt;/snowfall&lt;/code&gt;, and &lt;code&gt;/spaceinvaders&lt;/code&gt; commands fun and entertaining (and too noisy to use often).&lt;/p&gt;
&lt;p&gt;I&#39;ve long been fascinated by the ways in which limiting vocabulary affects the way folks communicate and ultimately think. Consider the period when Facebook permitted the singular reaction &amp;quot;Like&amp;quot; to respond to news events, or whatever set of half a dozen emotional quick responses are available to respond to a LinkedIn post. Or Orwell&#39;s Newspeak, in &amp;quot;1984&amp;quot;, a language created by a totalitarian state to limit the range of thought.&lt;/p&gt;
&lt;p&gt;Sometimes - especially in the business world - it&#39;s important to express calm acceptance of a situation, and perhaps even celebrate this. For this I thought it&#39;d be good to add a &lt;code&gt;/shruggiestorm&lt;/code&gt; command to Matrix.&lt;/p&gt;
&lt;p&gt;When I looked into this, I learned the Matrix team are &lt;a href=&quot;https://github.com/vector-im/element-meta/blob/develop/docs/chat_effects.md&quot;&gt;not accepting additional effects&lt;/a&gt; (probably for quite reasonable reasons).&lt;/p&gt;
&lt;p&gt;I wanted to see it in action; now we can. That button below will rain shruggies for you.&lt;/p&gt;
&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://chris.bur.gs/media/send-shrugs.css&quot; /&gt;
&lt;script src=&quot;https://chris.bur.gs/media/send-shrugs.js&quot;&gt;&lt;/script&gt;
&lt;button class=&quot;send-shrugs&quot;&gt;¯&#92;_(ツ)_/¯&lt;/button&gt;
&lt;div class=&quot;canvas&quot;&gt;&lt;/div&gt;
&lt;p&gt;Source (being lazy, just linking):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://chris.bur.gs/media/send-shrugs.js&quot;&gt;send-shrugs.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chris.bur.gs/media/send-shrugs.css&quot;&gt;send-shrugs.css&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thankyou for your interest! For a more immersive experience, enjoy &lt;a href=&quot;https://chris.bur.gs/shrugs/&quot;&gt;the Shrugs generator page&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Shroomcam 🍄</title>
    <link href="https://chris.bur.gs/shroomcam/"/>
    <updated>2023-12-09T00:00:00Z</updated>
    <id>https://chris.bur.gs/shroomcam/</id>
    <content xml:lang="en" type="html">&lt;video width=&quot;720&quot; controls=&quot;&quot;&gt;
  &lt;source src=&quot;https://chris.bur.gs/media/shroomcam.mp4&quot; type=&quot;video/mp4&quot; /&gt;
Your browser does not support the video tag.
&lt;/video&gt;
&lt;p&gt;We grew some mushrooms! We&#39;re having them in tacos tonight.&lt;/p&gt;
&lt;p&gt;These are native Phoenix Oyster mushrooms (pleurotus pulmonarius). If you know the right reo Māori kupu I&#39;m interested.&lt;/p&gt;
&lt;p&gt;I got the kit from the farmers markets here in Ōtepoti, from &lt;a href=&quot;https://www.brightsidegourmetfood.co.nz/&quot;&gt;Brightside&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;🍄🍄🍄&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Lando + Memcache + Memcacheadmin</title>
    <link href="https://chris.bur.gs/lando-memcacheadmin/"/>
    <updated>2023-12-06T00:00:00Z</updated>
    <id>https://chris.bur.gs/lando-memcacheadmin/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I had not heard of &lt;a href=&quot;https://github.com/hatamiarash7/Memcached-Admin&quot;&gt;hatamiarash7/Memcached-Admin&lt;/a&gt; before today, and got nerdsniped by someone telling me they had trouble setting it up for their local development with SilverStripe. So, here&#39;s a recipe.&lt;/p&gt;
&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; test&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;memcached&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; memcached&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;memcacheadmin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; compose&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;app_mount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;false&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; hatamiarash7/memcached&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;admin&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;latest&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; docker&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;php&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;entrypoint apache2&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;foreground&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# This needs configuring to the hostname shown from `lando info`&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token key atrule&quot;&gt;MEMCACHED_HOST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;memcached.test.internal&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will bring up a small Lando stack with only memcached and memcached-admin, and the only URL output will be for the admin interface. This example is mostly for lifting into an existing project, and as an example of how to utilise a Docker image as part of a Lando stack using a &lt;code&gt;compose&lt;/code&gt; service.&lt;/p&gt;
&lt;p&gt;Note that &lt;code&gt;MEMCACHED_HOST&lt;/code&gt; contains the &lt;em&gt;name&lt;/em&gt; of the project in the hostname - this will need updating to match the project it&#39;s being dropped into.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Oh and another thing&lt;/h2&gt;
&lt;p&gt;I also want to give a shout out to how you can overlay configs in Lando, so I can add &lt;code&gt;.lando.local.yml&lt;/code&gt; to &lt;code&gt;.gitignore&lt;/code&gt;, and then put the &amp;quot;extras&amp;quot; into the local file only. This means I can have a regular project &lt;code&gt;.lando.yml&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.lando.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Lots of project specific config here.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; whatever&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; lamp&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then isolate whatever whimsical things I&#39;m doing - eg test out services or tooling in a local sandbox - without it showing up in the main configuration.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.lando.local.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Stuff I&#39;m messing with and don&#39;t want to track in git.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;memcached&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; memcached&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;memcacheadmin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; compose&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;app_mount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;false&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; hatamiarash7/memcached&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;admin&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;latest&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; docker&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;php&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;entrypoint apache2&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;foreground&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;80&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;# This needs configuring to the hostname shown from `lando info`&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token key atrule&quot;&gt;MEMCACHED_HOST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;memcached.whatever.internal&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;# Random example tooling made up to demo adding that as well.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;tooling&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;install-netcat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; memcached&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;cmd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; apt &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;y update&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; apt &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;y install netcat&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; root&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;query-memcache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; memcached&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;cmd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; echo &quot;version&quot; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt; nc memcached.test.internal 11211&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; nc memcached.test.internal 11211&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This to me is a tidy way to stash things in a file that means I can retain a modification locally while hopping between branches. I can copy the day&#39;s tooling or services into the main &lt;code&gt;.lando.yml&lt;/code&gt; when the time is right.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>SensorWatch days of the week in reo Māori</title>
    <link href="https://chris.bur.gs/sensor-watch-r%C4%81-m%C4%81ori/"/>
    <updated>2023-11-11T21:54:00Z</updated>
    <id>https://chris.bur.gs/sensor-watch-r%C4%81-m%C4%81ori/</id>
    <content xml:lang="en" type="html">&lt;p&gt;The &lt;a href=&quot;https://www.sensorwatch.net/&quot;&gt;Sensor Watch&lt;/a&gt; is an open source, custom replacement board for the CASIO F-91W and similar watches. I bought one on a whim off Crowd Supply back in the fog of COVID and work that is the recent past. The project is by &lt;a href=&quot;https://mastodon.social/@joeycastillo&quot;&gt;Joey Castillo&lt;/a&gt; / &lt;a href=&quot;https://www.oddlyspecificobjects.com/&quot;&gt;Oddly Specific Obbjects&lt;/a&gt; and collaborators.&lt;/p&gt;
&lt;p&gt;The SensorWatch permits custom watchfaces, or you can select from a range of existing custom faces, and it comes with a default custom arrangement. The remaining hardware of the watch is unmodified which establishes some interesting boundaries to what&#39;s possible - there are three buttons available for operation, and the original CASIO LCD for output.&lt;/p&gt;
&lt;p&gt;I&#39;d purchased the board (and an additional temperature sensor, which is on the board in the later &amp;quot;red&amp;quot; edition), but did not do anything with it for eleven months until a recent isolation had me idle for a few days in my room. (I&#39;m always happy to find my former self has sent cool things through time to me!)&lt;/p&gt;
&lt;p&gt;The interface of the watch once it has new faces onboard is really interesting to me. At first the interface felt extremely familiar, because it&#39;s the three buttons of a CASIO watch, so the actions are familiar. The watchfaces however were unfamiliar - but the familiarity of the buttons makes the interface discoverable. Something I found interesting (via &lt;code&gt;grep -rE &amp;quot;&#92;bEVENT_&amp;quot; .&lt;/code&gt; in the source) is that long presses are supported. I got to wondering if combined press events are supported?&lt;/p&gt;
&lt;p&gt;But first: it took me nearly a year to swap the board into the F91-w on my wrist. Lazy bones. I knew it required a solder and was timid at first. Eventually when I was in iso a few days I did put the board into my everyday watch, and then a week or two later took it apart again to transfer the piezo connector from the original watch board (which went fine, as it should) and finetune my custom ROM.&lt;/p&gt;
&lt;p&gt;One minor change I had wished for was having the days of the week (&amp;quot;MO&amp;quot;, &amp;quot;TU&amp;quot;, &amp;quot;WE&amp;quot;) appear in Te Reo Māori (&amp;quot;HI&amp;quot;, &amp;quot;TU, &amp;quot;AP&amp;quot;). It&#39;s distinctive and something special that reminds of my place in the world as we spin through the cosmos. The localisation change is extremely minor - a single array needs replacing in the source:&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;index 9e524762..0225dee0 100644&lt;br /&gt;&lt;span class=&quot;token coord&quot;&gt;--- a/watch-library/shared/watch/watch_utility.c&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token coord&quot;&gt;+++ b/watch-library/shared/watch/watch_utility.c&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token coord&quot;&gt;@@ -26,7 +26,8 @@&lt;/span&gt;&lt;br /&gt;#include &quot;watch_utility.h&quot;&lt;br /&gt;&lt;br /&gt;const char * watch_utility_get_weekday(watch_date_time date_time) {&lt;br /&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;    static const char weekdays[7][3] = {&quot;MO&quot;, &quot;TU&quot;, &quot;WE&quot;, &quot;TH&quot;, &quot;FR&quot;, &quot;SA&quot;, &quot;SU&quot;};&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;    static const char weekdays[7][3] = {&quot;HI&quot;, &quot;TU&quot;, &quot;A &quot;, &quot;PA&quot;, &quot;ME&quot;, &quot;HO&quot;, &quot;TA&quot;};&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;   return weekdays[watch_utility_get_iso8601_weekday_number(date_time.unit.year WATCH_RTC_REFERENCE_YEAR, date_time.unit.month, date_time.unit.day) - 1];&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rāhina, Rātu, Rāapa, Rāpare, Rāmere, Rāhoroi, Rātapu.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/casio-in-m%C4%81ori/casio-f91w-hi.jpg&quot; alt=&quot;Rāhina&quot; /&gt; &lt;img src=&quot;https://chris.bur.gs/media/casio-in-m%C4%81ori/casio-f91w-tu.jpg&quot; alt=&quot;Rātu&quot; /&gt; &lt;img src=&quot;https://chris.bur.gs/media/casio-in-m%C4%81ori/casio-f91w-a_.jpg&quot; alt=&quot;Rāapa&quot; /&gt; &lt;img src=&quot;https://chris.bur.gs/media/casio-in-m%C4%81ori/casio-f91w-pa.jpg&quot; alt=&quot;Rāpare&quot; /&gt; &lt;img src=&quot;https://chris.bur.gs/media/casio-in-m%C4%81ori/casio-f91w-me.jpg&quot; alt=&quot;Rāmere&quot; /&gt; &lt;img src=&quot;https://chris.bur.gs/media/casio-in-m%C4%81ori/casio-f91w-ho.jpg&quot; alt=&quot;Rāhoroi&quot; /&gt; &lt;img src=&quot;https://chris.bur.gs/media/casio-in-m%C4%81ori/casio-f91w-ta.jpg&quot; alt=&quot;Rātapu&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Hey, why&#39;s Rāapa just a single &amp;quot;A&amp;quot;? The original LCD &lt;a href=&quot;https://www.sensorwatch.net/docs/wig/display/#the-day-digits&quot;&gt;doesn&#39;t have separately addressable segments&lt;/a&gt; for all of the shapes in the letter. If &amp;quot;AP&amp;quot; is displayed, the LCD shows &amp;quot;AF&amp;quot;. Of the possible displays, I felt &amp;quot;A &amp;quot; was clearest and least wrong.&lt;/p&gt;
&lt;p&gt;&amp;quot;AF&amp;quot; is amusing and I was tempted to keep it: Wednesday As Fuck. But, it wasn&#39;t what I wanted.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/casio-in-m%C4%81ori/casio-f91w-af.jpg&quot; alt=&quot;casio-f91w-af.jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I also customised the selection of faces - currently I have simple clock, tomato, countdown (3min), moon phase, prefs, set time. Here&#39;s a link to &lt;a href=&quot;https://github.com/xurizaemon/Sensor-Watch/pull/1&quot;&gt;my current custom ROM source&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The SensorWatch is a super cool board and the result is a customisation of a classic simple watch that I&#39;m happy with. I&#39;m really enjoying it and plan on ordering at least &lt;em&gt;one&lt;/em&gt; more as a gift :)&lt;/p&gt;
&lt;p&gt;It might be interesting to consider a custom face which supports localisation, or even introducing a localisation layer to the main ROM, for a more full featured support.&lt;/p&gt;
&lt;p&gt;Update, next day: I&#39;d popped into the Sensor Watch Discord channel at the end of October with a question, and shared my WIP at the time. After writing this post yesterday I see &lt;a href=&quot;https://github.com/joeycastillo/Sensor-Watch/pull/323&quot;&gt;joeycastillo/Sensor-Watch#323&lt;/a&gt; has been created by wesleyac, which brings in a compile-time setting for day language, with the initial options being English (default), Spanish, German and reo Māori. Neat!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Cypress in Gitlab CI</title>
    <link href="https://chris.bur.gs/gitlab-ci-cypress/"/>
    <updated>2023-10-28T00:00:00Z</updated>
    <id>https://chris.bur.gs/gitlab-ci-cypress/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I had a few days downtime and found myself trying out an unfamiliar testing framework, Cypress. I was interested to see how it compared to other test frameworks.&lt;/p&gt;
&lt;p&gt;I&#39;d seen folks posting about trouble getting it running in containerised environments - local dev with DDev or Lando, and CI, which interested me.&lt;/p&gt;
&lt;p&gt;I figured I&#39;d give it a spin in Gitlab CI. My first attempt was bringing Cypress up alongside the existing environment by pulling Cypress&#39;s dependency chain (NodeJS) into the PHP service which the site&#39;s tests already ran on. This worked fairly well.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gitlab.com/xurizaemon/commerce-demo/-/merge_requests/20&quot;&gt;Refer to xurizaemon/commerce-demo!20&lt;/a&gt; for details.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Lando v4 preview</title>
    <link href="https://chris.bur.gs/lando-v4-preview/"/>
    <updated>2023-09-30T19:49:00Z</updated>
    <id>https://chris.bur.gs/lando-v4-preview/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I was fortunate to join a Lando v4 preview call back at the start of October and get a look at what&#39;s coming up in v4. Mike Pirog and Alec walked a group of us through some of what&#39;s exciting and how it&#39;s coming along. Here&#39;s my notes!&lt;/p&gt;
&lt;h3&gt;Lando v3.20 / v4 sponsor preview&lt;/h3&gt;
&lt;p&gt;The new services API sounds really nice, less bash build steps and more tooling to drop in eg PHP extensions, faster (caching layers instead of writing docker-compose files which require a full build on rebuild)&lt;/p&gt;
&lt;p&gt;Orchestrator / Build engine (currently Docker Compose) becomes swappable, opens door for Colima, K8s etc - great news for folks who want that flexibility.&lt;/p&gt;
&lt;p&gt;Will still use Docker / Docker Compose syntax generally, translate to other orchestrators &amp;amp; build engines, permits per-project build engine / orchestration (if desired).&lt;/p&gt;
&lt;p&gt;Permits using full users instead of user mapping, decouples recipes more from current base images (eg bitnami/* for DBs).&lt;/p&gt;
&lt;p&gt;Lots of existing but prev undocumented switches now documented in &lt;a href=&quot;https://docs.lando.dev/core/v3/lando-service.html&quot;&gt;https://docs.lando.dev/core/v3/lando-service.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Much nicer debug output (eg seeing results of failed healthchecks directly, debug output shows full output).&lt;/p&gt;
&lt;p&gt;Stealth launch today of WIP v4 docs &lt;a href=&quot;https://docs.lando.dev/core/v4/&quot;&gt;https://docs.lando.dev/core/v4/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;lando&lt;/code&gt; service type looks great - it&#39;s &lt;code&gt;compose&lt;/code&gt; service but &lt;code&gt;image&lt;/code&gt; key can be any of: local dockerfile path, inline dockerfile, registry image, or configurations (eg &amp;quot;php type, but add xsl&amp;quot; only without copypasta of apt steps).&lt;/p&gt;
&lt;p&gt;Version becomes easier to swap cos you can update services to Lando v4 individually - sounds good for maintenance, no big jumps.&lt;/p&gt;
&lt;p&gt;Couple mentions of &amp;quot;Lando Team&amp;quot; for shared/common config - I didn&#39;t catch what this product / offering is, but it sounds interesting. It makes sense for Lando to be offering this sort of value extension.&lt;/p&gt;
&lt;p&gt;Service groups (eg share configuration chunks like &amp;quot;get abc php extensions&amp;quot; between several services) could help tidy up some of our currently inheriting configurations.&lt;/p&gt;
&lt;p&gt;All in all - looking really good, and grateful to the Lando team for sharing!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Convert MR patches to local patches</title>
    <link href="https://chris.bur.gs/mrs-to-patches/"/>
    <updated>2023-09-12T20:48:00Z</updated>
    <id>https://chris.bur.gs/mrs-to-patches/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Many projects use pull or merge requests to collaborate on fixes to software components. Tooling can enable us to apply these patches during build phase, so we can say &amp;quot;Use some/module v1.2.3, but apply the fixes I need from issues #4 and #5 as well&amp;quot;.&lt;/p&gt;
&lt;p&gt;Collaborating on these changes is great, and it&#39;s helpful to be able to pull patchs from patch URLs (&lt;a href=&quot;https://stackoverflow.com/questions/6188591/download-github-pull-request-as-unified-diff&quot;&gt;Github&lt;/a&gt;, &lt;a href=&quot;https://docs.gitlab.com/ee/development/merge_request_concepts/diffs/&quot;&gt;Gitlab&lt;/a&gt;, &lt;a href=&quot;https://www.drupal.org/docs/develop/git/using-gitlab-to-contribute-to-drupal/downloading-a-patch-file&quot;&gt;Drupal&lt;/a&gt; docs) directly when things are in a state of lots of change. You might need to add additional fixes as you go, and tracking that with a local patch copy is a bit of extra work.&lt;/p&gt;
&lt;p&gt;It&#39;s convenient to use the patch URLs when things are very dynamic, but that adds some risks - other collaborators can push changes which break things unexpectedly, and an external actor could even introduce malicious code to your builds when you&#39;re pulling unvalidated code from the internet like that.&lt;/p&gt;
&lt;p&gt;So, before things start to &amp;quot;set&amp;quot;, you want to turn those URLs into local copies of patches, which aren&#39;t mutable by external parties. To that end, here&#39;s a quick PHP script to convert remote MR patch/diff URLs to local patches.&lt;/p&gt;
&lt;p&gt;This example is hardcoded for a build using &lt;code&gt;cweagans/composer-patches&lt;/code&gt;, with a filename of &lt;code&gt;composer.patches.json&lt;/code&gt; and merge requests from Drupal&#39;s Gitlab, but can be readily adjusted to suit your needs.&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-php&quot;&gt;#!/usr/bin/env php&lt;br /&gt;&lt;span class=&quot;token php language-php&quot;&gt;&lt;span class=&quot;token delimiter important&quot;&gt;&amp;lt;?php&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/**&lt;br /&gt; * For remote patches which are at mutable URLs, we make a local copy.&lt;br /&gt; *&lt;br /&gt; * This counters risk of breakage or attack via update to an open MR.&lt;br /&gt; */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$config&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;json_decode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;file_get_contents&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;composer.patches.json&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;patches&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$project&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$patches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$patches&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$description&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$parsed_url&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parse_url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;isset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$parsed_url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;scheme&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;stristr&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;git.drupalcode.org&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$parsed_url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;host&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token variable&quot;&gt;$prefix&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;preg_replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;#/#&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;_&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;          &lt;span class=&quot;token variable&quot;&gt;$filename&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;preg_replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;/[^0-9a-zA-Z_]/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;-&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string double-quoted-string&quot;&gt;&quot;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$prefix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;-&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token variable&quot;&gt;$filename&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;preg_replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;/-+/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;-&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$filename&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token variable&quot;&gt;$filename&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;trim&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$filename&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;-&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;          &lt;span class=&quot;token variable&quot;&gt;$filename&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string double-quoted-string&quot;&gt;&quot;patches/&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$filename&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;.patch&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token variable&quot;&gt;$cmd&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;escapeshellcmd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string double-quoted-string&quot;&gt;&quot;curl -sSo &lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$filename&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;shell_exec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$cmd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string double-quoted-string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;token string double-quoted-string&quot;&gt;&quot;Retrieved &lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; to &lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$filename&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;.&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;PHP_EOL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token variable&quot;&gt;$url&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$filename&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;token string double-quoted-string&quot;&gt;&quot;Unable to retrieve &lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; for project &lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;.&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;PHP_EOL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token comment&quot;&gt;// OK to skip; it&#39;s not a remote URL.&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;token string double-quoted-string&quot;&gt;&quot;Unable to parse patch URL &lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt; for project &lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;.&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;PHP_EOL&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token function&quot;&gt;file_put_contents&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;composer.patches.json&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;json_encode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;JSON_UNESCAPED_SLASHES&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;JSON_PRETTY_PRINT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>Debugging a Sieve detail that was confounding me</title>
    <link href="https://chris.bur.gs/sieve-order-fileinto/"/>
    <updated>2023-09-12T20:48:00Z</updated>
    <id>https://chris.bur.gs/sieve-order-fileinto/</id>
    <content xml:lang="en" type="html">&lt;p&gt;This Sieve behaviour had been bugging me, I just solved it and needed to share.&lt;/p&gt;
&lt;p&gt;Sieve (&lt;a href=&quot;https://en.wikipedia.org/wiki/Sieve_(mail_filtering_language)&quot;&gt;Wikipedia&lt;/a&gt;) is a mail filtering tool. It&#39;s brutally effective and if your mail provider supports it (as my work and personal emails do), I recommend it highly to sort your email for you (and more).&lt;/p&gt;
&lt;p&gt;However ... a specific filter seemed to be misbehaving. The intended behaviour was:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For emails from Gitlab, if the project path contains &amp;quot;clientname&amp;quot;, place the message in my &amp;quot;Projects/Clientname&amp;quot; folder&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://chris.bur.gs/sieve-order-fileinto/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;li&gt;Unless I&#39;m explicitly mentioned, mark the message as &amp;quot;Seen&amp;quot;&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://chris.bur.gs/sieve-order-fileinto/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&#39;s what I used to have:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  if header :contains &amp;quot;X-GitLab-Project-Path&amp;quot; [
    &amp;quot;clientname/&amp;quot;
  ] {
    fileinto &amp;quot;INBOX.Projects.Clientname&amp;quot;;
    if not body :contains &amp;quot;@chrisburgess&amp;quot; {
      setflag &amp;quot;&#92;&#92;Seen&amp;quot;;
    }
    stop;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My gut feel was that this worked fine - but I could see evidence (emails marked unread which didn&#39;t @mention me) it wasn&#39;t working now. A slightly hard bug to be confident about as there&#39;s a lot of emails, and it &lt;em&gt;felt&lt;/em&gt; like it worked most of the time. I had that Heisenbug feeling. This configuration was still filing Gitlab messages into the correct folder ... but it seemed like it was inconsistently marking items as Seen.&lt;/p&gt;
&lt;p&gt;I wondered if Gitlab&#39;s emails contained the string &lt;code&gt;@chrisburgess&lt;/code&gt; in the headers - not how I expected &lt;code&gt;body :contains&lt;/code&gt; to work, but I did start to wonder. I checked the email source and found no matches.&lt;/p&gt;
&lt;p&gt;Perhaps I was considering the &lt;code&gt;@&lt;/code&gt; symbol wrong for matching purposes?&lt;/p&gt;
&lt;p&gt;Then I found, via a &lt;a href=&quot;https://chris.bur.gs/sieve-order-fileinto/&quot;&gt;StackOverflow question&lt;/a&gt;, a link to &lt;a href=&quot;https://app.fastmail.com/sievetester/&quot;&gt;Fastmail&#39;s Sieve tester&lt;/a&gt;. That tool appears to be available whether or not you&#39;re logged into Fastmail.&lt;/p&gt;
&lt;p&gt;Using a web-based (or CLI&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://chris.bur.gs/sieve-order-fileinto/#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;&lt;/sup&gt;) tester, I can much more quickly iterate over testing changes than I could if each test required an email delivery (and in this case, a Gitlab issue update). Similar to the difference between a full setup and execution to validate, and testing in a REPL.&lt;/p&gt;
&lt;p&gt;With a hosted web-based tool, I do need to consider confidentiality (eg not submitting client names in my Sieve configuration OR the sample email source) and implementation (Fastmail&#39;s Sieve has a couple of plugins missing relative to my work email).&lt;/p&gt;
&lt;p&gt;With those things in mind I was able to reduce my full Sieve configuration to a version that reproduced the incorrect behaviour - the web interface showing the results of my filter:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;filing message into &#39;INBOX.Projects.ClientName&#39;&amp;quot;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I was then able to test a few things out and quickly identify a fix - a trivial one! Faster test iteration lets us try things out more freely.&lt;/p&gt;
&lt;p&gt;Fastmail now reported the desired outcome:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;filing message into &#39;INBOX.Projects.ClientName&#39; with flags &#39;&#92;seen&#39;&amp;quot;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here&#39;s the solution:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  if header :contains &amp;quot;X-GitLab-Project-Path&amp;quot; [
    &amp;quot;clientname/&amp;quot;
  ] {
    if not body :contains &amp;quot;@chrisburgess&amp;quot; {
      setflag &amp;quot;&#92;&#92;Seen&amp;quot;;
    }
    fileinto &amp;quot;INBOX.Projects.Clientname&amp;quot;;
    stop;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What changed? &lt;strong&gt;I moved the &lt;code&gt;setflag&lt;/code&gt; above the &lt;code&gt;fileinto&lt;/code&gt;&lt;/strong&gt;, establishing that ordering of &lt;code&gt;setflag&lt;/code&gt; and &lt;code&gt;fileinto&lt;/code&gt; is significant; &lt;code&gt;setflag&lt;/code&gt; action applies to subsequent &lt;code&gt;fileinto&lt;/code&gt; actions.&lt;/p&gt;
&lt;p&gt;Now the messages will all be filed in the per-client folder (searchable), but only marked unread if they explicitly @mention me.&lt;/p&gt;
&lt;p&gt;Reviewing the rest of my Sieve configuration for the same fix, I saw that other similar filters had &lt;code&gt;setflag&lt;/code&gt; before &lt;code&gt;fileinto&lt;/code&gt; - which probably contributed to my uneasy sense that this rule configuration had worked in the past, but inconsistently today; I hadn&#39;t twigged to that detail being out of order for this one customer.&lt;/p&gt;
&lt;p&gt;I don&#39;t see this ordering detail mentioned in the published version of Sieve&#39;s &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc5232.html&quot;&gt;RFC5232&lt;/a&gt;, but once I knew what to look for, I saw it was mentioned in an &lt;a href=&quot;https://datatracker.ietf.org/doc/html/draft-melnikov-sieve-imapflags-03#section-3.1&quot;&gt;earlier draft&lt;/a&gt;, section 3.1.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot; /&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I have similar filters for other issue trackers such as Redmine and WRMS. &lt;a href=&quot;https://chris.bur.gs/sieve-order-fileinto/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;This condition depends on whether I&#39;m active on the project; it lets me hear only about updates that someone wants to put on my radar. &lt;a href=&quot;https://chris.bur.gs/sieve-order-fileinto/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I see there&#39;s also &lt;a href=&quot;https://linux.die.net/man/1/sieve-test&quot;&gt;&lt;code&gt;sieve-test&lt;/code&gt;&lt;/a&gt; available as a CLI option. &lt;a href=&quot;https://chris.bur.gs/sieve-order-fileinto/#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  <entry>
    <title>pa11y in Lando</title>
    <link href="https://chris.bur.gs/pa11y-in-lando/"/>
    <updated>2023-06-25T00:00:00Z</updated>
    <id>https://chris.bur.gs/pa11y-in-lando/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Reading &lt;a href=&quot;https://www.morganwebdev.org/posts/standards&quot;&gt;a post by Morgan on web performance and accessibility standards&lt;/a&gt; and I&#39;m reminded of &lt;code&gt;pa11y&lt;/code&gt;, a CLI tool for accessibility checking that we&#39;ve used before.&lt;/p&gt;
&lt;p&gt;Tools can&#39;t match expert assessment, but they can be useful to automate and frequency of checking can be a real boon to raising standards.&lt;/p&gt;
&lt;p&gt;Here&#39;s &lt;code&gt;pa11y&lt;/code&gt; in a Lando configuration - you can add the &lt;em&gt;service&lt;/em&gt; and &lt;em&gt;tooling&lt;/em&gt; entries below to some existing Lando environment, or use this entire config as-is for a standalone tool.&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; pa11y&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;pa11y&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; node&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;overrides&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; registry.gitlab.com/gitlab&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;org/ci&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;cd/accessibility&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;latest&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;tooling&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;pa11y&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; pa11y&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;cmd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; pa11y &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;config /pa11y&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;configs/pa11y.json&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once those are in an environment, you can use &lt;code&gt;pa11y&lt;/code&gt; alongside your project:&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;$ lando pa11y --sitemap https://my.lndo.site/sitemap.xml&lt;br /&gt;$ lando pa11y https://example.org&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This uses the image made available via &lt;a href=&quot;https://gitlab.com/gitlab-org/ci-cd/accessibility&quot;&gt;Gitlab&#39;s Accessibility Testing tools&lt;/a&gt;; you can use their &lt;a href=&quot;https://docs.gitlab.com/ee/ci/testing/accessibility_testing.html#configure-accessibility-testing&quot;&gt;configuration&lt;/a&gt; to test accessibility in your Gitlab CI pipelines too.&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;chris@thip:~/Projects/lando-pa11y$ lando pa11y https://example.org --reporter cli&lt;br /&gt;&lt;br /&gt;Welcome to Pa11y&lt;br /&gt;&lt;br /&gt; &gt; Running Pa11y on URL https://example.org&lt;br /&gt;&lt;br /&gt;Results for URL: https://example.org/&lt;br /&gt;&lt;br /&gt; • Error: The html element should have a lang or xml:lang attribute which describes the language of the document.&lt;br /&gt;   ├── WCAG2AA.Principle3.Guideline3_1.3_1_1.H57.2&lt;br /&gt;   ├── html&lt;br /&gt;   └── &lt;html&gt;&lt;head&gt; &lt;title&gt;Example Domai...&lt;/html&gt;&lt;br&gt;&lt;br&gt;1 Errors&lt;/code&gt;&lt;/pre&gt;
&lt;/title&gt;&lt;/head&gt;&lt;/html&gt;&lt;/code&gt;&lt;/pre&gt;</content>
  </entry>
  <entry>
    <title>Lighthousing</title>
    <link href="https://chris.bur.gs/lighthouse/"/>
    <updated>2023-06-25T00:00:00Z</updated>
    <id>https://chris.bur.gs/lighthouse/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I&#39;ve just gotten around to running some Lighthouse reports over this site for the first time. It &lt;a href=&quot;https://www.webpagetest.org/result/230624_BiDcDF_8XA/&quot;&gt;scored at 93, 96, 100, 100, 22&lt;/a&gt;, which is interesting. There are a couple of improvements I can make, and a bunch of suggestions that won&#39;t make it to prioritisation.&lt;/p&gt;
&lt;p&gt;That is an OK first result, but I&#39;m not taking particular pride in something I&#39;ve done very little to achieve. The credit for the coffee I&#39;m drinking at home right now goes to those who grew, prepared, shipped and roasted the beans. Likewise, for this site I selected some ingredients (markdown, Eleventy, Tailwind) which made sense for my needs, and I&#39;ve added as little technical extra as I could get away with.&lt;/p&gt;
&lt;p&gt;For me this is the right approach. This is a tool to share some thoughts, and the tooling is here to enable me and then stay out of my way. I particularly don&#39;t want a high maintenance burden looking after it. So if anything, in that 93 score I can take a little credit for picking tooling that&#39;s delivered decent results out of the box.&lt;/p&gt;
&lt;p&gt;The 22 is the PWA ranking in Lighthouse, and something which highlights criteria which make sense for a limited range of sites. It&#39;s a useful reminder that Lighthouse metrics definitely aren&#39;t to be pursued just to get &amp;quot;all green&amp;quot; ratings. Few of those checks are for things which would add meaningful value to this site.&lt;/p&gt;
&lt;p&gt;On the other hand, there are a couple of improvements identified in the first test which I could knock off pretty easily, and did so.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Seeing that loading the PrismJS libraries from a CDN was the biggest single wait time nudged me to finally fix code formatting here. Until now I&#39;d shrugged and used Github Gist embeds - also fine. Fixed.&lt;/li&gt;
&lt;li&gt;One of the accessibility checks flagged that I had not correctly set the language metadata in the root HTML tag. Fixed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Result: &lt;a href=&quot;https://www.webpagetest.org/result/230625_BiDcWN_61D/&quot;&gt;95, 100, 100, 100, 22&lt;/a&gt;, and more importantly two minor annoyances fixed. For this content page now, a slightly higher rating - &lt;a href=&quot;https://www.webpagetest.org/result/230625_BiDcXP_61Z/&quot;&gt;99, 100, 100, 100, 22&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thanks Lighthouse, that was helpful after all!&lt;/p&gt;
&lt;p&gt;And not from the numbers but the screenshot, a nudge for me to sort out the site title and menu which overlap at present. Not fixed &lt;em&gt;yet&lt;/em&gt;.&lt;/p&gt;
&lt;img src=&quot;https://chris.bur.gs/media/lighthouse-screenshot.jpeg&quot; alt=&quot;Screenshot of this page on mobile - how meta&quot; style=&quot;max-width: 250px&quot; /&gt;
</content>
  </entry>
  <entry>
    <title>XDebug, Lando and a CLI service</title>
    <link href="https://chris.bur.gs/lando-phpstorm-xdebug/"/>
    <updated>2023-06-10T00:00:00Z</updated>
    <id>https://chris.bur.gs/lando-phpstorm-xdebug/</id>
    <content xml:lang="en" type="html">&lt;ul&gt;
&lt;li&gt;I use &lt;a href=&quot;https://lando.dev/&quot;&gt;Lando&lt;/a&gt; to simplify management of my local development environments.&lt;/li&gt;
&lt;li&gt;I use PHPStorm to edit, comprehend and debug complex PHP codebases.&lt;/li&gt;
&lt;li&gt;I use XDebug as a debug tool&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you&#39;ve not discovered how helpful XDebug can be when debugging PHP, I strongly recommend you give it a try. If you don&#39;t code in PHP, find the equivalent tooling!&lt;/p&gt;
&lt;p&gt;The initial setup for XDebug can be complex as a number of things have to be aligned to function correctly. A challenge for me is that when I try to configure it I&#39;ve typically just run into a gnarly problem on a project; once I get it running my monkey brain turns straight back to debugging and forgets to record what steps I took to configure the debugger. Over time I&#39;d collected a scattered set of &amp;quot;today, x worked&amp;quot; notes in my lab journal. This Saturday morning, a person asked in the Lando Slack asked how to get it running, and I figured I&#39;d document a few details that often catch me out.&lt;/p&gt;
&lt;h2&gt;How does XDebug work?&lt;/h2&gt;
&lt;p&gt;A quick outline works as I understand it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When PHP starts executing something, if the XDebug extension is loaded then it can be activated&lt;/li&gt;
&lt;li&gt;XDebug and a debug client (your IDE, etc) may then communicate&lt;/li&gt;
&lt;li&gt;XDebug will connect to the configured IDE via IP and port&lt;/li&gt;
&lt;li&gt;The IDE will decide whether to handle this request&lt;/li&gt;
&lt;li&gt;The IDE can tell XDebug about breakpoints in code&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Docs&lt;/h2&gt;
&lt;p&gt;First, the proper docs, which you should refer to before my own notes.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://xdebug.org/docs/&quot;&gt;https://xdebug.org/docs/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.lando.dev/guides/lando-phpstorm.html&quot;&gt;https://docs.lando.dev/guides/lando-phpstorm.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.jetbrains.com/help/phpstorm/profiling-with-xdebug.html&quot;&gt;https://www.jetbrains.com/help/phpstorm/profiling-with-xdebug.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;My notes&lt;/h2&gt;
&lt;h3&gt;Check XDebug module is enabled&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;lando ssh&lt;/code&gt; then &lt;code&gt;php -i | grep -i xdebug&lt;/code&gt; - you&#39;ll see XDebug&#39;s configuration from PHP config here&lt;/p&gt;
&lt;h3&gt;Environment variables Lando sets&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Among other variables, Lando sets these when XDebug is enabled for a service.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;XDEBUG_MODE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;XDEBUG_CONFIG&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PHP_IDE_CONFIG&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PHP_IDE_KEY&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Check environment in PHP service&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;lando ssh&lt;/code&gt; then &lt;code&gt;env | grep XDEBUG&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Check &lt;code&gt;XDEBUG_MODE&lt;/code&gt; and &lt;code&gt;XDEBUG_CONFIG&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Config to check&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;client_host&lt;/code&gt; - should point at the IP of the host the IDE is running on&lt;/li&gt;
&lt;li&gt;&lt;code&gt;discover_client_host&lt;/code&gt; - can use HTTP headers to discover the IP of the browser and connect to IDE on that IP address&lt;/li&gt;
&lt;li&gt;&lt;code&gt;idekey&lt;/code&gt; is often &#39;phpstorm&#39; but may only need to be set?&lt;/li&gt;
&lt;li&gt;&lt;code&gt;serverName&lt;/code&gt; is the XDebug server name passed to PHPStorm&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Config to check - IDE&lt;/h3&gt;
&lt;p&gt;XDebug will pass the file path and other execution information to the IDE. The IDE needs to know which paths correspond from your IDE OS and the environment it&#39;s running in. PHPStorm has a settings screen (Settings &amp;gt; PHP &amp;gt; Servers) where you can map the project&#39;s files to the path as seen within the environment XDebug runs in. Here I&#39;ve set the project root to map to &lt;code&gt;/app&lt;/code&gt; in Lando.&lt;br /&gt;
![[attachments/Pasted image 20230610152023.png]]&lt;/p&gt;
&lt;h3&gt;XDebug on a CLI only Lando&lt;/h3&gt;
&lt;h3&gt;Lando service configuration&lt;/h3&gt;
&lt;p&gt;If PHP is running on another service than &amp;quot;appserver&amp;quot;, the global Lando configuration option may not be applied. (I think this is &lt;a href=&quot;https://github.com/lando/core/blob/a114d17c2a6817340bff5d1e3345da87215a9258/plugins/lando-recipes/types/laemp/builder.js#L83-L111&quot;&gt;here&lt;/a&gt;.) If you want XDebug on a service not named &amp;quot;appserver&amp;quot;, the global &lt;code&gt;xdebug&lt;/code&gt; option won&#39;t apply and you should set &lt;code&gt;xdebug:&lt;/code&gt; on the service instead.&lt;/p&gt;
&lt;h3&gt;Lando restart after switching networks&lt;/h3&gt;
&lt;p&gt;Lando&#39;s &lt;code&gt;XDEBUG_CONFIG&lt;/code&gt; variable won&#39;t update if your laptop switches IPs until you restart. This can mean that XDebug stops working after you move locations. &lt;code&gt;lando restart -y&lt;/code&gt; should refresh the IP of the Lando service you want to debug.&lt;/p&gt;
&lt;h3&gt;More links&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://gist.github.com/quentint/6331aa9a75313ed955b2ea20d33557af&quot;&gt;https://gist.github.com/quentint/6331aa9a75313ed955b2ea20d33557af&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gist.github.com/xurizaemon/4b30f525b555c4c2399cac7bdfe08ad5&quot;&gt;https://gist.github.com/xurizaemon/4b30f525b555c4c2399cac7bdfe08ad5&lt;/a&gt; is a CLI Lando config with xdebug enabled&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>PatchWatch progress report</title>
    <link href="https://chris.bur.gs/patchwatch-progress/"/>
    <updated>2023-05-29T00:00:00Z</updated>
    <id>https://chris.bur.gs/patchwatch-progress/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Thursday before last at DrupalSouth 2023 codesprint I knocked together a prototype of &lt;a href=&quot;https://gitlab.com/xurizaemon/patchwatch&quot;&gt;PatchWatch&lt;/a&gt;, a tool to consolidate information about patches applied to a set of projects. [Earlier post on patchwatch here]((&lt;a href=&quot;https://chris.bur.gs/composer-drupal-patches-report/&quot;&gt;https://chris.bur.gs/composer-drupal-patches-report/&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;This last weekend, I revisited the prototype and fleshed it out some.&lt;/p&gt;
&lt;p&gt;The tool now is a Symfony console command with three or four commands implemented:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;patchwatch:report-composer-patches&lt;/code&gt; reports on cweagans/composer-patches patches from a &lt;code&gt;composer.json&lt;/code&gt; or &lt;code&gt;patches-file&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;patchwatch:report-drush-make&lt;/code&gt; reports on patches from an old style &lt;code&gt;drush.make&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;&lt;code&gt;patchwatch:report-patch-directory&lt;/code&gt; reports on a directory of patches&lt;/li&gt;
&lt;li&gt;&lt;code&gt;patchwatch:report-patch-package&lt;/code&gt; reports on a directory of patches in &lt;a href=&quot;https://chris.bur.gs/patchwatch-progress/&quot;&gt;patch-package&lt;/a&gt; format&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It has some test coverage in &lt;code&gt;tests/src/&lt;/code&gt; which parses sample input data from &lt;code&gt;tests/fixtures/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I also added a caching layer in the Guzzle client, so we don&#39;t annoy HTTP endpoints too much while querying for data about issues related to patches.&lt;/p&gt;
&lt;p&gt;The core function of &amp;quot;read patch input data and output something structured&amp;quot; works, now. The tool does not yet collate several sources into a single &amp;quot;high level&amp;quot; view, but that should be straightforward to add. I want to spend a bit of time reflecting on the structure of the patch listings before I do that.&lt;/p&gt;
&lt;p&gt;I&#39;ve added some Gitlab CI and this includes a &amp;quot;demo&amp;quot; job which shows the output of the commands when using the test fixture samples as inputs. See the &lt;code&gt;demo&lt;/code&gt; job in &lt;a href=&quot;https://gitlab.com/xurizaemon/patchwatch/-/pipelines/main/latest&quot;&gt;the latest pipeline&lt;/a&gt; for this.&lt;/p&gt;
&lt;p&gt;I also had a play with taking the generated output and throwing it at a store ... having heard a bit of buzz about Metabase from some team members lately, I threw the generated JSON at MongoDB and then yoinked that into Metabase, which was interesting. See &lt;a href=&quot;https://chris.bur.gs/lando-metabase/&quot;&gt;Lando + Metabase&lt;/a&gt; for some notes on that!&lt;/p&gt;
&lt;h2&gt;Next steps&lt;/h2&gt;
&lt;h3&gt;Configurable patterns&lt;/h3&gt;
&lt;p&gt;I am thinking about how to take a listing of patches (which will have some basic data such as a patch URL/path and possibly a label), and &amp;quot;enrich&amp;quot; the list of patches into something more full featured. Since different organisations will treat this differently, there is probably room for configurable pattern matching, eg to help an org discern that &lt;code&gt;#123: Bugfix for frobnitz adapter&lt;/code&gt;. It may also make sense here to specify that there&#39;s no default behaviour and that all references should be canonical (&lt;code&gt;drupal/drupal#123&lt;/code&gt;) ... but flexibility here would make adoption easier.&lt;/p&gt;
&lt;h3&gt;Plugins for informing patch data&lt;/h3&gt;
&lt;p&gt;The other thing that&#39;s on my mind is in the Patch reader where we apply &amp;quot;informers&amp;quot; to the source data. At this point we might want to pass the patch data (an array with a filename) into a set of transformers which might &amp;quot;enhance&amp;quot; the data (&amp;quot;I recognise an issue number in that filename pattern; I will add to the patch data information retrieved from an upstream tracker.&amp;quot;). There&#39;s only one such plugin at present (for Drupal issues) and I want to work out the right pattern for making this easier to adapt, and possibly allow for user-specific plugins as well.&lt;/p&gt;
&lt;h3&gt;Support for forks as well&lt;/h3&gt;
&lt;p&gt;Later I plan to add support for forked repositories as well. Initially I was taking the opinionated position that forks are a worse strategy, and this tool should focus on patch handling. Now that I&#39;ve implemented a few source adapters, I realise that implementing Git support wouldn&#39;t be &lt;em&gt;that&lt;/em&gt; hard, and it would make the tool useful to folks who have a local fork of an upstream project.&lt;/p&gt;
&lt;p&gt;(I still recommend to not adopt the strategy of long-lived forks, but it&#39;s a thing people do, and we can allow for that.)&lt;/p&gt;
&lt;h2&gt;Credits&lt;/h2&gt;
&lt;p&gt;Thanks to the authors of the following open source tools which I&#39;ve been reading and considering as I implement this. I&#39;m grateful to be building on what you&#39;ve shared.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://symfony.com/doc/current/components/console.html&quot;&gt;Symfony Console&lt;/a&gt; and &lt;a href=&quot;https://docs.guzzlephp.org/en/stable/&quot;&gt;Guzzle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/cweagans/composer-patches/&quot;&gt;cweagans/composer-patches&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/mglaman/drupalorg-cli&quot;&gt;mglaman/drupalorg-cli&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/davidrjonas/composer-lock-diff&quot;&gt;davidrjonas/composer-lock-diff&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/drutiny/drutiny&quot;&gt;drutiny/drutiny&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ds300/patch-package&quot;&gt;patch-package&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Lando + Metabase</title>
    <link href="https://chris.bur.gs/lando-metabase/"/>
    <updated>2023-05-29T00:00:00Z</updated>
    <id>https://chris.bur.gs/lando-metabase/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Having heard buzz on it from colleagues, I wanted to give Metabase a quick try. I knocked together a quick &lt;code&gt;.lando.yml&lt;/code&gt; to test it out. I&#39;m impressed. Here&#39;s the &lt;code&gt;.lando.yml&lt;/code&gt; I used:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; metabase&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;proxy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;metabase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; metabase.lndo.site&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3000&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;metabase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; compose&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;app_mount&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;false&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; metabase/metabase&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;3000&#39;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token key atrule&quot;&gt;MB_DB_TYPE&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; postgres&lt;br /&gt;        &lt;span class=&quot;token key atrule&quot;&gt;MB_DB_DBNAME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; metabase&lt;br /&gt;        &lt;span class=&quot;token key atrule&quot;&gt;MB_DB_PORT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;5432&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token key atrule&quot;&gt;MB_DB_USER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; postgres&lt;br /&gt;        &lt;span class=&quot;token key atrule&quot;&gt;MB_DB_PASS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; postgres&lt;br /&gt;        &lt;span class=&quot;token key atrule&quot;&gt;MB_DB_HOST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; database&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;database&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; postgres&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;creds&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;database&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; metabase&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This brought up a Metabase service at &lt;code&gt;http://metabase.lndo.site&lt;/code&gt; for me. I expected to be able to load data directly into Metabase, but I found you can mostly connect Metabase to external sources. So I added a MongoDB service to the above, and threw my JSON into that, and connected Metabase to the MongoDB store.&lt;/p&gt;
&lt;p&gt;So - my first misconception I think was that Metabase was a store of data. From my initial look, this seems to be wrong - while Metabase does store data internally (ok, &lt;em&gt;metadata&lt;/em&gt; I guess), it seems to want to give you a view of &lt;em&gt;external&lt;/em&gt; data. Which may or may not have been yanked into Metabase to support your insights.&lt;/p&gt;
&lt;p&gt;Then I realised I had other data sources I could wire into Metabase. I had a local Commerce site running in Lando, and expected that Metabase from Lando would be able to see the DB from that commerce site via Lando&#39;s &lt;code&gt;.internal&lt;/code&gt; network hosts. I ran &lt;code&gt;lando info&lt;/code&gt; in the commerce project, snagged the DB creds, and fed those into Metabase; it indexed the data and fed back a kind of &amp;quot;Facebook memories&amp;quot; of my commerce DB.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/metabase-facebook-memories-of-commerce.png&quot; alt=&quot;Facebook memories for Drupal Commerce&quot; /&gt;&lt;/p&gt;
&lt;p&gt;And some WIP visibility on a set of patches from &lt;a href=&quot;https://gitlab.com/xurizaemon/patchwatch&quot;&gt;Patchwatch&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/patchwatch-metabase.png&quot; alt=&quot;Some patches&quot; /&gt;&lt;/p&gt;
&lt;p&gt;All in all, a decent first experience for trying it out. Looking forward to learning more.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Fixing the Element notification sound</title>
    <link href="https://chris.bur.gs/custom-element-notification-sounds/"/>
    <updated>2023-05-25T07:03:00Z</updated>
    <id>https://chris.bur.gs/custom-element-notification-sounds/</id>
    <content xml:lang="en" type="html">&lt;aside&gt;NB: I didn&#39;t solve this, I copied it from &lt;a href=&quot;https://github.com/vector-im/element-web/issues/2105#issuecomment-998217391&quot;&gt;vector-im/element-web#2105&lt;/a&gt; and documented it a bit more, because it&#39;s a good process that is worth knowing about. Credit to &lt;a href=&quot;https://github.com/Half-Shot&quot;&gt;Will Hunt aka @half-shot&lt;/a&gt;.&lt;/aside&gt;
&lt;p&gt;If you use Element you might have noticed that the default notification sound is ... jarring? janky? It&#39;s loud, and it&#39;s sharp, and it&#39;s honestly kind of agitating to me.&lt;/p&gt;
&lt;p&gt;&lt;audio controls=&quot;&quot; src=&quot;https://chris.bur.gs/media/matrix-message.mp3&quot;&gt;&lt;a href=&quot;https://chris.bur.gs/media/matrix-message.mp3&quot;&gt;Matrix message sound&lt;/a&gt;&lt;/audio&gt;&lt;/p&gt;
&lt;h2&gt;First, a restful tone&lt;/h2&gt;
&lt;p&gt;I mean, you can use anything you like. Bon Jovi. Go wild. Me, I wanted something restful to hear during the day - these are challenging times.&lt;/p&gt;
&lt;p&gt;Like the &lt;a href=&quot;https://www.soundboard.com/sb/Tokyo_Train_Ringtones&quot;&gt;Tokyo Train Dream Sound set, created by Zeethrogise&lt;/a&gt;, originally released circa 2004 as &lt;a href=&quot;https://www.adiumxtras.com/index.php?a=xtras&amp;amp;xtra_id=602&quot;&gt;an Adium soundset&lt;/a&gt;. I picked &lt;code&gt;CSnd.mp3&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;audio autoplay=&quot;true&quot; controls=&quot;&quot; src=&quot;https://chris.bur.gs/media/TokyoTrainDreamRingtones-CSnd.mp3&quot;&gt;&lt;a href=&quot;https://chris.bur.gs/media/TokyoTrainDreamRingtones-CSnd.mp3&quot;&gt;Matrix message sound&lt;/a&gt;&lt;/audio&gt;&lt;/p&gt;
&lt;h2&gt;Get a room&lt;/h2&gt;
&lt;p&gt;Element doesn&#39;t let you set a global notification via the UI yet (&lt;a href=&quot;https://github.com/vector-im/element-web/issues/20286&quot;&gt;vector-im/element-web#20286:  Add interface for setting a global notififcation sound&lt;/a&gt;). We need to assign the sound to a single room, then apply that sound to the global configuration.&lt;/p&gt;
&lt;p&gt;Pick any room you&#39;re in. I picked a PM I have with a bot I run.&lt;/p&gt;
&lt;p&gt;In the room, locate the &amp;quot;Room Info&amp;quot; button (top right corner for me), and then select &amp;quot;Notifications&amp;quot;. Upload your sound here.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://chris.bur.gs/media/room-settings-notifications-custom-sound.png&quot; alt=&quot;Room settings &amp;gt; Notifications &amp;gt; Custom sound&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Now:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Say &lt;code&gt;/devtools&lt;/code&gt; in that room&lt;/li&gt;
&lt;li&gt;Click &amp;quot;Explore Account Data&amp;quot;&lt;/li&gt;
&lt;li&gt;Click the &amp;quot;Account Data&amp;quot; button&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;im.vector.web.settings&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Copy the &lt;code&gt;notificationSound&lt;/code&gt; block, e.g.&lt;pre&gt;&lt;code&gt;&amp;quot;notificationSound&amp;quot;: {
    &amp;quot;name&amp;quot;: &amp;quot;Tokyo Train Dream Ringtones - CSnd.mp3&amp;quot;,
    &amp;quot;type&amp;quot;: &amp;quot;audio/mpeg&amp;quot;,
    &amp;quot;size&amp;quot;: 18560,
    &amp;quot;url&amp;quot;: &amp;quot;mxc://example.org/aPLnafWKcouYthRwKbHSqNHC&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Click &amp;quot;Back&amp;quot;&lt;/li&gt;
&lt;li&gt;Click &amp;quot;Room Data&amp;quot;&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;im.vector.web.settings&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Click &amp;quot;Edit&amp;quot;&lt;/li&gt;
&lt;li&gt;Insert the copied json object into this object somewhere, ensuring that the result is valid JSON. (Each entry should be followed by a comma, except the last one.)&lt;/li&gt;
&lt;li&gt;Click &amp;quot;Send&amp;quot;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Sounds should now change globally. To test, you&#39;ll need someone to message you (only, in a room which you&#39;re not watching).&lt;/p&gt;
&lt;p&gt;Enjoy! This small change may vastly improve my Matrix experience.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>What patches are we running?</title>
    <link href="https://chris.bur.gs/composer-drupal-patches-report/"/>
    <updated>2023-05-19T00:00:00Z</updated>
    <id>https://chris.bur.gs/composer-drupal-patches-report/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I&#39;m at a code sprint with members of the Drupal community today, and we&#39;re looking for tasks to work on. The agency company I work for manages Drupal sites for a number of clients, and across those sites we might apply patches for the same issues to various projects.&lt;/p&gt;
&lt;p&gt;Patches are applied using &lt;a href=&quot;https://github.com/cweagans/composer-patches&quot;&gt;cweagans/composer-patches&lt;/a&gt; and defined per-project using an entry in a composer patches file.&lt;/p&gt;
&lt;p&gt;By scanning these files and looking at the related issues, we can get a high-level view of the patches in play, and start to see some interesting things.&lt;/p&gt;
&lt;p&gt;My goal for this is to build a Composer plugin (&lt;a href=&quot;https://gitlab.com/xurizaemon/patchwatch&quot;&gt;xurizaemon/patchwatch&lt;/a&gt;) which can be used as a command from the project&#39;s CI, but for today (and mostly due to network issues while travelling) I shot for a quick and dirty PHP script which gave me some serviceable output.&lt;/p&gt;
&lt;p&gt;Here&#39;s some excerpted and redacted output, to show what we can see afterwards. This is a pretty crude PoC.&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/xurizaemon/d8f8866b19826c6b1cd80aa40a9eeda6.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;And here&#39;s &lt;a href=&quot;https://gist.github.com/xurizaemon/4b1539fd2bbf570bd8149a933ad8fcbe&quot;&gt;the quick script&lt;/a&gt;, somewhat munged to hopefully share my rough initial script in a single executable &lt;code&gt;.php&lt;/code&gt;. The repository for this project will be &lt;a href=&quot;https://gitlab.com/xurizaemon/patchwatch&quot;&gt;https://gitlab.com/xurizaemon/patchwatch&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;(I haven&#39;t embedded the output and script here because I apparently haven&#39;t sorted out code embeds on my own blog yet ... sorry!)&lt;/p&gt;
&lt;p&gt;The interesting things we can start to see from the output above are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Some issues or patches are indeed common to multiple projects, and good candidates to focus on to reduce patch debt!&lt;/li&gt;
&lt;li&gt;Some projects have diverged patches, which points to a maintenance task for those sites. (They may not have matching versions of core, or the differing patches may be correct - but it&#39;s worth checking.)&lt;/li&gt;
&lt;li&gt;I included issue status somewhat on a whim, but this turned out really interesting. We see:
&lt;ul&gt;
&lt;li&gt;Patches for issues marked &amp;quot;Closed (fixed)&amp;quot;, which suggests to check if there&#39;s an option to update the component and drop the patch.&lt;/li&gt;
&lt;li&gt;Patches for issues marked &amp;quot;Closed (won&#39;t fix)&amp;quot;, which suggests that we should review our own use of the patch (and then perhaps revisit our issue communication). This may point to a future conflict between our project with patch, and the module&#39;s development direction.&lt;/li&gt;
&lt;li&gt;Patches for issues marked &amp;quot;Closed (can&#39;t reproduce)&amp;quot;, where we can probably contribute by supplying instructions to reproduce (because we &lt;em&gt;definitely&lt;/em&gt; recorded why we introduced this patch, right ...).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Next steps are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Consider patching for other projects, eg I recently learned of &lt;a href=&quot;https://www.npmjs.com/package/patch-package&quot;&gt;Node&#39;s &lt;code&gt;patch-package&lt;/code&gt;&lt;/a&gt; from the Mahara project ...&lt;/li&gt;
&lt;li&gt;Implement this suitably for CI, possibly as a Composer plugin (with close integration to &lt;code&gt;cweagans/composer-patches&lt;/code&gt;), or possibly with capacity to consider other build/patch solutions and sources (this version is Drupal-specific and ignores patches to components which don&#39;t have a &lt;code&gt;drupal/&lt;/code&gt; prefix).&lt;/li&gt;
&lt;li&gt;Integrate with systems for managing internal knowledge of which patches are significant to our organisation, so we have visibility for our team to identify issues of interest and can reduce duplicated effort&lt;/li&gt;
&lt;li&gt;Consider whether this tool could be generally useful to capture metrics of patch consumption eg for &lt;a href=&quot;http://drupal.org/&quot;&gt;Drupal.org&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Consider whether the output of this tool is indicating opportunity for us to contribute in other ways, eg by funding the maintainers of modules which we have lots of patches for, or by stepping up to maintain ourselves, ... or reconsidering use of modules which aren&#39;t getting patches landed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Something which might be interesting - if &lt;a href=&quot;http://drupal.org/&quot;&gt;Drupal.org&lt;/a&gt; wanted to surface it - would be the download stats / popularity of patch URLs. This might indicate patches which are &amp;quot;popular&amp;quot; and being applied by lots of sites, and good candidates for review.&lt;/p&gt;
&lt;p&gt;If this looks useful, or you know of a similar approach to this which I don&#39;t or are interested to explore further - get in touch! I&#39;d love to hear from you.&lt;/p&gt;
&lt;p&gt;Update: &lt;a href=&quot;https://chris.bur.gs/patchwatch-progress/&quot;&gt;progress report here&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Upgrading from Drupal 6 or 7 to 9</title>
    <link href="https://chris.bur.gs/upgrade-drupal-7-to-10/"/>
    <updated>2023-05-09T00:00:00Z</updated>
    <id>https://chris.bur.gs/upgrade-drupal-7-to-10/</id>
    <content xml:lang="en" type="html">&lt;p&gt;There is an auto-discovery process built in, using the Migrate Drupal module in core. It will generate an initial set of migrations for you, for both site configuration (eg content types, fields) and content (the nodes themselves, and friends).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.drupal.org/docs/upgrading-drupal&quot;&gt;https://www.drupal.org/docs/upgrading-drupal&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can use that to generate a pretty good starting point - and you can customise from there too.&lt;/p&gt;
&lt;p&gt;It&#39;s really important IMO to consider this as development of a &lt;em&gt;process to migrate sites&lt;/em&gt;, rather than development of a migrated site. Be comfortable with the idea of running the migration repeatedly while developing.&lt;/p&gt;
&lt;p&gt;With Migrate Plus module, you can take those migrations generated by Migrate Drupal and fine tune them. Core&#39;s auto-upgrade handles field types that are in core, but you might need to adjust where the Drupal 7 module needs more customisation.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>From Jekyll &amp; Hugo to Eleventy</title>
    <link href="https://chris.bur.gs/jekyll-to-eleventy/"/>
    <updated>2023-03-06T00:00:00Z</updated>
    <id>https://chris.bur.gs/jekyll-to-eleventy/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Well, I seem to have made it through a Jekyll and Hugo to Eleventy conversion, and come out the other side with a set of posts going back a decade. Hail plain text formats!&lt;/p&gt;
&lt;p&gt;Eleventy has been an accessible (for me) platform to do this. What have I had to tackle?&lt;/p&gt;
&lt;h2&gt;A variety of sources&lt;/h2&gt;
&lt;p&gt;The posts collated together here cover a few years of a &amp;quot;personal engineering&amp;quot; Hugo blog I&#39;ve kept internally at Catalyst, and about as many years of Jekyll blog I&#39;d stopped updating when I joined Catalyst. Two similar (markdown-based static site generator) systems with subtly different data sets. The frontmatter for both engines was different, and I did a little juggling with perl find and replace to rearrange things. I had both &lt;em&gt;categories&lt;/em&gt; from Jekyll and &lt;em&gt;tags&lt;/em&gt; from Hugo, for example.&lt;/p&gt;
&lt;p&gt;It could be tempting to perform a &amp;quot;migration&amp;quot; from Hugo and Jekyll to Eleventy, but one lovely aspect of Eleventy here was that it was flexible and fast enough for me to fix things &lt;em&gt;on the fly&lt;/em&gt;. So I didn&#39;t need to do that.&lt;/p&gt;
&lt;h2&gt;Tags&lt;/h2&gt;
&lt;p&gt;Eleventy uses tags differently, but in a way which works for me. Specifically, all posts are members of the &amp;quot;post&amp;quot; collection, via a &lt;code&gt;src/posts/posts.json&lt;/code&gt; data file which sets that tag on the contents of the directory. When displaying tags for an article, that means I filter out the tag &amp;quot;post&amp;quot;, such as at the top of this page.&lt;/p&gt;
&lt;p&gt;On the front page of the site, I surfaced the tags for each post - this was mostly a lazy way for me to visually check if any posts were missing tags. (They are. I&#39;ll get to them.)&lt;/p&gt;
&lt;h2&gt;Dates&lt;/h2&gt;
&lt;p&gt;For a long time on at least one of these blogs, I was hand-entering something like ISO dates. And sometimes got them wrong, cos I forget small things. So I wrote a couple lines of filter to handle most any date that the posts would throw Eleventy&#39;s way.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;postDate&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;dateInput&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; dateObj &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; DateTime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fromJSDate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dateInput&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dateObj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;invalid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    dateObj &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; DateTime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fromISO&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dateInput&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dateObj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;invalid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    dateObj &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; DateTime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fromFormat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dateInput&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; dateObj&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toLocaleString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;DateTime&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;DATE_MED&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Syntax highlighting&lt;/h2&gt;
&lt;p&gt;Eleventy had PrismJS built in and that worked well once set up, but I had to nudge a few formats about - in my Hugo blog I&#39;d used &amp;quot;cron&amp;quot; format which PrismJS didn&#39;t like, and so on. So, a little content mop-up required.&lt;/p&gt;
&lt;p&gt;This isn&#39;t quite working right yet - I&#39;m seeing things highlighted but combined into a single line on deployment. TBD.&lt;/p&gt;
&lt;h2&gt;Tailwind CSS&lt;/h2&gt;
&lt;p&gt;I tried Tailwind CSS, and I&#39;ve decided it&#39;s perfectly good. I wasn&#39;t sure at first. It&#39;s a whole vocabulary I&#39;d need to either pick up or look up to operate with, which is something. But it&#39;s produced a tidy, painless layout which I&#39;d otherwise have probably spent ages getting approximately right.&lt;/p&gt;
&lt;p&gt;I&#39;m using &lt;a href=&quot;https://www.npmjs.com/package/eleventy-plugin-tailwindcss&quot;&gt;eleventy-plugin-tailwindcss&lt;/a&gt; which seems to work great.&lt;/p&gt;
&lt;h2&gt;Drafts&lt;/h2&gt;
&lt;p&gt;I had posts with &lt;code&gt;draft: true&lt;/code&gt; set, and didn&#39;t want to drop them all on the world. I used &lt;a href=&quot;https://www.npmjs.com/package/eleventy-plugin-ignore&quot;&gt;eleventy-plugin-ignore&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Permalinks&lt;/h2&gt;
&lt;p&gt;My previous Jekyll blog had dates in the filename (&lt;code&gt;posts/2023-03-06-jekyll-to-eleventy&lt;/code&gt;) but not in the paths (&lt;code&gt;/jekyll-to-eleventy&lt;/code&gt;). I didn&#39;t want to have to handle the redirects, so I used Eleventy&#39;s per-directory JSON to rewrite paths.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;.eleventy.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addFilter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;stripDatePrefix&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; str&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;replace&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;^[0-9-]+&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In &lt;code&gt;src/posts/posts.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property&quot;&gt;&quot;permalink&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/{{ page.fileSlug | stripDatePrefix }}/&quot;&lt;/span&gt; &lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Gists&lt;/h2&gt;
&lt;p&gt;I&#39;d previously used a &lt;code&gt;{% gist &amp;lt;url&amp;gt; %}&lt;/code&gt; and needed to convert this from Jekyll.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;eleventyConfig&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addShortcode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;gist&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;lt;script src=&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;url&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;.js&quot;&gt;&amp;lt;/script&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Large media and hosting&lt;/h2&gt;
&lt;p&gt;I&#39;m already deploying this to Gitlab Pages, easy. However ... I want to have a slightly more flexible and funcitional hosting environment. So I&#39;ll wire up a deploy over SSH/rsync from Gitlab CI.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Commit credit carry-on</title>
    <link href="https://chris.bur.gs/commit-credit-carry-on/"/>
    <updated>2023-03-05T00:00:00Z</updated>
    <id>https://chris.bur.gs/commit-credit-carry-on/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Drupal was an early proponent of crediting community contribution visibly. Today the community faces an interesting consequence of this policy - a large number of (perceived to be) low-value contributions are being submitted through the issue queues, with the apparent aim of increasing promotion via the &lt;a href=&quot;https://www.drupal.org/drupalorg/contribution-credit&quot;&gt;Drupal.org contribution recognition systems&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Organisations which contribute to Drupal are recognised and have placement benefits on the &lt;a href=&quot;http://drupal.org/&quot;&gt;Drupal.org&lt;/a&gt; website. This creates a motivation to earn commit credits, which some feel has recently resulted in submissions which are of low value (eg &amp;quot;Rename README.txt to &lt;a href=&quot;http://readme.md/&quot;&gt;README.md&lt;/a&gt;&amp;quot;, or unnecessary patch re-rolls), which in turn &lt;em&gt;increases&lt;/em&gt; maintainer workload in dealing with the influx of submissions.&lt;/p&gt;
&lt;p&gt;Bad patch re-rolls and poor review process have the risk of introducing bugs and all increased workload has a human cost in maintainer workload. The increased effort can only be worthwhile if it&#39;s appropriately focused, or the impact to the wider community will be negative.&lt;/p&gt;
&lt;p&gt;It&#39;s worth noting that the organisational recognition system through commit credits is a form of hybrid, source-unavailable leaderboard - agencies can sponsor &lt;a href=&quot;http://drupal.org/&quot;&gt;Drupal.org&lt;/a&gt; to a certain tier, then I think have their promotional placement increased by achieving a given number of contribution recognitions. Leaderboards can be problematic, and because they encode both community and implementation decisions, are gamable interfaces. Much to say here about metrics too, and we&#39;ve &lt;a href=&quot;https://www.lullabot.com/articles/how-we-compare-leaderboards-and-related-comparison-metrics-drupal-community&quot;&gt;faced leaderboards before&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I have appreciated that Drupal&#39;s contribution system was flexible enough for people to use in innovative ways, eg how &lt;a href=&quot;https://chris.bur.gs/commit-credit-carry-on/&quot;&gt;Talking Drupal&lt;/a&gt; and several working groups use d.o issues for crediting those involved in non-code labour. That&#39;s cool. The credit system also can&#39;t ever perfectly reflect contribution valuing, because a community is complicated. As a community, I think we need to build a solid understanding of what commits are most valuable and effective.&lt;/p&gt;
&lt;p&gt;One response I&#39;ve found helpful in response to community behaviour I find challenging is to have a ready reply which is generic, simple and clear ... and re-usable! Eg I have a &lt;a href=&quot;https://gist.github.com/xurizaemon/656d36d15a1e2619a129d26eab158746&quot;&gt;no email / private support gist&lt;/a&gt; to copy paste when a community support discussion gets redirected to a PM support request I can&#39;t meet. I saw a &lt;a href=&quot;https://www.drupal.org/project/schemadotorg/issues/3344879#comment-14944642&quot;&gt;comment by @jrockowitz&lt;/a&gt; which I thought was a well-phrased response and that got me thinking about how to have a clear, community response to correcting a behaviour we want to reduce.&lt;/p&gt;
&lt;p&gt;(It&#39;s so important to keep in mind that this is a value judgement, that there may be more complexity to the situation, that )&lt;/p&gt;
&lt;p&gt;I&#39;m not sure if Drupal has made a &amp;quot;community comment&amp;quot; on the behaviour yet - it&#39;s complicated, and from conversations I&#39;ve seen it looks like there&#39;s been direct communication at least with some of the agencies doing it. I also see maintainers struggling with getting 40 issue queue updates a night who are being prevented from doing their regular maintainer workload. At that point it&#39;ll be hard to maintain cool or even continuing maintaining components and we risk real community loss. Even so, I&#39;ve observed &lt;em&gt;some&lt;/em&gt; &amp;quot;low-value&amp;quot; contributions being worthwhile and I try to give a fair response.&lt;/p&gt;
&lt;p&gt;I&#39;m concerned that if a brigade against entry-level contributions was set off, that could be really harmful for the community.&lt;/p&gt;
&lt;p&gt;The Drupal community was a fore-runner of commit credits and even leaderboards - something we know can have significant impact for good and bad. I&#39;ve seen proposed that we track negative credits and even display those against &amp;quot;offending&amp;quot; organisations to disincentivise, which I fear would have unintended consequences of its own. I&#39;m realising the interesting complexity of Drupal&#39;s contribution data, how it reflects (or doesn&#39;t reflect) values across a diverse community, and the responsibility of having an established process for this which we&#39;ll have to continue to improve upon over time as long as it exists.&lt;/p&gt;
&lt;p&gt;For me the reflection is about how we best redirect people who have time and energy to contribute from &amp;quot;commit credit carry-on&amp;quot; to outcomes which are those which are appreciated.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I worry we might redirect people to review, then see an uptick in low-effort RTBC reviews.&lt;/li&gt;
&lt;li&gt;I worry that as tooling improves, automated low-effort contributions will increase&lt;/li&gt;
&lt;li&gt;I think we can predict that even more automated (eg LLM-generated) contributions will be on their way&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I think there&#39;s a danger that a change in the balance of labour will affect thinking here. Agencies committing to contribution efforts will do well to be present and focus their contributions effectively.&lt;/p&gt;
&lt;p&gt;We can be confident we&#39;ll see more automated commits in the future. Maybe getting ahead of that wave offers some relief but I&#39;m guessing it won&#39;t be a refuge.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Responsible consumption of Open Source Software</title>
    <link href="https://chris.bur.gs/oss-responsible-consumption/"/>
    <updated>2023-03-04T00:00:00Z</updated>
    <id>https://chris.bur.gs/oss-responsible-consumption/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Consuming OSS components is a digital value transfer from community to the entity using the component.&lt;/p&gt;
&lt;p&gt;With digital goods, reproduction costs are much lower compared to physical artifacts, but production and maintenance costs can be high to the provider of the good. As we account for the full cost of ownership, we discover the cost of maintenance is much higher than it might have seemed, especially when we factor in the ongoing cost of maintenance.&lt;/p&gt;
&lt;p&gt;This has an analogy in extracting resources from the environment in early times when business doesn&#39;t account for the long term impact, and then later we start to appreciate a more full accounting of the impact of our work.&lt;/p&gt;
&lt;p&gt;It&#39;s incumbent on users of open soruce to recognise and nurture the communities that provide the software they depend upon.&lt;/p&gt;
&lt;p&gt;We should also appreciate the special power of open source, that we are able to contribute directly to projects. There are lots of ways to contribute - organising, coding, reviewing, testing, funding, directing, and more I&#39;m sure. When we contribute we enable ourselves to engage in direct advocacy and have agency over the functionality and future of the project.&lt;/p&gt;
&lt;p&gt;When building on OSS components, it can help to have ways of modifying them which support upstreaming our changes. Patching or forking workflows which are in public support and encourage visibility and sharing. (There&#39;s more to communicating than just forking in public, of course.) Visibility of working in public also asks us to code securely, to not commit sensitive data to a codebase, and to think carefully about how our code is structured for general usage - all valuable practices to work securely and with confidence.&lt;/p&gt;
&lt;p&gt;Developing an active presence in the community helps us work with the software better through familiarity in both directions. By becoming familiar with community approaches, we expend less effort trying to configure systems in ways which don&#39;t work in alignment with the project&#39;s intended use, and don&#39;t rabbithole ourselves inventing novel solutions to solved problems. Presence in the community also gives others familiarity with our work, which supports us when there&#39;s reason to propose a change to the way things work. Both are beneficial outcomes to working visibly.&lt;/p&gt;
&lt;p&gt;We use a lot of software and can&#39;t be expected to actively engage in every single component or community we make use of. For those of special significance to our business or process, and especially those we actively develop with, there are lots of opportunities for companies to contribute in ways that benefit the provider and consumer mutually.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>In Memoriam Instagram</title>
    <link href="https://chris.bur.gs/in-memoriam-instagram/"/>
    <updated>2023-03-03T00:00:00Z</updated>
    <id>https://chris.bur.gs/in-memoriam-instagram/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Recently helped a friend extract her own Instagram account&#39;s images from her hijacked account after it got hacked.&lt;/p&gt;
&lt;p&gt;The hijackers of her account had not yet deleted her previous posts, and using &lt;a href=&quot;https://instaloader.github.io/&quot;&gt;instaloader&lt;/a&gt; we were able to download her visible posts to a digital archive.&lt;/p&gt;
&lt;p&gt;My friend was upset to have lost access to a lot of special images which she didn&#39;t have copies of elsewhere.&lt;/p&gt;
&lt;p&gt;Losing control of an account can be upsetting for the loss of networks and connections we share, or experiences we&#39;re attached to. It&#39;s a form of identity loss I guess. Digital security is a privilege not all people have access too either.&lt;/p&gt;
&lt;p&gt;She wasn&#39;t yet able to recover her account but I was glad that we could retrieve those images she&#39;d lost access to. I posted them back to her yesterday by regular post. It&#39;s good that her images can be where they belong, with her, not attached to a lost account.&lt;/p&gt;
&lt;p&gt;I&#39;m mindful of Sumana Harihareswara&#39;s &lt;a href=&quot;https://docs.oscollective.org/guides/handling-burnout-and-career-planning&quot;&gt;&amp;quot;All maintainers eventually leave (or stop leading) projects.&amp;quot;&lt;/a&gt;. This is true of all digital accounts. All digital accounts in time will be cut from the identity attached to them. (Amusing corollary: there are now digital accounts, such as &lt;code&gt;@CocaCola&lt;/code&gt; but not just corporate, which may now last for generations of humans.)&lt;/p&gt;
&lt;p&gt;Before I got it together to send the digital archive by post (is the postal service a cloud of sorts?), I wanted to reassure her that the images were safe. So I had the idea of combining a year&#39;s images into a few seconds of video and messaging that to her - used &lt;code&gt;ffmpeg&lt;/code&gt;, it looked cool.&lt;/p&gt;
&lt;p&gt;The video at the top of this post is me having finally got round to doing the same for my own (admittedly shorter) feed.&lt;/p&gt;
&lt;p&gt;The envelope is suitably sombre and has been cut from building paper which I had scraps of in the garden.&lt;/p&gt;
&lt;p&gt;Anyway - after posting this today, I went back and made a backup of my (much smaller) Instagram history. And then I found the ffmpeg command to turn it into a brief video. My own posts only makes a ten second video, but watched on loop it&#39;s quite fun, and various things leap out as you watch it.&lt;/p&gt;
&lt;p&gt;I&#39;ve hidden it behind a reveal click below because otherwise it&#39;s a bit of an eyeful!&lt;/p&gt;
&lt;div class=&quot;p-4 h-[700px] content-center bg-gray-300&quot;&gt;
&lt;details&gt;
&lt;summary&gt;Click here to see my instagram account&#39;s entire feed in a ten second loop&lt;/summary&gt;
&lt;video width=&quot;600&quot; height=&quot;600&quot; autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot;&gt;
  &lt;source src=&quot;https://chris.bur.gs/media/xurizaemon-all.mp4&quot; type=&quot;video/mp4&quot; /&gt;
&lt;/video&gt;
&lt;/details&gt;
&lt;/div&gt;
</content>
  </entry>
  <entry>
    <title>Islandora and ISLE DC</title>
    <link href="https://chris.bur.gs/islandora-isle-dc/"/>
    <updated>2023-03-02T00:00:00Z</updated>
    <id>https://chris.bur.gs/islandora-isle-dc/</id>
    <content xml:lang="en" type="html">&lt;p&gt;For work reasons I ran up an &lt;a href=&quot;https://github.com/Islandora-Devops/isle-dc&quot;&gt;Isle DC&lt;/a&gt; instance or two on Thursday. OK, first let&#39;s unpack that!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Islandora is an open source digital asset manager and digital heritage repository. It&#39;s composed of a &lt;em&gt;lot&lt;/em&gt; of services.&lt;/li&gt;
&lt;li&gt;ISLE is Islandora: Enterprise 2 (I think)&lt;/li&gt;
&lt;li&gt;ISLE DC is a Docker Compose environment to spin up Islandora sites&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The ISLE DC installer uses a Makefile (love it) and brings up several containers to run the various services that make an Islandora environment. (You can configure lots of this, eg swapping in your preferred DB backend service for the Matomo or Drupal components).&lt;/p&gt;
&lt;p&gt;The environment which it brings up has lots to it. I&#39;m planning on making some post-it notes to list out the services that it include, since I look at the list and boggle.&lt;/p&gt;
&lt;p&gt;We use Lando quite a lot on other projects, and both Lando and ISLE DC drop in a Traefik proxy which wants (out of the box) to bind port 80. I&#39;d &lt;em&gt;love&lt;/em&gt; to be able to have both run alongside each other, so I&#39;m interested to see whether we can do that. Ideally we want to be able to switch quickly between projects without needing to shut down all the others. The conflict is really around the default ports, which can be resolved of course, and this is a challenge you can run into with running (say) Vagrant and Lando sites in parallel.&lt;/p&gt;
&lt;p&gt;I wonder if we can have the two systems co-operate on a common Traefik proxy ... there are other options.&lt;/p&gt;
&lt;p&gt;The installer for the demo Islandora site takes a wee while, during which my everyday laptop lagged crazily. But it came up and it&#39;s an impressive work of orchestration to bring each of those several services up and connect them. Really impressed. I can see that the orchestration here is keeping deployment to other platforms in mind too, and it got me wondering about how we plan to deploy Islandora ourselves.&lt;/p&gt;
&lt;p&gt;I don&#39;t require local development environments to faithfully reproduce hosted environments, I see that as an anti-pattern. It&#39;s beneficial to not couple too tightly to a specific deployment implementation, and so I like to treat local dev and CI as a loose parallel which may run up only the required services. If I&#39;m not developing or testing against Matomo, I&#39;ll choose to omit that from the dev and CI layouts unless it&#39;s of significance. To my thinking, this actually tests that things work in a broader array of configurations.&lt;/p&gt;
&lt;h2&gt;Thoughts&lt;/h2&gt;
&lt;p&gt;A few notes on the deployment it brought up. I&#39;ve only had a brief introduction to the software so far, so I&#39;m not going to leap ahead and propose changes yet. These are just first impressions!&lt;/p&gt;
&lt;h3&gt;Use of &lt;code&gt;sudo&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;I was a bit uncomfortable with the Makefile prompting me for my system password part way through the process. This doesn&#39;t feel like it should be necessary, and is always worth a careful review. On doing so I saw &lt;a href=&quot;https://github.com/Islandora-Devops/isle-dc/blob/3261fc9223350aa443b4ea7bfc702ab69636d1de/Makefile#L492&quot;&gt;this code which is triggered from the presence of an environment variable&lt;/a&gt; in &lt;code&gt;.env&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-makefile&quot;&gt;&lt;code class=&quot;language-makefile&quot;&gt;CMD &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;shell&lt;/span&gt; [ &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;IS_DRUPAL_PSSWD_FILE_READABLE&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; -eq 1 ] &amp;amp;&amp;amp; echo &lt;span class=&quot;token string&quot;&gt;&#39;tee&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; echo &lt;span class=&quot;token string&quot;&gt;&#39;sudo -k tee&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&#39;s a little hard to read - I think this is only using &lt;code&gt;sudo&lt;/code&gt; to read the contents of the file, right? But it makes me nervous seeing &lt;code&gt;sudo&lt;/code&gt; scattered around as a workaround for correct file permissions configuration, and until I read that carefully I wondered if I could exec something by setting &lt;code&gt;DRUPAL_DEFAULT_ACCOUNT_PASSWORD&lt;/code&gt; to &lt;code&gt;&amp;quot;password&amp;quot;; adduser baddie &amp;lt;...&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Docker Compose or otherwise, calling &lt;code&gt;sudo&lt;/code&gt; commands on the host OS are something to be wary of. Lando does some fancy shenanigans behind the scenes to keep file ownership aligned, which is nice. Many services which are fully self-contained don&#39;t need this treatment at all; for those which permit direct edits in a volume mount, it&#39;s important to get right. (For Drupal media, configuring &lt;a href=&quot;https://git.drupalcode.org/project/drupal/-/blob/9.5.x/sites/example.settings.local.php#L121-131&quot;&gt;$settings[&#39;skip_permissions_hardening&#39;] = TRUE;&lt;/a&gt; in local dev helps with this.)&lt;/p&gt;
&lt;h3&gt;I disagree with &lt;code&gt;update_settings_php&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;On first impression I don&#39;t agree with &lt;a href=&quot;https://github.com/Islandora-Devops/isle-buildkit/blob/4be2994f4658c59ce92391e3a6b76f826a1e6a31/drupal/rootfs/etc/islandora/utilities.sh#L341-L400&quot;&gt;&lt;code&gt;update_settings_php&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I don&#39;t like that it places configuration AFTER &lt;code&gt;include settings.local.php&lt;/code&gt;, which is supposed to be at the very bottom of Drupal&#39;s &lt;code&gt;settings.php&lt;/code&gt;. &lt;code&gt;settings.local.php&lt;/code&gt; should IMO still be able to override any of the values set in that snippet. The snippet generated should be placed above that final include, or in a separate include if it&#39;s intended to be generated per environment/build rather than committed to the codebase.&lt;/li&gt;
&lt;li&gt;I don&#39;t like that the implementation is a bash function stashed away inside a built container, which can hide how things are working if you&#39;re trying to understand what&#39;s going on by reading the source of your Islandora codebase.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Traefik and sharing proxy&lt;/h3&gt;
&lt;p&gt;As mentioned above, this is on my mind ... I see in ISLE DC that there&#39;s a configuration &lt;code&gt;INCLUDE_TRAEFIK_SERVICE&lt;/code&gt;, which apparently permits the environment to use an external Traefix proxy if set false. Lando also has a global config &lt;code&gt;proxy&lt;/code&gt; which can be set &amp;quot;OFF&amp;quot; to disable Lando&#39;s Traefik&lt;/p&gt;
&lt;h3&gt;Do we need all this?&lt;/h3&gt;
&lt;p&gt;Some of the containers may not be necessary? I don&#39;t know yet which, but I was glad that the inclusion of VS Code server is not activated by default (!!!), and I would probably turn Matomo off by default. The services which did come up had &lt;a href=&quot;https://github.com/Islandora-Devops/isle-dc/blob/3261fc9223350aa443b4ea7bfc702ab69636d1de/sample.env#L156-L175&quot;&gt;related settings for memory limits&lt;/a&gt;, and the total of all those memory limits was in the neighbourhood of 50GB for services which did come up in my test. Whoa!&lt;/p&gt;
&lt;h3&gt;Resource demand&lt;/h3&gt;
&lt;p&gt;The fcrepo service certainly gave my work laptop a run for its money and I killed some updating daemons at one point when the system lagged to a standstill with a loadavg just shy of 400. I may have to requisition an appropriate 64GB RAM stick, or a standalone environment! But I&#39;d rather be able to hack on my laptop and not remotely. Maybe that&#39;s why VS Code server got in there ... With things as they are, I probably won&#39;t casually put an Islandora environment on my personal laptop to test things out.&lt;/p&gt;
&lt;h2&gt;In summary&lt;/h2&gt;
&lt;p&gt;This project is &lt;em&gt;exciting&lt;/em&gt;. The above criticisms / thoughts are just an early response, and my bigger feeling is - wow, this is really well put together, and reminds me of other community efforts where people find something that works and make progress. I&#39;m stoked to see that the Islandora community has got things so well put together so far and I&#39;m excited to see what a deployment of this looks like. We have a handful of existing Islandora projects I look forward to trying it out on.&lt;/p&gt;
&lt;p&gt;The Islandora community looks really exciting to be a part of, and I look forward to getting to work on some of these projects in future, and building a connection with the community.&lt;/p&gt;
&lt;p&gt;Working with digital heritage and culture artifacts is something I&#39;d love to see myself doing more in the next few years, and I&#39;d be excited to work directly engaged with institutions implementing these systems. I think it&#39;s so important to have a strong connection to the people involved in working with the objects of significance in order to best support building tools that reflect both the community best practice and the special nature of the heritage at hand.&lt;/p&gt;
&lt;p&gt;I&#39;m thankful to &lt;a href=&quot;https://github.com/kayakr&quot;&gt;Jonathan Hunt&lt;/a&gt; and &lt;a href=&quot;https://github.com/antbrown&quot;&gt;Ant Brown&lt;/a&gt; for making the introduction, and giving me a good roadmap to begin my journey with Islandora!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Eleventy!</title>
    <link href="https://chris.bur.gs/eleventy/"/>
    <updated>2023-02-28T00:00:00Z</updated>
    <id>https://chris.bur.gs/eleventy/</id>
    <content xml:lang="en" type="html">&lt;p&gt;OK, my blog at &lt;a href=&quot;http://chris.bur.gs/&quot;&gt;chris.bur.gs&lt;/a&gt; had gone pretty idle since joining Catalyst in 2017.&lt;/p&gt;
&lt;p&gt;This is an interesting note - I think I felt that starting in a new role, I wasn&#39;t confident of my voice and paused posting. At Catalyst I started an internal blog once I got the hang of things, and realised I wanted a place to share thoughts. I highly rate this approach, tbh - I find writing helps me consolidate thinking, and it&#39;s an unobtrusive way to work openly and occasionally surprise your future self.&lt;/p&gt;
&lt;p&gt;I&#39;ve long had a pocket journal, notebook or digital journal / blog and honestly I think I mostly write for my own future self, but with others in mind where I can?&lt;/p&gt;
&lt;p&gt;Anyway - I&#39;ve pulled some of those internal articles across to this as a continuation of my process.&lt;/p&gt;
&lt;p&gt;It&#39;s nice that so much of my former blog converted readily to Eleventy - testament to markdown as a format really. I&#39;m more and more enamoured of the static site approach.&lt;/p&gt;
&lt;p&gt;I&#39;ve not really implemented any themeing at this point ... exploring Tailwind, just out of interest.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Using Drupal Migrate to report on content</title>
    <link href="https://chris.bur.gs/migrate-for-content-report/"/>
    <updated>2022-11-20T23:19:00Z</updated>
    <id>https://chris.bur.gs/migrate-for-content-report/</id>
    <content xml:lang="en" type="html">&lt;p&gt;A customer requested a report of:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;How many times a given token (&lt;code&gt;[foo:some-value:3]&lt;/code&gt;) could be found in a content corpus&lt;/li&gt;
&lt;li&gt;The specific IDs of each value in each content item&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The content corpus in question is a decent size (150K content items) stored in a Drupal 7 DB. While this request might be deliverable via a SQL query, the risk of bypassing application logic is that we might not account for some detail of implementation that means we miss some content items.&lt;/p&gt;
&lt;p&gt;Since we&#39;re already writing migrations for this Drupal 7 site, why not use Drupal Migrate to report on it?&lt;/p&gt;
&lt;p&gt;Here&#39;s my plan, and how it worked out:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Add a temporary field to the Drupal 9 destination content fields&lt;/li&gt;
&lt;li&gt;Add a migration which extracts the tokens &lt;em&gt;only&lt;/em&gt; from the source content and stores it to that field.&lt;/li&gt;
&lt;li&gt;Run the migration and see&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;Implementation&lt;/h2&gt;
&lt;p&gt;This request came in before we had the Drupal 9 site codebase for the site in question ready, so I opted to hijack another similar site&#39;s DB for the purpose.&lt;/p&gt;
&lt;p&gt;I brought up the Drupal 7 dev environment in Lando, and reconfigured the destination site&#39;s Migrate source connection to use the same credentials but with a changed hostname. Lando makes the DB for an environment available at something like &lt;code&gt;database.name.internal&lt;/code&gt; where &lt;code&gt;name&lt;/code&gt; is derived from the D7 site&#39;s &lt;code&gt;name&lt;/code&gt; value.&lt;/p&gt;
&lt;p&gt;Drupal 9 &lt;code&gt;web/sites/default/settings.php&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-php&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getenv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;MIGRATION_DATABASE_NAME&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token variable&quot;&gt;$databases&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;migrate&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;default&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;database&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getenv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;MIGRATION_DATABASE_NAME&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;username&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getenv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;MIGRATION_DATABASE_USERNAME&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;password&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getenv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;MIGRATION_DATABASE_PASSWORD&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;prefix&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;// &#39;host&#39; =&gt; getenv(&#39;MIGRATION_DATABASE_HOST&#39;),&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;host&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;database.SITENAME.internal&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;port&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getenv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;MIGRATION_DATABASE_PORT&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;driver&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;getenv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;MIGRATION_DATABASE_DRIVER&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I confirmed that I could now see the alternate D7 source data via &lt;code&gt;drush migrate:status&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;h3&gt;Side quest: performance challenge!&lt;/h3&gt;
&lt;p&gt;This &lt;code&gt;drush migrate:status&lt;/code&gt; command was &lt;em&gt;really really&lt;/em&gt; slow. In another terminal I connected to the D7 source system and executed &lt;code&gt;watch -n5 &amp;quot;mysql -uroot -e &#39;SHOW FULL PROCESSLIST&#39;&amp;quot;&lt;/code&gt; to see what was going on. The query was checking content for regex matches against &lt;code&gt;%src=&amp;quot;/file%&lt;/code&gt;, and sure enough this led me to the Media Migrate plugin and a &lt;a href=&quot;https://www.drupal.org/project/media_migration/issues/3209706&quot;&gt;performance issue I&#39;d raised 11months ago&lt;/a&gt;, which now had a patch. Win!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I didn&#39;t want to have to iterate through the entire content set to develop, so I used &lt;code&gt;drush migrate:import migration_id --limit=1 --migrate-debug&lt;/code&gt; to identify the first node we&#39;d get out of the system. This would become my sacrificial test node, even though it didn&#39;t originally contain the tokens in question.&lt;/p&gt;
&lt;p&gt;I modified the D7 DB state directly so that I would be able to retrieve a matching result from the first source row. It&#39;s intentional that the tokens aren&#39;t identical.&lt;/p&gt;
&lt;pre class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;update&lt;/span&gt; field_revision_body &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; body_value&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;blah&#92;n&#92;n[foo:grouped-media:0]&#92;n&#92;n[foo:grouped-media-table:1]&#92;n&#92;n[foo:grouped-media:2]&#92;n&#92;nblah&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; entity_id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;74&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;update&lt;/span&gt; field_data_body &lt;span class=&quot;token keyword&quot;&gt;set&lt;/span&gt; body_value&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;blah&#92;n&#92;n[foo:grouped-media:0]&#92;n&#92;n[foo:grouped-media-table:1]&#92;n&#92;n[foo:grouped-media:2]&#92;n&#92;nblah&#39;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;where&lt;/span&gt; entity_id&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;74&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I reconfigured the D9 site to have an additional field &lt;code&gt;field_tmp_token_count&lt;/code&gt; (text) to store the report values. This can be later used in Views to report for the client. I exported the Drupal 9 configuration to preserve the field configuration.&lt;/p&gt;
&lt;p&gt;I now created a node classic migration for the content type we were interested in. I modified it to process the body field directly into the temporary field.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  field_tmp_token_count: body/0/value
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I commented out almost all other process pipelines; I only wanted the essentials: nid, vid, title, and anything else required for the migration to work as expected.&lt;/p&gt;
&lt;p&gt;I then wrote the process plugin, testing my &lt;code&gt;preg_match_all()&lt;/code&gt; using &lt;code&gt;php -a&lt;/code&gt; (or &lt;code&gt;drush core-cli&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;web/modules/custom/foo_migrate/src/Plugin/migrate/process/TokenCount.php&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-php&quot;&gt;&lt;span class=&quot;token php language-php&quot;&gt;&lt;span class=&quot;token delimiter important&quot;&gt;&amp;lt;?php&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;token package&quot;&gt;Drupal&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;foo_migrate&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;Plugin&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;migrate&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;process&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token package&quot;&gt;Drupal&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;migrate&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;MigrateException&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token package&quot;&gt;Drupal&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;migrate&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;MigrateExecutableInterface&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token package&quot;&gt;Drupal&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;migrate&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;ProcessPluginBase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token package&quot;&gt;Drupal&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;migrate&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;Row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token package&quot;&gt;Drupal&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;node&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;Entity&lt;span class=&quot;token punctuation&quot;&gt;&#92;&lt;/span&gt;Node&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;/**&lt;br /&gt; * Count the number of tokens in a string.&lt;br /&gt; *&lt;br /&gt; * @MigrateProcessPlugin(&lt;br /&gt; *   id = &quot;token_count&quot;&lt;br /&gt; * )&lt;br /&gt; *&lt;br /&gt; * @code&lt;br /&gt; * field_token_count:&lt;br /&gt; *   plugin: token_count&lt;br /&gt; *   source: body/value&lt;br /&gt; * @endcode&lt;br /&gt; */&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name-definition class-name&quot;&gt;TokenCount&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProcessPluginBase&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;/**&lt;br /&gt;   * {@inheritdoc}&lt;br /&gt;   *&lt;br /&gt;   * @throws &#92;Drupal&#92;migrate&#92;MigrateException&lt;br /&gt;   */&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name type-declaration&quot;&gt;MigrateExecutableInterface&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$migrate_executable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name type-declaration&quot;&gt;Row&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$row&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$destination_property&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$matches&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$pattern&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;/&#92;[foo&#92;:[^&#92;[]*&#92;]/&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token variable&quot;&gt;$res&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;preg_match_all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$pattern&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$matches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$res&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;implode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39; &#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$matches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now I can use my process plugin in the migration:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;  &lt;span class=&quot;token key atrule&quot;&gt;field_tmp_token_count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;plugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; token_count&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; body/0/value&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I imported this configuration change using &lt;code&gt;drush config:import&lt;/code&gt; and re-ran the migration with &lt;code&gt;drush migrate:import migration_id --update --limit=1 --migrate-debug&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I could see the matched tokens extracted and stored in the destination field.&lt;/p&gt;
&lt;p&gt;Now I could kick off the full migration and let it run. This took two and a half hours.&lt;/p&gt;
&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt; [notice] Processed 48046 items (19490 created, 22960 updated, 0 failed, 5596 ignored) in 8539.5 seconds (337.6/min) - done with &#39;foo_d7_node_resource&#39;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The migration yaml&lt;/h2&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;uuid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 91a9f5db&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;d4fc&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;4387&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;9adc&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;24d672a89286&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;langcode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; en&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;dependencies&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; foo_d7_node_resource&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Drupal&#92;node&#92;Plugin&#92;migrate&#92;D7NodeTranslation&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;field_plugin_method&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token null important&quot;&gt;null&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;cck_plugin_method&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token null important&quot;&gt;null&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;migration_tags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Drupal 7&#39;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; Content&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;migration_group&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; migrate_drupal_7&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Node (Resource)&#39;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;plugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; d7_node_complete&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;node_type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; resource&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;revision_log_append&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@logMessage&#39;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;nid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;plugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; get&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; tnid&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;vid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;plugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; get&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; vid&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;langcode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;plugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; default_value&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; language&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;default_value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; und&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;moderation_state&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;plugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; static_map&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;default_value&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; draft&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@moderation_state&#39;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token key atrule&quot;&gt;draft&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; draft&lt;br /&gt;        &lt;span class=&quot;token key atrule&quot;&gt;published&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; published&lt;br /&gt;        &lt;span class=&quot;token key atrule&quot;&gt;archive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; archived&lt;br /&gt;        &lt;span class=&quot;token key atrule&quot;&gt;needs_review&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; draft&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;plugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; get&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; title&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;uid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;plugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; get&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; node_uid&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;plugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; get&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; status&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;field_tmp_token_count&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;plugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; token_count&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; body/0/value&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;destination&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;plugin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;entity_complete:node&#39;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;translations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;default_bundle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; resource&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;migration_dependencies&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;required&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; foo_d7_media_document&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; foo_d7_media_image&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; foo_d7_media_video&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; foo_d7_taxonomy_term_author_corporate&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; foo_d7_taxonomy_term_content_status&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; foo_d7_taxonomy_term_copyright_licensing&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; foo_d7_taxonomy_term_region&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; foo_d7_taxonomy_term_resource_types&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; foo_d7_taxonomy_term_solr_keywords&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; foo_d7_taxonomy_term_tags&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; foo_d7_taxonomy_term_viewpoint&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; foo_d7_user&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; foo_embedded_taxonomy_term&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>Drupalsouth Talks</title>
    <link href="https://chris.bur.gs/drupalsouth-talks/"/>
    <updated>2022-10-30T22:10:00Z</updated>
    <id>https://chris.bur.gs/drupalsouth-talks/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I didn&#39;t make it to DrupalSouth this year sadly, but it sounds from some attendee reports like it was a tight conference and the industry is going great over the water. An interesting challenge of this is that Australia is in a hiring frenzy for Drupal folk, and it&#39;ll be a challenge for us to compete with that.&lt;/p&gt;
&lt;p&gt;I applied to attend when my talk proposal was accepted, but was disappointed to find that there was no remaining budget for this.&lt;/p&gt;
&lt;p&gt;Some good talks coming out on video now anyway! I get the impression that the Drupal industry is really moving ahead in Australia.&lt;/p&gt;
&lt;h2&gt;How to lead talented technical teams&lt;/h2&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/TJ2MP2sLtHU&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;Keynote by Sarah Jane Peterschlingmann. Some good insights on team culture &amp;amp; development, particularly interesting to see her comments on how trying to improve team makeup using pay can have unintended consequences, and appreciated her articulation of the &amp;quot;expert leader&amp;quot; trap.&lt;/p&gt;
&lt;h2&gt;Drupal 9, Migrations, Decoupled APIs and Online Collections / Morgan Strong&lt;/h2&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/DGia2z2qACo&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;QAGOMA is the Queensland Art Gallery / Gallery of Modern Art. A really interesting look at a complex project which Drupal is one part of, and how Migrate is helping them.&lt;/p&gt;
&lt;h2&gt;Scaling Drupal Through a Pandemic&lt;/h2&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/yXEil10HLyM&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;Kim Pepper (PNX, Skpr) talks about handling massive web traffic during the pandemic, sharing some of the lessons and wins they found along the way.&lt;/p&gt;
&lt;h2&gt;Beyond plain language: Techniques for communicating complex concepts and processes&lt;/h2&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/kFf_AWNGm90&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;Really valuable; not about technology but communication.&lt;/p&gt;
&lt;h2&gt;More&lt;/h2&gt;
&lt;p&gt;There&#39;s lots more - I&#39;d love to hear what you found valuable!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/playlist?list=PL31TzQhFjH2Lk9JjkudM7prduSDfIx30e&quot;&gt;https://www.youtube.com/playlist?list=PL31TzQhFjH2Lk9JjkudM7prduSDfIx30e&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Growing Catalyst Cloud Volume</title>
    <link href="https://chris.bur.gs/growing-catalyst-cloud-volume/"/>
    <updated>2022-10-29T17:35:00Z</updated>
    <id>https://chris.bur.gs/growing-catalyst-cloud-volume/</id>
    <content xml:lang="en" type="html">&lt;p&gt;So, I ran up a couple of cloud instances recently, and this week looked back and both had full volumes. Le sigh.&lt;/p&gt;
&lt;p&gt;(No, I probably wouldn&#39;t have run into this if I&#39;d had one of our excellent ops team deploy an instance for us.)&lt;/p&gt;
&lt;p&gt;Time to grow the available diskspace for them!&lt;/p&gt;
&lt;h2&gt;Docs&lt;/h2&gt;
&lt;p&gt;Catalyst Cloud has an entry on this in their &lt;a href=&quot;https://docs.catalystcloud.nz/block-storage/faq.html&quot;&gt;Block Storage FAQ&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;On first read I actually missed what looks like the easy instructions at the top. Second time around I&#39;m going to use those better.&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;I logged into &lt;a href=&quot;https://dashboard.cloud.catalyst.net.nz/&quot;&gt;https://dashboard.cloud.catalyst.net.nz&lt;/a&gt; and found my instances, making a note of the storage size and type (b1.standard is the default, and that uses spinning disks which are cheaper but slower).&lt;/p&gt;
&lt;p&gt;I have an instance with a 10GB disk and another with a 40GB disk. I&#39;ll tackle the 10GB first, then repeat for the other once I&#39;m happy with round one.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://docs.catalystcloud.nz/block-storage/faq.html#boot-volumes&quot;&gt;FAQ answer&lt;/a&gt; to growing boot volumes give three answers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deploy a new instance and configure from scratch - good advice generally, not doing that thanks&lt;/li&gt;
&lt;li&gt;Use a volume snapshot - looks easy but says I can never delete the source volume, which seems wrong&lt;/li&gt;
&lt;li&gt;&amp;quot;Old fashioned method&amp;quot; is what we&#39;ll be doing today, then&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Am I deterred by the label &amp;quot;old-fashioned method&amp;quot;? Why doesn&#39;t it describe the process (&amp;quot;clone data to new volume&amp;quot;), but instead makes a comment on the process (&amp;quot;old-fashioned method&amp;quot;)? It feels like this ought to dissuade us - everyone knows old-fashioned means bad, right? Well, no, I&#39;m not deterred - option one is labour intensive, option two has a weird gotcha, let&#39;s try option three.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Process followed (v1)&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;I suggest you read the whole article before following this, as the first attempt didn&#39;t work out.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In the Catalyst Cloud dashboard, I create a new volume, with a size of &lt;code&gt;40GB&lt;/code&gt; and a type of &lt;code&gt;b1.sr-r3-nvme-1000&lt;/code&gt;. The default label is &lt;code&gt;b1.standard&lt;/code&gt;&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://chris.bur.gs/growing-catalyst-cloud-volume/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; which I believe from experience means a spinning disk storage - much slower. I can see &amp;quot;nvme&amp;quot; in this label, and while Catalyst Cloud doesn&#39;t specify what the types are in the UX, I find clues on the &lt;a href=&quot;https://catalystcloud.nz/pricing/price-list/&quot;&gt;pricing page&lt;/a&gt; - &amp;quot;3 replicas on NVME with 1000 IOPS burst limit&amp;quot;.&lt;/p&gt;
&lt;p&gt;I don&#39;t need to answer the &amp;quot;Volume name&amp;quot; question, it&#39;ll get a UUID generated if I leave it blank. And the instructions say this new volume needs to be bootable; that option isn&#39;t available when creating the new volume, but it is if I create the volume, then edit it and check the &amp;quot;Bootable&amp;quot; option.&lt;/p&gt;
&lt;p&gt;I can now attach the volume to my running instance, by using the menu on the volume. This asks just for the compute instance to connect to. It also asks for a device ID and the placeholder value shown is &lt;code&gt;/dev/vdc&lt;/code&gt;, but I notice that when I leave that blank and attach it, the volume shows up as &lt;code&gt;/dev/vdb&lt;/code&gt;. Also fine, just one to keep an eye on.&lt;/p&gt;
&lt;p&gt;Now I can SSH into that running instance.&lt;/p&gt;
&lt;p&gt;First we want to make a filesystem on the new volume, and mount it at a target destination to be copied to. (This isn&#39;t stated in the docs. If you omit this, you&#39;ll end up copying to the same disk.)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkfs.ext4 /dev/vdb
mkdir /media/newvolume
mount /dev/vdb /media/newvolume
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The docs have a command for us to perform the copy at the top of the page, but it seems to be explicitly referring to non-boot volumes. Let&#39;s see what that &lt;code&gt;rsync&lt;/code&gt; command does:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rsync -avxHAXSW --numeric-ids --info=progress2 /data/ /mnt/data_new/&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-a  : all files, with permissions, etc..
-v  : verbose, mention files
-x  : stay on one file system
-H  : preserve hard links (not included with -a)
-A  : preserve ACLs/permissions (not included with -a)
-X  : preserve extended attributes (not included with -a)
-S  : handle sparse files, such as virtual disks, efficiently
-W  : copy files whole (w/o delta-xfer algorithm)
--info=progress2 : will show the overall progress info and transfer speed
--numeric-ids : don&#39;t map uid/gid values by user/group name
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I&#39;d probably drop the &lt;code&gt;-v&lt;/code&gt; here since I don&#39;t need to see all files listead (I wonder if that plays nicely with &lt;code&gt;--info=progress2&lt;/code&gt;?). &lt;code&gt;-x&lt;/code&gt; is going to take care of skipping mounts like &lt;code&gt;/dev&lt;/code&gt;, &lt;code&gt;/proc&lt;/code&gt;, &lt;code&gt;/sys&lt;/code&gt;. &lt;code&gt;-HAX&lt;/code&gt; looks good. &lt;code&gt;-S&lt;/code&gt; seems reasonable? &lt;code&gt;-W&lt;/code&gt; I guess?&lt;/p&gt;
&lt;p&gt;Let&#39;s try it with &lt;code&gt;-v&lt;/code&gt; first:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rsync -avxHAXSW --numeric-ids --info=progress2 / /media/newvolume/&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;root@deploy-instance:~$ rsync -avxHAXSW --numeric-ids --info=progress2 / /media/newvolume/
sending incremental file list
./
bin -&amp;gt; usr/bin
lib -&amp;gt; usr/lib
lib32 -&amp;gt; usr/lib32
lib64 -&amp;gt; usr/lib64
libx32 -&amp;gt; usr/libx32
sbin -&amp;gt; usr/sbin
boot/
boot/System.map-5.15.0-52-generic
     12,494,544  11%   20.67MB/s    0:00:00 (xfr#2, ir-chk=1039/1065)
boot/config-5.15.0-52-generic
     13,018,266  11%   19.17MB/s    0:00:00 (xfr#4, ir-chk=1037/1065)
boot/initrd.img -&amp;gt; initrd.img-5.15.0-52-generic
boot/initrd.img-5.15.0-52-generic
     52,744,423  48%   24.63MB/s    0:00:02  ^Crsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at io.c(503) [generator=3.2.3]

rsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at rsync.c(703) [sender=3.2.3]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;OK, that&#39;s doing per-file progress updates. I don&#39;t need that at all.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rsync -axHAXSW --numeric-ids --info=progress2 / /media/newvolume/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Much nicer - now I&#39;ve got a percentage progress bar (which leaps around a bit?) and not too much output. Just right.&lt;/p&gt;
&lt;p&gt;The copy does its thing.&lt;/p&gt;
&lt;h2&gt;Launch with new volume&lt;/h2&gt;
&lt;p&gt;Since I was doing &amp;quot;old fashioned method&amp;quot; I figured this next would work as though for a physical machine - I&#39;d detach the old volume and leave only the newer one connected.&lt;/p&gt;
&lt;p&gt;I found I couldn&#39;t detach the original volume from the existing instance. The instructions actually say &amp;quot;Attach the new volume and the original to another instance.&amp;quot;.&lt;/p&gt;
&lt;p&gt;I&#39;d already done the clone so instead I launched a new instance with the new volume.&lt;/p&gt;
&lt;p&gt;Using &lt;strong&gt;Launch Instance&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Details
&lt;ul&gt;
&lt;li&gt;name: deploy-instance-2&lt;/li&gt;
&lt;li&gt;source: volume deploy-volume&lt;/li&gt;
&lt;li&gt;flavor: c2r4&lt;/li&gt;
&lt;li&gt;networks: deploy-network&lt;/li&gt;
&lt;li&gt;secgroups: deploy-secgroup&lt;/li&gt;
&lt;li&gt;key pair: chrisburgess&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then I&#39;ll need to move the allocated IP over from the original. Under &lt;strong&gt;Floating IPs&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Disassociate the existing IP (lol)&lt;/li&gt;
&lt;li&gt;Associate it to the new instance&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This didn&#39;t boot up (ok, maybe I did need to run grub, sigh).&lt;/p&gt;
&lt;p&gt;Couldn&#39;t detach the new volume from the new instance to put it back on the old instance. Ugh.&lt;/p&gt;
&lt;p&gt;Tried uploading the volume to image, no dice. Tried a few things to see if I could &amp;quot;unlock&amp;quot; the volume (unlock the instance, set the volume not bootable) but nope. Huh.&lt;/p&gt;
&lt;h2&gt;That didn&#39;t work, let&#39;s try again&lt;/h2&gt;
&lt;p&gt;I deleted the new instance created above, so I had just the original additional volume. I attached that to the original instance. (Thank heck I didn&#39;t get eager and delete the instance early.)&lt;/p&gt;
&lt;p&gt;Inspect the attached volume&#39;s partition table:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;root@deploy-instance:~# fdisk -l /dev/vdb
Disk /dev/vdb: 40 GiB, 42949672960 bytes, 83886080 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
root@deploy-instance:~# fdisk -l /dev/vda
Disk /dev/vda: 10 GiB, 10737418240 bytes, 20971520 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 6845360D-8391-4A07-9961-267A575B1271

Device      Start      End  Sectors  Size Type
/dev/vda1  227328 20971486 20744159  9.9G Linux filesystem
/dev/vda14   2048    10239     8192    4M BIOS boot
/dev/vda15  10240   227327   217088  106M EFI System

Partition table entries are not in disk order.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Clone the old partition to the new volume&#39;s partition:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;root@deploy-instance:~# dd if=/dev/vda of=/dev/vdb bs=16M status=progress
10737418240 bytes (11 GB, 10 GiB) copied, 239 s, 44.9 MB/s
640+0 records in
640+0 records out
10737418240 bytes (11 GB, 10 GiB) copied, 240.109 s, 44.7 MB/s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Take another look at the partition table on the new volume:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;root@deploy-instance:~# fdisk -l /dev/vdb
GPT PMBR size mismatch (20971519 != 83886079) will be corrected by write.
The backup GPT table is not on the end of the device.
Disk /dev/vdb: 40 GiB, 42949672960 bytes, 83886080 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 6845360D-8391-4A07-9961-267A575B1271

Device      Start      End  Sectors  Size Type
/dev/vdb1  227328 20971486 20744159  9.9G Linux filesystem
/dev/vdb14   2048    10239     8192    4M BIOS boot
/dev/vdb15  10240   227327   217088  106M EFI System

Partition table entries are not in disk order.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Expand the partition on the new volume:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;root@deploy-instance:~# growpart /dev/vdb 1
CHANGED: partition=1 start=227328 old: size=20744159 end=20971487 new: size=83658719 end=83886047
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Take another look of the changed partition table:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;root@deploy-instance:~# fdisk -l /dev/vdb
Disk /dev/vdb: 40 GiB, 42949672960 bytes, 83886080 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 6845360D-8391-4A07-9961-267A575B1271

Device      Start      End  Sectors  Size Type
/dev/vdb1  227328 83886046 83658719 39.9G Linux filesystem
/dev/vdb14   2048    10239     8192    4M BIOS boot
/dev/vdb15  10240   227327   217088  106M EFI System

Partition table entries are not in disk order.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Mount in the required things to chroot and run grub:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;root@deploy-instance:/mnt# mount /dev/vdb1 /mnt
root@deploy-instance:/mnt# for i in /dev /dev/pts /proc /sys /run ; do mount -B $i .$i ; done
root@deploy-instance:/mnt# chroot /mnt
root@deploy-instance:/# mount
/dev/vdb1 on / type ext4 (rw,relatime)
udev on /dev type devtmpfs (rw,nosuid,relatime,size=1989100k,nr_inodes=497275,mode=755,inode64)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
tmpfs on /run type tmpfs (rw,nosuid,nodev,noexec,relatime,size=401816k,mode=755,inode64)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Grub it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;root@deploy-instance:/# grub-install /dev/vdb
Installing for i386-pc platform.
Installation finished. No error reported.
root@deploy-instance:/# update-grub
Sourcing file `/etc/default/grub&#39;
Sourcing file `/etc/default/grub.d/50-cloudimg-settings.cfg&#39;
Sourcing file `/etc/default/grub.d/init-select.cfg&#39;
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-5.15.0-52-generic
Found initrd image: /boot/initrd.img-5.15.0-52-generic
Found linux image: /boot/vmlinuz-5.15.0-48-generic
Found initrd image: /boot/initrd.img-5.15.0-48-generic
Warning: os-prober will not be executed to detect other bootable partitions.
Systems on them will not be added to the GRUB boot configuration.
Check GRUB_DISABLE_OS_PROBER documentation entry.
done
root@deploy-instance:/#
exit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now detach the new volume and launch a new compute instance, selecting &amp;quot;Volume&amp;quot; under Source. If the volume doesn&#39;t appear in the list you may need to edit the volume first and check &amp;quot;Bootable&amp;quot;.&lt;/p&gt;
&lt;p&gt;Yay, and it came up OK! Now I can shelve then later delete the old instance.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;This got me thinking about driving built versions of the site from Gitlab CI =&amp;gt; Catalyst Cloud Openstack. I know that&#39;s what Cove offers but it requires K8s and the config overhead feels pretty big.&lt;/p&gt;
&lt;p&gt;Easy for me to say after making a LEMP stack though, there&#39;s a lot more to maintaining infra than spinning it up.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot; /&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I think the entry tier nVME storage would be a better default option honestly - it&#39;s similarly priced ($0.21/GB/month for spinning disk, $0.23/GB/month for nVME with double the burst limits). Current defaults will mean newly onboarded customers experience spinning disk until they migrate away. &lt;a href=&quot;https://chris.bur.gs/growing-catalyst-cloud-volume/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  <entry>
    <title>Gitlab CI Runner</title>
    <link href="https://chris.bur.gs/gitlab-ci-runner/"/>
    <updated>2022-10-23T04:55:00Z</updated>
    <id>https://chris.bur.gs/gitlab-ci-runner/</id>
    <content xml:lang="en" type="html">&lt;p&gt;This doesn&#39;t need to be on the same instance as you&#39;re deploying to - in fact it shouldn&#39;t be. For the purposes of this prototype I did re-use the same instance. You could split to a second instance if you want.&lt;/p&gt;
&lt;p&gt;SSH into the instance you&#39;re going to host the Gitlab CI runner on.&lt;/p&gt;
&lt;h2&gt;Install Docker&lt;/h2&gt;
&lt;p&gt;We&#39;re following the Docker documentation for &lt;a href=&quot;https://docs.docker.com/engine/install/ubuntu/&quot;&gt;install Docker Engine on Ubuntu&lt;/a&gt; here.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt-get remove docker docker-engine docker.io containerd runc
sudo apt-get install &#92;
    ca-certificates &#92;
    curl &#92;
    gnupg &#92;
    lsb-release
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo &#92;
  &amp;quot;deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu &#92;
  $(lsb_release -cs) stable&amp;quot; | sudo tee /etc/apt/sources.list.d/docker.list &amp;gt; /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo usermod -a -G docker ubuntu
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Log out and back in to get the new group &lt;code&gt;docker&lt;/code&gt; for your user. Test Docker.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ docker run hello-world
Unable to find image &#39;hello-world:latest&#39; locally
latest: Pulling from library/hello-world
2db29710123e: Pull complete
Digest: sha256:18a657d0cc1c7d0678a3fbea8b7eb4918bba25968d3e1b0adebfa71caddbc346
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Great, that worked.&lt;/p&gt;
&lt;h2&gt;Install &amp;amp; run Gitlab Runner&lt;/h2&gt;
&lt;p&gt;Now we&#39;re going to follow Gitlab&#39;s &lt;a href=&quot;https://docs.gitlab.com/runner/install/docker.html&quot;&gt;run Gitlab Runner in a container&lt;/a&gt; docs.&lt;/p&gt;
&lt;p&gt;You might want to put the configuration directory somewhere other than &lt;code&gt;/etc/gitlab-runner&lt;/code&gt;, up to you.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo mkdir /etc/gitlab-runner
$ docker run -d --name gitlab-runner --restart always &#92;
  -v /etc/gitlab-runner:/etc/gitlab-runner &#92;
  -v /var/run/docker.sock:/var/run/docker.sock &#92;
  gitlab/gitlab-runner:latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Obtain a registration key from your Gitlab instance and register the runner (&lt;a href=&quot;https://docs.gitlab.com/runner/register/&quot;&gt;docs&lt;/a&gt;) with your Gitlab instance.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ docker run --rm -it -v /etc/gitlab-runner:/etc/gitlab-runner gitlab/gitlab-runner register
Runtime platform                                    arch=amd64 os=linux pid=6 revision=0d4137b8 version=15.5.0
Running in system-mode.

Enter the GitLab instance URL (for example, https://gitlab.com/):
https://gitlab.example.org
Enter the registration token:
GR123456abcdefg-dddddEEEEEfff
Enter a description for the runner:
[123abc456def]: deploy-runner
Enter tags for the runner (comma-separated):
docker
Enter optional maintenance note for the runner:

Registering runner... succeeded                     runner=GR123456abcdefg-x
Enter an executor: docker, shell, virtualbox, docker+machine, docker-ssh+machine, custom, docker-ssh, parallels, ssh, instance, kubernetes:
docker
Enter the default Docker image (for example, ruby:2.7):
alpine:latest
Runner registered successfully. Feel free to start it, but if it&#39;s running already the config should be automatically reloaded!

Configuration (with the authentication token) was saved in &amp;quot;/etc/gitlab-runner/config.toml&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once your runner shows up as connected in Gitlab on Settings &amp;gt; CI &amp;gt; Runners, you can move on to &lt;a href=&quot;https://chris.bur.gs/post/2022-10-23-gitlab-ci-deployment&quot;&gt;configure a deployment pipeline from CI to webserver&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>LEMP stack</title>
    <link href="https://chris.bur.gs/lemp-stack/"/>
    <updated>2022-10-23T04:54:00Z</updated>
    <id>https://chris.bur.gs/lemp-stack/</id>
    <content xml:lang="en" type="html">&lt;p&gt;We&#39;ve got a fresh Ubuntu install by way of &lt;a href=&quot;https://chris.bur.gs/post/2022-10-23-catalyst-cloud-instance&quot;&gt;create a Catalyst Cloud compute instance&lt;/a&gt;. Now we&#39;ll install a simple LEMP stack (Linux, NginX, MariaDB, PHP).&lt;/p&gt;
&lt;p&gt;First, decide your project name - eg I&#39;m using this to test for CLIENT, so I&#39;ll use &lt;code&gt;client&lt;/code&gt; where &lt;code&gt;PROJECT&lt;/code&gt; appears below. You also need the IP address of your webserver. The instance IP from is shown below as &lt;code&gt;x.x.x.x&lt;/code&gt; below, but you should use your provisioned IP from &lt;a href=&quot;https://chris.bur.gs/post/2022-10-23-catalyst-cloud-instance&quot;&gt;create a Catalyst Cloud compute instance&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;SSH into your instance.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh ubuntu@x.x.x.x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install MariaDB.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo apt update &amp;amp;&amp;amp; sudo apt install -y nginx mariadb-server
$ sudo mysql_secure_installation
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a DB and grant access.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo mariadb
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;MariaDB [(none)]&amp;gt; create database client;
Query OK, 1 row affected (0.001 sec)
MariaDB [(none)]&amp;gt; grant all on client.* to client@localhost identified by &#39;client&#39;;
Query OK, 0 rows affected (0.009 sec)
$ mariadb -u client -p
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install PHP and required libraries for Drupal 9.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo apt install -y php-fpm php-mysql php-curl php-gd php-json php-mbstring php-xml php-curl php-gd php-mbstring php-xml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create nginx config in &lt;code&gt;/etc/nginx/sites-available/PROJECT&lt;/code&gt; using &lt;a href=&quot;https://gitlab.catalyst.net.nz/apl/client/-/blob/ci-deploy/config/nginx/drupal.conf&quot;&gt;&lt;code&gt;config/nginx/drupal.conf&lt;/code&gt;&lt;/a&gt; as a reference implementation. Symlink to the NginX config to enable it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo ln -s /etc/nginx/sites-available/client /etc/nginx/sites-enabled/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reload NginX.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo nginx -t &amp;amp;&amp;amp; sudo nginx -s reload
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Create a test &lt;code&gt;.php&lt;/code&gt; file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir /var/www/PROJECT/current/web
echo &#39;&amp;lt;?php phpinfo();&#39; &amp;gt; /var/www/PROJECT/current/web/index.php
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Verify that the phpinfo works when accessed over http (use the IP address) by visiting &lt;code&gt;http://x.x.x.x&lt;/code&gt; in your browser.&lt;/p&gt;
&lt;p&gt;If it works, we&#39;ll remove it to prepare for the next step.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ rm /var/www/client/current/web/index.php
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we need to ensure the deployment can SSH in and exec commands as a user. In this example we&#39;ll use &lt;code&gt;ubuntu&lt;/code&gt; as the deploy &lt;em&gt;and&lt;/em&gt; login user. In a real setup we&#39;d use separate users.&lt;/p&gt;
&lt;p&gt;Generate an SSH key on your local system.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ssh-keygen -t ecdsa -f ~/.ssh/deploy-key
Generating public/private ecdsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/ubuntu/.ssh/deploy-key
Your public key has been saved in /home/ubuntu/.ssh/deploy-key.pub
The key fingerprint is:
SHA256:abcdefabcdefabcdef/abcdefabcdef ubuntu@deploy-instance
The key&#39;s randomart image is:
+---[ECDSA 256]---+
|                 |
|                 |
|                 |
|                 |
|                 |
|                 |
|                 |
|                 |
|                 |
+----[SHA256]-----+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add the SSH key to deploy user &lt;code&gt;.ssh/authorized_keys&lt;/code&gt; on the compute instance.&lt;/p&gt;
&lt;p&gt;Test SSH access from your computer.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh -i ~/.ssh/deploy-key ubuntu@x.x.x.x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next up: &lt;a href=&quot;https://chris.bur.gs/post/2022-10-23-gitlab-ci-runner&quot;&gt;Set up a Gitlab CI runner&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Catalyst Cloud Instance</title>
    <link href="https://chris.bur.gs/catalyst-cloud-instance/"/>
    <updated>2022-10-23T04:53:00Z</updated>
    <id>https://chris.bur.gs/catalyst-cloud-instance/</id>
    <content xml:lang="en" type="html">&lt;p&gt;These are very brief instructions to get an instance up and running with a recent Ubuntu release. This is basically what&#39;s documented at more length in &lt;a href=&quot;https://docs.catalystcloud.nz/first-instance/overview.html&quot;&gt;Catalyst Cloud Docs: &lt;/a&gt;. The Ubuntu image we use will mean we end up with a default user account of &lt;code&gt;ubuntu&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We need to be logged into a Catalyst Cloud account with some available resources. I&#39;m using &lt;code&gt;test.chc.cat-it.co.nz&lt;/code&gt; here, in region &lt;code&gt;nz-por-1&lt;/code&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Log into &lt;a href=&quot;https://dashboard.cloud.catalyst.net.nz/project/&quot;&gt;Catalyst Cloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dashboard.cloud.catalyst.net.nz/project/key_pairs&quot;&gt;Create an SSH key&lt;/a&gt; (or you can re-use an existing key of yours)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dashboard.cloud.catalyst.net.nz/project/networks/&quot;&gt;Create a network&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;name = deploy-network&lt;/li&gt;
&lt;li&gt;subnet = 192.168.x.0/254&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dashboard.cloud.catalyst.net.nz/project/routers/&quot;&gt;Create a router&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;name = deploy-router&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Add an interface to the router, connecting to the network&#39;s subnet&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dashboard.cloud.catalyst.net.nz/project/security_groups/&quot;&gt;Create a security group&lt;/a&gt; (or re-use an existing again)
&lt;ul&gt;
&lt;li&gt;name = deploy-secgroup&lt;/li&gt;
&lt;li&gt;ingress http, https, ssh for 0.0.0.0/0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dashboard.cloud.catalyst.net.nz/project/instances/&quot;&gt;Create and launch an instance&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;instance name = deploy-prototype&lt;/li&gt;
&lt;li&gt;source = ubuntu-22.04-x86_64&lt;/li&gt;
&lt;li&gt;flavor = c1.c2r4&lt;/li&gt;
&lt;li&gt;network = deploy-network&lt;/li&gt;
&lt;li&gt;security group =&lt;/li&gt;
&lt;li&gt;ssh key = yours&lt;/li&gt;
&lt;li&gt;launch instance&lt;/li&gt;
&lt;li&gt;associate a floating IP to the instance&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When it&#39;s done, you should be able to &lt;code&gt;ssh ubuntu@x.x.x.x&lt;/code&gt; where &lt;code&gt;x.x.x.x&lt;/code&gt; is the IP associated with the compute instance&lt;/p&gt;
&lt;p&gt;Next: &lt;a href=&quot;https://chris.bur.gs/post/2022-10-23-lemp-stack&quot;&gt;Set up a webserver environment&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Contriblog</title>
    <link href="https://chris.bur.gs/contriblog/"/>
    <updated>2022-09-25T04:26:00Z</updated>
    <id>https://chris.bur.gs/contriblog/</id>
    <content xml:lang="en" type="html">&lt;p&gt;ContribLog (&lt;a href=&quot;https://github.com/adaptdk/contrib&quot;&gt;adaptdk/contrib&lt;/a&gt;) is a tool for capturing contribution records on the commandline.&lt;/p&gt;
&lt;p&gt;On first run the user does some interactive setup of their organisation and first project. After that, users can select the project and interactively add contributions. Trying it out, it&#39;s a lightweight but manual method for capturing a personal record of contributions.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;www-data@ea69a85560cf:~$ ./contriblog add
The &amp;quot;contributions.yml&amp;quot; file does not exist yet. Do you want to generate initialize it? (y/n) y
[1/2] What is the name of the organization? Catalyst IT
What is main URL for the organization? https://catalyst.net.nz
[1/7] Which project received the contribution?
  [0] New project
 &amp;gt;
What is the machine name of the project? (E.g. drupal/migrate_plus): dmore/behat-chrome-extension
What is the name of the project? (E.g. Migrate Plus): Behat Chrome Extension
What is the main URL of the project? (E.g. https://www.drupal.org/project/migrate_plus): https://gitlab.com/behat-chrome/behat-chrome-extension
Please provide tags for the project, e.g. drupal (one per line)
Use EOL to finish, e.g. Ctrl+D on an empty line to finish input
behat
testing

[2/7] Please give the contribution a title: Some thing
[3/7] What was the main type of the contribution? [default: code]
  [code            ] Writing Code
  [code_review     ] Reviewing Code
  [triage          ] Bug Triaging
  [qa              ] Quality Assurance and Testing
  [security        ] Security-Related Activities
  [localization    ] Localization/L10N and Translation
  [event           ] Event Organization
  [documentation   ] Documentation Authorship
  [community       ] Community Building and Management
  [teaching        ] Teaching and Tutorial Building
  [troubleshooting ] Troubleshooting and Support
  [creative        ] Creative Work and Design
  [ux              ] User Interface, User Experience, and Accessibility
  [social          ] Social Media Management
  [user-support    ] User Support and Answering Questions
  [write           ] Writing Articles
  [public_relations] Public Relations - Interviews with Technical Press
  [speak           ] Speaking at Events
  [marketing       ] Marketing and Campaign Advocacy
  [website         ] Website Development
  [legal           ] Legal Council
  [financial       ] Financial Management
 &amp;gt; teaching
[4/7] Who is making the the contribution?
  [0] New person
 &amp;gt;
What is the name of the person? (E.g. José Saramago): Chris Burgess
What is the identifier for the person? (E.g. Jose): chris
[5/7] When was the contribution first published? (E.g. 2020-01-22) [default: 2022-09-25]:
[6/7] How would you describe the contribution? (multiline)
Use EOL to finish, e.g. Ctrl+D on an empty line to finish input
Teaching a workshop
[7/7] Please provide public links related to the contribution? (one per line)
Use EOL to finish, e.g. Ctrl+D on an empty line to finish input
https://gitlab.com/behat-chrome/behat-chrome-extension/-/issues/123
The file &amp;quot;contributions.yml&amp;quot; was updated correctly
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On second run, we won&#39;t get so many questions:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;www-data@ea69a85560cf:~$ ./contriblog add
[1/7] Which project received the contribution?
  [0] New project
  [1] dmore/behat-chrome-extension
 &amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And contributions are logged to a local &lt;code&gt;.yml&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;www-data@ea69a85560cf:~$ cat contributions.yml
organization:
    name: Catalyst IT
    url: &#39;https://catalyst.net.nz&#39;
people:
    xurizaemon: &#39;Chris Burgess&#39;
projects:
    dmore/behat-chrome-extension:
        name: &#39;Behat Chrome Extension&#39;
        url: &#39;https://gitlab.com/behat-chrome/behat-chrome-extension&#39;
        tags:
            - drupal
contributions:
    -
        project: catalyst-it/something
        title: &#39;Some thing&#39;
        type: teaching
        who: chris
        start: 2022-09-25T00:00:00+00:00
        description: &#39;Deliver presentation&#39;
        links:
            - &#39;https://github.com/catalyst-it/something/issues/123&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What I like about the tool is that it enables tracking of non-code contributions on an equal standing with code ones. This tool could suit CLI users if we asked staff to manually track contributions. It uses PHP, but could be packaged as a &lt;code&gt;phar&lt;/code&gt; for distribution.&lt;/p&gt;
&lt;h2&gt;Engari ko te raruraru&lt;/h2&gt;
&lt;p&gt;The chief challenge of this tool for me is ... it&#39;s a lot like timesheeting, right? Timesheeting is effort, and introducing a new task steals energy from other tasks. I sense anyone who&#39;s ever had to chase timesheets knows it&#39;s not reliable either.&lt;/p&gt;
&lt;p&gt;That&#39;s why I think that if we can do so responsibly, we should prefer unobtrusive tools to gather data in the first instance, and bolster automated approaches with ways of capturing this information together - perhaps a discussion roundup at the end of the week, in which we might capture a handful of contributions. A team&#39;s &lt;code&gt;contriblog&lt;/code&gt; output might be captured there and consumed by a tool like GrimoireLab.&lt;/p&gt;
&lt;p&gt;There are some other challenges/opportunities too - eg I see that the contribution is associated with an individual in the example above? Perhaps the tool could push contributions entered to a centralised (organisation or community) service as well. The UX is that of a decent prototype and could be improved.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Chaoss Metrics Test Run</title>
    <link href="https://chris.bur.gs/chaoss-metrics-test-run/"/>
    <updated>2022-09-25T03:23:00Z</updated>
    <id>https://chris.bur.gs/chaoss-metrics-test-run/</id>
    <content xml:lang="en" type="html">&lt;p&gt;&lt;a href=&quot;https://chaoss.community/metrics/&quot;&gt;CHAOSS Metrics&lt;/a&gt; is a series of proposed metrics for a project to assess its health. A metric might be something like &amp;quot;how many clones of this repo were made in the most recent 90 days&amp;quot;. Metrics such as these, proposed by CHAOSS for measuring OSS ecosystem health, could be useful for organisations or OSS communities to self-assess. This has the advantage that process doesn&#39;t need to be invented by each organisation individually, and that organisations can use a common yardstick to compare success.&lt;/p&gt;
&lt;p&gt;Metrics have their challenges (Goodhart&#39;s law: &amp;quot;When a measure becomes a target, it ceases to be a good measure.&amp;quot;, or &lt;a href=&quot;https://twitter.com/allenholub/status/1551218052448108544&quot;&gt;many of these quotes&lt;/a&gt;), so we have to use them carefully and appropriately. Measuring things is helpful when appropriate and these metrics seem to be a pretty well considered set. It&#39;s interesting to consider how these metrics might compare when evaluated against the needs of Catalyst as a business, and the needs of the OSS communities we engage with.&lt;/p&gt;
&lt;h2&gt;An example metric: Contributions - Clones&lt;/h2&gt;
&lt;p&gt;Number of clones in a time period is available from both Github and Gitlab APIs, apparently. A Git server would be able to report on this too - most would already have the data recorded in its logs.&lt;/p&gt;
&lt;p&gt;Trying to extract the data from Gitlab - using Gitlab&#39;s &lt;a href=&quot;https://gitlab.com/-/graphql-explorer&quot;&gt;GraphQL explorer&lt;/a&gt;, I don&#39;t see this stat available in GraphQL:&lt;/p&gt;
&lt;pre class=&quot;language-graphql&quot;&gt;&lt;code class=&quot;language-graphql&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Query for project details.&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token keyword&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token property-query&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token attr-name&quot;&gt;fullPath&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;behat-chrome/behat-chrome-extension&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token property&quot;&gt;id&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token object&quot;&gt;statistics&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;containerRegistrySize&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;pipelineArtifactsSize&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;snippetsSize&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;uploadsSize&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token property&quot;&gt;wikiSize&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Curl has it:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--header&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;PRIVATE-TOKEN: &lt;span class=&quot;token variable&quot;&gt;$GITLAB_TOKEN&lt;/span&gt;&quot;&lt;/span&gt; https://gitlab.com/api/v4/projects/3329075/statistics&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But, it&#39;s forbidden for projects you aren&#39;t admin of? Got a 403 for &lt;a href=&quot;https://gitlab.com/api/v4/projects/278964/statistics&quot;&gt;https://gitlab.com/api/v4/projects/278964/statistics&lt;/a&gt; (gitlab&#39;s repo).&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Drupal Core patch testing locally with Lando</title>
    <link href="https://chris.bur.gs/drupal-core-patch-testing-lando/"/>
    <updated>2021-12-22T22:33:00Z</updated>
    <id>https://chris.bur.gs/drupal-core-patch-testing-lando/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Testing patches for Drupal core can be fiddly, but it&#39;s a process worth making easy so we can better engage in upstream fixes.&lt;/p&gt;
&lt;p&gt;Here&#39;s my current approach (as of 2021).&lt;/p&gt;
&lt;h2&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;You&#39;ll need Lando installed, which requires Docker as well.&lt;/p&gt;
&lt;h2&gt;Upstream codebase&lt;/h2&gt;
&lt;p&gt;I keep a long-lived copy of the Drupal core codebase in &lt;code&gt;~/Projects/drupal-core&lt;/code&gt;. This is a git clone of &lt;a href=&quot;https://git.drupalcode.org/project/drupal.git&quot;&gt;https://git.drupalcode.org/project/drupal.git&lt;/a&gt; and the workflow is either to create patches for upload (old style) or use Gitlab&#39;s merge requests (new style) to submit proposed changes.&lt;/p&gt;
&lt;p&gt;So, step one: clone a copy of Drupal core! Note that I change the cloned repo name to &amp;quot;drupal-core&amp;quot; for my own sanity.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd ~/Projects # or whereever you&#39;d like to keep it
git clone --branch=9.4.x https://git.drupalcode.org/project/drupal.git drupal-core
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can switch between branches of upstream, but we want to default to the current dev branch for proposed changes. Most patches will apply to your current version.&lt;/p&gt;
&lt;h2&gt;Local Lando environment&lt;/h2&gt;
&lt;p&gt;I like Lando and this setup works for me. We need to add a handful of files into the core codebase, and then not accidentally commit those files. These files are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.lando.yml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.lando.base.yml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;phpunit.xml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sites/default/settings.php&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It may feel unfamiliar that the Drupal core codebase has the codebase as the root of the repo - there&#39;s no &lt;code&gt;web/&lt;/code&gt; subdirectory. For projects, we use the &lt;code&gt;web/&lt;/code&gt; subdirectory, but for core, we don&#39;t use that. 🤷 It&#39;s OK!&lt;/p&gt;
&lt;h3&gt;Additions&lt;/h3&gt;
&lt;p&gt;These are the files I put into the Drupal codebase:&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;.lando.yml&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;This file and the next could be combined, and in this project there&#39;s little advantage to the separation. However, I track these Lando configs across multiple projects, so the separation is an advantage to me in that I can copy changes to the base file more easily across &lt;a href=&quot;https://gitlab.com/xurizaemon/commerce-demo/&quot;&gt;various&lt;/a&gt; &lt;a href=&quot;https://gitlab.com/xurizaemon/drupal-contrib&quot;&gt;Drupal&lt;/a&gt; &lt;a href=&quot;https://gitlab.com/xurizaemon/drupal-contrib&quot;&gt;projects&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; drupal&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;core&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;# Change to `true` and run `lando rebuild` to enable XDebug.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;xdebug&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;false&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;code&gt;.lando.base.yml&lt;/code&gt;&lt;/h4&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; unconfigured&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;recipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; drupal9&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;env_file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; .env&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;webroot&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; .&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;database&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; mysql&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;via&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;appserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;build_as_root&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; apt update &lt;span class=&quot;token important&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt install libxslt1&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;dev &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;y &lt;span class=&quot;token important&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;php&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;ext&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;install xsl&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; composer install &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;prefer&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;dist&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;mailhog&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; mailhog&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; node&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;14&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;tooling&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;phpunit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; appserver&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;cmd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;php /app/vendor/bin/phpunit -c /app/phpunit.xml&quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;phpcs-install&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; appserver&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;cmd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; composer require &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;dev squizlabs/php_codesniffer&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; phpcs &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;config&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;set installed_paths /app/vendor/drupal/coder/coder_sniffer&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;phpcs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; appserver&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;cmd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; /app/vendor/bin/phpcs &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;standard=Drupal&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;DrupalPractice&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;phpcbf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; appserver&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;cmd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; /app/vendor/bin/phpcbf &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;standard=Drupal&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;DrupalPractice&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;site-install&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; appserver&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;cmd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; drush site&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;install &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;y&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;code&gt;phpunit.xml&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;This configures PHPUnit to run in this environment.&lt;/p&gt;
&lt;pre class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;&lt;span class=&quot;token prolog&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- TODO set checkForUnintentionallyCoveredCode=&quot;true&quot; once https://www.drupal.org/node/2626832 is resolved. --&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- PHPUnit expects functional tests to be run with either a privileged user&lt;br /&gt; or your current system user. See core/tests/README.md and&lt;br /&gt; https://www.drupal.org/node/2116263 for details.&lt;br /&gt;--&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;phpunit&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;xmlns:&lt;/span&gt;xsi&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://www.w3.org/2001/XMLSchema-instance&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;bootstrap&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;/app/core/tests/bootstrap.php&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;colors&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;true&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;beStrictAboutTestsThatDoNotTestAnything&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;true&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;beStrictAboutOutputDuringTests&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;true&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;beStrictAboutChangesToGlobalState&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;true&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;failOnWarning&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;true&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;printerClass&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&#92;Drupal&#92;Tests&#92;Listeners&#92;HtmlOutputPrinter&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;cacheResult&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;false&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;&lt;span class=&quot;token namespace&quot;&gt;xsi:&lt;/span&gt;noNamespaceSchemaLocation&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;https://schema.phpunit.de/9.3/phpunit.xsd&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;php&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- Set error reporting to E_ALL. --&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;ini&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;error_reporting&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;32767&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- Do not limit the amount of memory tests take to run. --&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;ini&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;memory_limit&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;-1&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- Example SIMPLETEST_BASE_URL value: http://localhost --&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;env&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;SIMPLETEST_BASE_URL&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;http://appserver_nginx&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;force&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;true&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- Example SIMPLETEST_DB value: mysql://username:password@localhost/databasename#table_prefix --&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;env&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;SIMPLETEST_DB&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- Example BROWSERTEST_OUTPUT_DIRECTORY value: /path/to/webroot/sites/simpletest/browser_output --&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;env&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;BROWSERTEST_OUTPUT_DIRECTORY&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- To have browsertest output use an alternative base URL. For example if&lt;br /&gt;     SIMPLETEST_BASE_URL is an internal DDEV URL, you can set this to the&lt;br /&gt;     external DDev URL so you can follow the links directly.&lt;br /&gt;    --&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;env&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;BROWSERTEST_OUTPUT_BASE_URL&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- To disable deprecation testing completely uncomment the next line. --&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- &amp;lt;env name=&quot;SYMFONY_DEPRECATIONS_HELPER&quot; value=&quot;disabled&quot;/&gt; --&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- Example for changing the driver class for mink tests MINK_DRIVER_CLASS value: &#39;Drupal&#92;FunctionalJavascriptTests&#92;DrupalSelenium2Driver&#39; --&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;env&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;MINK_DRIVER_CLASS&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- Example for changing the driver args to mink tests MINK_DRIVER_ARGS value: &#39;[&quot;http://127.0.0.1:8510&quot;]&#39; --&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;env&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;MINK_DRIVER_ARGS&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- Example for changing the driver args to webdriver tests MINK_DRIVER_ARGS_WEBDRIVER value: &#39;[&quot;chrome&quot;, { &quot;chromeOptions&quot;: { &quot;w3c&quot;: false } }, &quot;http://localhost:4444/wd/hub&quot;]&#39; For using the Firefox browser, replace &quot;chrome&quot; with &quot;firefox&quot; --&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;env&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;MINK_DRIVER_ARGS_WEBDRIVER&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;php&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;testsuites&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;testsuite&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;unit&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;./core/tests/TestSuites/UnitTestSuite.php&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;testsuite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;testsuite&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;kernel&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;./core/tests/TestSuites/KernelTestSuite.php&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;testsuite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;testsuite&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;functional&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;./core/tests/TestSuites/FunctionalTestSuite.php&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;testsuite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;testsuite&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;functional-javascript&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;./core/tests/TestSuites/FunctionalJavascriptTestSuite.php&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;testsuite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;testsuite&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;build&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;./core/tests/TestSuites/BuildTestSuite.php&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;testsuite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;testsuites&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;listeners&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;listener&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&#92;Drupal&#92;Tests&#92;Listeners&#92;DrupalListener&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;listener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;listeners&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- Filter for coverage reports. --&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;phpunit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;code&gt;sites/default/settings.php&lt;/code&gt;&lt;/h4&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-php&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;//$settings[&#39;config_sync_directory&#39;] = &#39;../config/drupal/sync&#39;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token variable&quot;&gt;$databases&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;default&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;default&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;array&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;database&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;drupal9&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;username&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;drupal9&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;password&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;drupal9&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;prefix&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;host&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;database&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;port&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;namespace&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;Drupal&#92;&#92;Core&#92;&#92;Database&#92;&#92;Driver&#92;&#92;mysql&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;driver&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;mysql&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;How to use it&lt;/h3&gt;
&lt;p&gt;With these in place, I should be able to execute &lt;code&gt;lando start&lt;/code&gt; and bring up a workable Drupal 9 site.&lt;/p&gt;
&lt;p&gt;More importantly, I should be able to execute &lt;code&gt;lando phpunit&lt;/code&gt; to run core tests.&lt;/p&gt;
&lt;p&gt;I&#39;ll now follow the instructions from the issue to check out a local working branch with the existing MR or patch applied.&lt;/p&gt;
&lt;p&gt;I can use PHPUnit&#39;s &lt;code&gt;--group&lt;/code&gt; and &lt;code&gt;--filter&lt;/code&gt; to run specific tests (eg those related to the issue, or those already failing).&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Dependency Track</title>
    <link href="https://chris.bur.gs/dependency-track/"/>
    <updated>2021-11-10T19:51:00Z</updated>
    <id>https://chris.bur.gs/dependency-track/</id>
    <content xml:lang="en" type="html">&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/luwOif1mSmo?start=1290&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;Below are my notes for the lightning talk above, given at DrupalSouth Shorts 2021.&lt;/p&gt;
&lt;h2&gt;The situation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;We like components, they allow composition of complex things&lt;/li&gt;
&lt;li&gt;This means we need to understand the components we&#39;re using&lt;/li&gt;
&lt;li&gt;As an organisation, we&#39;ll have many projects with many components&lt;/li&gt;
&lt;li&gt;There are advantages to understanding this - reducing complexity by consolidation&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Our context, briefly&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Many DrupalSouths ago (2010?!), Catalyst released Archimedes&lt;/li&gt;
&lt;li&gt;Over time, Archimedes became Mataara&lt;/li&gt;
&lt;li&gt;Other tools exist for various spaces - what system packages are installed, host info etc&lt;/li&gt;
&lt;li&gt;We want to consolidate on process and reduce complexity - better team process, less siloing/divergence&lt;/li&gt;
&lt;li&gt;For the same reasons as we might use a tool like this, 11 years later we don&#39;t need to write &lt;em&gt;our own&lt;/em&gt; tool&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Software composition analysis for development&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;For a Drupal project, hundreds (to thousands) of packages in the dependency tree&lt;/li&gt;
&lt;li&gt;Applies outside Composer - NPM, frameworks, containers, OS, firmware&lt;/li&gt;
&lt;li&gt;What are we running? What else is running x?&lt;/li&gt;
&lt;li&gt;What vulnerabilities do we need to care about?&lt;/li&gt;
&lt;li&gt;What license considerations are there?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Dependency Track&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Enter Dependency Track, an OWASP project to analyse, process and report on composition&lt;/li&gt;
&lt;li&gt;v4.3.6 today&lt;/li&gt;
&lt;li&gt;Ran it up in Catalyst Cloud for purpose of demo&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;How it works&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;BOM = Bill of Materials&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Uses a BOM format called CycloneDX&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Turns composer.json/lock, package.json/package-lock.json into BOM&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Automated inbound data (via your CI)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Imports into Dependency Track, analyses &amp;amp; extracts metadata (version, license, ...)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Sources vulnerability intelligence externally - public and licensed&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure to apply your organisational rules - license, reporting, alerts&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Docker install was pretty easy, maybe configure SSL first cos CORS failure + static frontend took a minute to spot&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Give it lots of RAM, it has to eat all the vulns that came out in 2005 - failed at 4GB, worked at 16GB, thanks Catalyst Cloud :)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Give it half an hour to haul all the data in initially - you can use it in that time - and let it chug through analysis on its own time&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Feed it from CI or manually at first&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What do you get?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;High level reporting of vulnerabilities&lt;/li&gt;
&lt;li&gt;Alerting / notification for newly identified vulnerabilities&lt;/li&gt;
&lt;li&gt;Identify policy violations on licensing&lt;/li&gt;
&lt;li&gt;Drill into projects, components, search, report&lt;/li&gt;
&lt;li&gt;Export report data for signoff&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What can this mean&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Reference for developers considering a new component&lt;/li&gt;
&lt;li&gt;Added review step for new components - firming up our approach here&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What else is there?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Tidelift provides a similar paid, hosted service&lt;/li&gt;
&lt;li&gt;... tell me about other things?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;What did you learn?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Some projects aren&#39;t licensed!&lt;/li&gt;
&lt;li&gt;Some repackage licensed things without add a license!&lt;/li&gt;
&lt;li&gt;Vulnerabilities view shows all known vulns?&lt;/li&gt;
&lt;li&gt;Lots of CLI tools to handle turning everybody&#39;s Gemfile/Composer/whatever into a BOM&lt;/li&gt;
&lt;li&gt;Had to remove Byte Order Markers from Bill of Materials XML&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Update. Your. Shit.</title>
    <link href="https://chris.bur.gs/update-your-shit/"/>
    <updated>2021-09-27T22:05:00Z</updated>
    <id>https://chris.bur.gs/update-your-shit/</id>
    <content xml:lang="en" type="html">&lt;p&gt;A few years back, I was working on a migration project. A project which shall not be named. Tonight I was reminded of a very particular thing I ran into there.&lt;/p&gt;
&lt;p&gt;As part of the migration process, I had to regularly reload DBs from the customer source. That process took a long time. The compressed DBs weren&#39;t big, but the DB loading took ... longer than felt right.&lt;/p&gt;
&lt;p&gt;Eventually, and with the time that you end up with waiting on a slow task, I made an effort to ask myself why. Looking, I saw the DB load spent ages writing to a particular table: &lt;code&gt;{epsacrop_files}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This table stored image crop settings for every image in the Drupal content DB. The intent was to store an array of dimensions, but a wrong JS expectation in the module&#39;s admin UI -&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; presets &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;presets&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1234&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;123&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;456&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token comment&quot;&gt;// presets.length = 1235&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;had resulted in every image stored being padded with a vast number of &lt;code&gt;NULL&lt;/code&gt; values before you got to the dimensions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This bug (&lt;a href=&quot;https://www.drupal.org/project/epsacrop/issues/1823940&quot;&gt;epsacrop#1823940: Simplify data storage to avoid massive amounts of null values&lt;/a&gt; was fixed in 2015, but the copy of the site we&#39;d been supplied was still using a version that predated the fix, and so the busy content DB had many GiB of NULL in it. A massive DB table full of almost entirely NULL, NULL, NULL, NULL.&lt;/p&gt;
&lt;p&gt;What a waste of developer time, leaving that in the system for years!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Drupal Core Patches With Lando</title>
    <link href="https://chris.bur.gs/drupal-core-patches-with-lando/"/>
    <updated>2021-09-13T01:55:00Z</updated>
    <id>https://chris.bur.gs/drupal-core-patches-with-lando/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I have been working on a tidy setup for contribution using available community tools, and today I needed the same for core. Core patches and Contrib have slightly different workflows.&lt;/p&gt;
&lt;p&gt;Anyway, it turned out really easy to do this.&lt;/p&gt;
&lt;h2&gt;Get your codebase&lt;/h2&gt;
&lt;p&gt;I already had a copy of the Drupal core codebase in &lt;code&gt;~/Projects/src/drupal&lt;/code&gt;, checked out to my branch (&lt;code&gt;3232222-pager-no-h4&lt;/code&gt;).&lt;/p&gt;
&lt;h2&gt;Copy in &lt;code&gt;.lando.*&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;I copied in the following two Lando &lt;code&gt;.yml&lt;/code&gt; files:&lt;/p&gt;
&lt;h3&gt;.lando.yml&lt;/h3&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; drupal&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;core&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;# Change to `true` and run `lando rebuild` to enable XDebug.&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;xdebug&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;false&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;.lando.base.yml&lt;/h3&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; unconfigured&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;recipe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; drupal9&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;env_file&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; .env&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;webroot&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; .&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;database&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; mysql&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;via&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; nginx&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;appserver&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;build_as_root&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; apt update &lt;span class=&quot;token important&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt install libxslt1&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;dev &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;y &lt;span class=&quot;token important&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;php&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;ext&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;install xsl&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; composer install &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;prefer&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;dist&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;memcache&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; memcached&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;mailhog&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; mailhog&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; node&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;14&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;tooling&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;phpunit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; appserver&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;cmd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;php /app/vendor/bin/phpunit -c /app/core/phpunit.xml.dist&quot;&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;# lando composer require --dev squizlabs/php_codesniffer&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token comment&quot;&gt;# lando phpcs --config-set installed_paths /app/vendor/drupal/coder/coder_sniffer&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;phpcs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; appserver&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;cmd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; /app/vendor/bin/phpcs &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;standard=Drupal&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;DrupalPractice&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;phpcbf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; appserver&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;cmd&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; /app/vendor/bin/phpcbf &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;standard=Drupal&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;DrupalPractice&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Start your engines&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;lando start&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;Run tests&lt;/h2&gt;
&lt;p&gt;PHPUnit has various ways of filtering which tests to run. In this case I had only one test I needed to run:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;lando phpunit core/tests/Drupal/KernelTests/Core/Theme/ConfirmClassyCopiesTest.php&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;It&#39;s fast :)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ lando phpunit core/tests/Drupal/KernelTests/Core/Theme/ConfirmClassyCopiesTest.php
PHPUnit 8.5.19 by Sebastian Bergmann and contributors.

Testing Drupal&#92;KernelTests&#92;Core&#92;Theme&#92;ConfirmClassyCopiesTest
.....                                                               5 / 5 (100%)

Time: 2.23 seconds, Memory: 6.00 MB
OK (5 tests, 2481 assertions)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Upload your changes&lt;/h2&gt;
&lt;p&gt;Pushed my changes to the merge request per the instructions on the issue. Done!&lt;/p&gt;
&lt;p&gt;For more information on contributing to Drupal, see &lt;a href=&quot;https://www.drupal.org/community/contributor-guide/reference-information/quick-info&quot;&gt;Contributor Guide: Quick Info&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Testing a fork of Sector distribution</title>
    <link href="https://chris.bur.gs/composer-sector-testing-fork/"/>
    <updated>2021-08-03T12:44:00Z</updated>
    <id>https://chris.bur.gs/composer-sector-testing-fork/</id>
    <content xml:lang="en" type="html">&lt;p&gt;This is my notes on how to test a fork of Sector. This is tricky because we are using &lt;code&gt;composer create-project&lt;/code&gt; and Composer needs to recursively resolve package dependencies, and we&#39;re testing a change in a required package.&lt;/p&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;We want to test that running &lt;code&gt;composer create-project sparksinteractive/sector-project&lt;/code&gt; will do the right thing with a patch which we&#39;ve submitted to a dependency, &lt;code&gt;sparksinteractive/sector-distribution&lt;/code&gt;. Our change will be that we are modifying &lt;code&gt;composer.json&lt;/code&gt; in the &lt;em&gt;distribution&lt;/em&gt; project to use an updated patch to another contrib module.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sector-project is the template, which is used with &lt;code&gt;composer create-project&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;sector-distribution is the profile, which depends on a large number of contrib modules&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Preparation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Identify your working directory&lt;/li&gt;
&lt;li&gt;Clone both projects there&lt;/li&gt;
&lt;li&gt;Modify &lt;code&gt;sector-project&lt;/code&gt; repository to incorporate the local copy of &lt;code&gt;sector-distribution&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Modify &lt;code&gt;sector-distribution&lt;/code&gt; to incorporate the upstream patch&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;create-project&lt;/code&gt; with some additional args&lt;/li&gt;
&lt;li&gt;Verify that the patch is applied&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Caveats&lt;/h2&gt;
&lt;p&gt;If Composer appears to not notice your changes, clear your Composer cache with &lt;code&gt;composer clearcache&lt;/code&gt;. This can add ~5min to your next download run.&lt;/p&gt;
&lt;h2&gt;Process&lt;/h2&gt;
&lt;h3&gt;Identify your working directory&lt;/h3&gt;
&lt;p&gt;I&#39;m going to do this in &lt;code&gt;/tmp&lt;/code&gt; so all paths are short.&lt;/p&gt;
&lt;h3&gt;Clone both projects there&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;cd /tmp
git clone https://github.com/sparksi/sector-distribution.git sector-distribution
git clone https://github.com/sparksi/sector-project.git sector-project
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Modify &lt;code&gt;sector-project&lt;/code&gt; to reference the local copy of &lt;code&gt;sector-distribution&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;In &lt;code&gt;/tmp/sector-project&lt;/code&gt;, modify &lt;code&gt;composer.json&lt;/code&gt; to add a new local repository entry in the &lt;code&gt;repositories&lt;/code&gt; key.&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;commit ee8af146a1643f3cfce4de6fafe00af77ab227a2 (HEAD -&gt; local-distro)&lt;br /&gt;Author: Chris Burgess &amp;lt;chris.burgess@catalyst.net.nz&gt;&lt;br /&gt;Date:   Tue Aug 3 23:44:53 2021 +1200&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;   Use local copy of sector-distribution&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;diff --git a/composer.json b/composer.json&lt;br /&gt;index d594d36..20e3bf0 100644&lt;br /&gt;&lt;span class=&quot;token coord&quot;&gt;--- a/composer.json&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token coord&quot;&gt;+++ b/composer.json&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token coord&quot;&gt;@@ -39,6 +39,16 @@&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;        }&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;    },&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;    &quot;repositories&quot;: {&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;        &quot;sector-distribution-local&quot;: {&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;            &quot;type&quot;: &quot;path&quot;,&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;            &quot;url&quot;: &quot;/tmp/sector-distribution&quot;,&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;            &quot;options&quot;: {&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;                &quot;symlink&quot;: false,&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;                &quot;versions&quot;: {&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;                    &quot;sparksinteractive/sector-distribution&quot;: &quot;9.1.4&quot;&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;                }&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;            }&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;        },&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;        &quot;packagist.drupal&quot;: {&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;            &quot;type&quot;: &quot;composer&quot;,&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;            &quot;url&quot;: &quot;https://packages.drupal.org/8&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;key&lt;/th&gt;
&lt;th&gt;value&lt;/th&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;type&lt;/td&gt;
&lt;td&gt;path&lt;/td&gt;
&lt;td&gt;use the current state of this local directory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;url&lt;/td&gt;
&lt;td&gt;/tmp/sector-distribution&lt;/td&gt;
&lt;td&gt;the local path to your modified distribution codebase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;options.symlink&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;copy the files into place&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;options.versions&lt;/td&gt;
&lt;td&gt;sparksinteractive/sector-distribution: 9.1.4&lt;/td&gt;
&lt;td&gt;increment above the current release&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Modify &lt;code&gt;sector-distribution&lt;/code&gt; to reference the upstream patch we want&lt;/h3&gt;
&lt;p&gt;In &lt;code&gt;/tmp/sector-distribution&lt;/code&gt;, modify &lt;code&gt;composer.json&lt;/code&gt; to update the patch URL we want to change. (This could be any other change to the local distro codebase.)&lt;/p&gt;
&lt;pre class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;commit a5dd78ffbb61667f5a191467f6d767d980b6f01c (HEAD -&gt; patch-updates)&lt;br /&gt;Author: Chris Burgess &amp;lt;chris.burgess@catalyst.net.nz&gt;&lt;br /&gt;Date:   Tue Aug 3 23:47:49 2021 +1200&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;   Update patch for Menu Block&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;diff --git a/composer.json b/composer.json&lt;br /&gt;index be7ab2e..aa931e4 100644&lt;br /&gt;&lt;span class=&quot;token coord&quot;&gt;--- a/composer.json&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token coord&quot;&gt;+++ b/composer.json&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token coord&quot;&gt;@@ -103,7 +103,7 @@&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;        &quot;patches&quot;: {&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;            &quot;drupal/menu_block&quot;: {&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;                &quot;No contextual link to edit menu&quot;: &quot;https://www.drupal.org/files/issues/2799821-2.menu_block.contextual_links.patch&quot;,&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token deleted-sign deleted&quot;&gt;&lt;span class=&quot;token prefix deleted&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;                &quot;Hide menu block title if empty&quot;: &quot;https://www.drupal.org/files/issues/2018-05-08/menu_block-hide_block_if_no_links-2757215-9.patch&quot;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token inserted-sign inserted&quot;&gt;&lt;span class=&quot;token prefix inserted&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;                &quot;Hide menu block title if empty&quot;: &quot;https://www.drupal.org/files/issues/2020-02-12/menu_block-hide_block_if_no_links-2757215-13.patch&quot;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token unchanged&quot;&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;            },&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;            &quot;drupal/entity_usage&quot;: {&lt;br /&gt;&lt;/span&gt;&lt;span class=&quot;token prefix unchanged&quot;&gt; &lt;/span&gt;&lt;span class=&quot;token line&quot;&gt;                &quot;Better tracking per entity&quot;: &quot;https://www.drupal.org/files/issues/2020-06-16/per-node-tracking_3151920_4.patch&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Run &lt;code&gt;create-project&lt;/code&gt; with the additional args required&lt;/h3&gt;
&lt;p&gt;Switch back to the parent directory, and run &lt;code&gt;composer create-project&lt;/code&gt; with a &lt;code&gt;--repository&lt;/code&gt; parameter which will instruct Composer to discover the local copy of &lt;code&gt;sector-project&lt;/code&gt; and create the templated site in subdirectory &lt;code&gt;sector-test&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd /tmp
composer create-project --prefer-dist --repository=&#39;{&amp;quot;type&amp;quot;:&amp;quot;path&amp;quot;, &amp;quot;url&amp;quot;:&amp;quot;/tmp/sector-project&amp;quot;,&amp;quot;options&amp;quot;:{&amp;quot;symlink&amp;quot;:false, &amp;quot;versions&amp;quot;:{&amp;quot;sparksinteractive/sector-project&amp;quot;:&amp;quot;9.1.4&amp;quot;}}}&#39; sparksinteractive/sector-project sector-test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Keys and values here are much as documented in the changes for &lt;code&gt;sector-project&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Verify that the output contains the correct applied patch&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;  - Applying patches for drupal/menu_block
    https://www.drupal.org/files/issues/2799821-2.menu_block.contextual_links.patch (No contextual link to edit menu)
    https://www.drupal.org/files/issues/2020-02-12/menu_block-hide_block_if_no_links-2757215-13.patch (Hide menu block title if empty)
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>Review Achievements</title>
    <link href="https://chris.bur.gs/review-achievements/"/>
    <updated>2021-04-29T21:34:00Z</updated>
    <id>https://chris.bur.gs/review-achievements/</id>
    <content xml:lang="en" type="html">&lt;p&gt;At the end of 2020 I was disappointed to realise with COVID that we hadn&#39;t dived into our usual annual review, and I did my own personal annual review over the new years break which was really helpful to me. (Also, it didn&#39;t just cover work things!)&lt;/p&gt;
&lt;p&gt;When I talked to my manager in early 2021 about the GROW/GLOBE/GROWTH worksheet, I realised I had this in my journal upstairs. While my review was &lt;em&gt;a lot&lt;/em&gt; due to some challenging things I&#39;d been dealing with, I was really pleased to have a solid list of achievements for the year at hand!&lt;/p&gt;
&lt;p&gt;Here&#39;s what I had:&lt;/p&gt;
&lt;h2&gt;Achievements&lt;/h2&gt;
&lt;p&gt;Maintained &amp;amp; improved my development and project delivery standards and practices over the year, with reference to the wider open source community and Catalyst&#39;s internal standards eg The Catalyst Test.&lt;/p&gt;
&lt;p&gt;Worked to deliver positive change through leading by example. This included:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Continuing program of &lt;strong&gt;establishing or improving test / CI coverage&lt;/strong&gt; across all projects I engaged with.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Advocating for and normalising code review&lt;/strong&gt; as an requirement of project process.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Improving project documentation&lt;/strong&gt; to ensure clear guidance on process and future maintainability.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Contributed to RFPs&lt;/strong&gt; for Ministry of Education, Treasury, LINZ and (less significantly) NZ Police. This was a new and challenging area of work to contribute to, and I felt that my efforts were valued and we achieved good outcomes. I enjoy the communication and writing aspects of this work.&lt;/p&gt;
&lt;p&gt;Provided &lt;strong&gt;peer support and training&lt;/strong&gt;. Supported team members to become more familiar with testing and CI process, active presence in chat channels to assist developers on their journey.&lt;/p&gt;
&lt;p&gt;Ensured that I &lt;strong&gt;documented clearly whenever possible&lt;/strong&gt;. I made &lt;a href=&quot;https://wiki.wgtn.cat-it.co.nz/w/index.php?limit=500&amp;amp;title=Special%3AContributions&amp;amp;contribs=user&amp;amp;target=Chrisburgess&amp;amp;namespace=&amp;amp;tagfilter=&amp;amp;start=2020-01-01&amp;amp;end=2020-12-31&amp;amp;hideMinor=1&quot;&gt;384 edits to the Catalyst Wiki in 2020&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Organised and facilitated workshop&lt;/strong&gt; in Christchurch to share knowledge around CI and testing process. Followed up with additional support on CI improvements across projects for other team members.&lt;/p&gt;
&lt;p&gt;Maintained &amp;amp; &lt;strong&gt;developed presence in Drupal community&lt;/strong&gt; by learning and sharing in the open. Attended DrupalSouth 2021 virtual conference, awarded one of several event prizes for engagement. Regularly active in regional and topical Drupal chat channels.&lt;/p&gt;
&lt;p&gt;Continued &lt;strong&gt;contributing to various open source projects &amp;amp; communities&lt;/strong&gt;. See:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://gitlab.com/users/xurizaemon&quot;&gt;https://gitlab.com/users/xurizaemon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xurizaemon&quot;&gt;https://github.com/xurizaemon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.drupal.org/user/76026/track/code&quot;&gt;https://www.drupal.org/user/76026/track/code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Contributed to &lt;strong&gt;Matrix chat rollout&lt;/strong&gt; with testing, documentation, planning and bot development. My &lt;a href=&quot;https://wiki.wgtn.cat-it.co.nz/wiki/Catsup&quot;&gt;Catathon project from 2018&lt;/a&gt; formed the basis for a Matrix-compatible bot implementation, which includes &lt;a href=&quot;https://bots.pages.gitlab.wgtn.cat-it.co.nz/misc/&quot;&gt;thorough documentation&lt;/a&gt;. Wormy is risen!&lt;/p&gt;
&lt;p&gt;Engaged in collaborative technical leadership through &lt;strong&gt;active sharing of development process &amp;amp; learning&lt;/strong&gt; - see &lt;a href=&quot;https://chrisburgess.pages.gitlab.wgtn.cat-it.co.nz/working-loud/&quot;&gt;Working Loud blog&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Established &lt;strong&gt;personal support / development relationship&lt;/strong&gt; with staff from another team for co-mentoring &amp;amp; peer development.&lt;/p&gt;
&lt;p&gt;Asked to guide and direct &lt;strong&gt;establishment of reproducible Drupal project processes and practices&lt;/strong&gt; in the scope of the Treasury project.&lt;/p&gt;
&lt;p&gt;In the first quarter of 2021 I&#39;ve been &lt;strong&gt;nominated and elected to the Eight&lt;/strong&gt; and invited to &lt;strong&gt;participate in the Inclusivity Team&lt;/strong&gt;. I&#39;m really happy to be engaging in these places as they add depth to my technical work and allow me to support some of the special culture Catalyst has.&lt;/p&gt;
&lt;p&gt;I&#39;ve worked to ensure our &lt;strong&gt;team are co-ordinating and communicating&lt;/strong&gt; in our various systems - organising team meetings, including all members correctly in email distribution lists, raising allocation gaps on projects, and contributing to our best practices WRs and issue queue.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Work and Trails</title>
    <link href="https://chris.bur.gs/work-and-trails/"/>
    <updated>2021-04-28T06:55:00Z</updated>
    <id>https://chris.bur.gs/work-and-trails/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I had an experience today where someone said, &amp;quot;can I do that, I have a special way to do it&amp;quot;. Then they took over the task and made a small change to the process which improved it. Job done, and great - since I saw the approach, I can use that next time.&lt;/p&gt;
&lt;p&gt;It got me thinking about types of task though, and allocation within teams. Let&#39;s turn this into a model for deciding who should take that ticket off the board.&lt;/p&gt;
&lt;p&gt;Let&#39;s say there&#39;s ten tasks on the board. Some of those tasks demand in depth knowledge and creative solutions, and some are more ordinary problems. Some are trail-blazing, and some are walks on a trail we know.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I don&#39;t like &amp;quot;junior/senior&amp;quot; terminology but I&#39;ll use it here for now.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If our senior developer picks up a task that the junior could have done with a little guidance, then the senior is wasting both their own resource and a learning opportunity for the team.&lt;/p&gt;
&lt;p&gt;So, when you allocate a task, ask yourself: do we need to trailblaze&lt;sup class=&quot;footnote-ref&quot;&gt;&lt;a href=&quot;https://chris.bur.gs/work-and-trails/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt; here, or could we show someone to the start of the path and let them take it from there?&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep&quot; /&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;There&#39;s a whole lot more to discuss in resisting the desire to innovate when solutions exist. Another time! &lt;a href=&quot;https://chris.bur.gs/work-and-trails/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
  </entry>
  <entry>
    <title>Team Resources</title>
    <link href="https://chris.bur.gs/team-resources/"/>
    <updated>2021-04-15T07:20:00Z</updated>
    <id>https://chris.bur.gs/team-resources/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I&#39;ve been gathering a bunch of reading on teams and figured I might collect some of it in a post.&lt;/p&gt;
&lt;h2&gt;Who&#39;s in the room&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.hiremorewomenintech.com/&quot;&gt;Hire More Women In Tech&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://gender-decoder.katmatfield.com/&quot;&gt;Gender Decoder&lt;/a&gt; and &lt;a href=&quot;https://joblint.org/&quot;&gt;JobLint&lt;/a&gt;, tools to check your job listing for gendered smells&lt;/li&gt;
&lt;li&gt;There&#39;s more to consider here than gender!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Interviewing&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.keyvalues.com/culture-queries&quot;&gt;Culture Queries&lt;/a&gt; - questions to ask if you&#39;re being interviewed for a position. Also if you&#39;re reflecting on your current environment.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Getting Better&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://career-ladders.dev/&quot;&gt;Career Ladders&lt;/a&gt; by &lt;a href=&quot;https://twitter.com/sarah_edo&quot;&gt;@sarah_edo&lt;/a&gt; &amp;amp; pals&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.progression.fyi/&quot;&gt;progression.fyi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.progression.team/podcast&quot;&gt;Progression Podcast&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Values&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://about.gitlab.com/handbook/values/&quot;&gt;Gitlab&#39;s Values&lt;/a&gt; - I love this, myself.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.keyvalues.com/github&quot;&gt;Github Values&lt;/a&gt; - some good stuff here too, but Gitlab&#39;s resonates with me a lot more.&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Drush SQL Sanitize Config</title>
    <link href="https://chris.bur.gs/drush-sql-sanitize-config/"/>
    <updated>2021-04-13T08:38:00Z</updated>
    <id>https://chris.bur.gs/drush-sql-sanitize-config/</id>
    <content xml:lang="en" type="html">&lt;p&gt;A handy snippet to sanitize DB snippets:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# drush/drush.yml
# ...
sql:
  structure-tables:
    # Data to exclude from default sync/dump.
    common:
      - cache
      - &#39;cache_*&#39;
      - history
      - queue
      - &#39;search_*&#39;
      - &#39;session&#39;
      - watchdog
      - &#39;webform_*&#39;
  skip-tables:
    # Whole tables to exclude from default sync/dump.
    common:
      - &#39;migration_*&#39;
      - &#39;migrate_*&#39;
command:
  sql:
    dump:
      options:
        structure-tables-key: common
        skip-tables-key: common
    sync:
      options:
        structure-tables-key: common
        skip-tables-key: common
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Via &lt;a href=&quot;https://drupal.slack.com/archives/C45SW3FLM/p1618298597023300?thread_ts=1618273874.010500&amp;amp;cid=C45SW3FLM&quot;&gt;@sime on Drupal Slack&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Demo: Commerce Demo &amp; Commerce DPS</title>
    <link href="https://chris.bur.gs/demo-commerce-dps/"/>
    <updated>2021-03-29T03:06:00Z</updated>
    <id>https://chris.bur.gs/demo-commerce-dps/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Last week &lt;a href=&quot;https://chat.catalyst.net.nz/#/room/!XoBQPKshjvZopRDGnb:catalyst.net.nz/$-uN2AOZtb-qYOkuIJv3lcYvKFyZ3Abljb2FASf0ZjhU?via=catalyst.net.nz&amp;amp;via=catalyst-eu.net&amp;amp;via=bots.chat.catalyst.net.nz&quot;&gt;Frederik asked&lt;/a&gt; about DPS on Drupal Commerce (Commerce DPS is a Drupal payment module I maintain, but which I hadn&#39;t had a chance to road test the Drupal 8 version of). I figured I should get familiar!&lt;/p&gt;
&lt;p&gt;I ran up a Commerce Demo site with DPS payments and threw a Behat test on it locally. Then made that all execute in CI.&lt;/p&gt;
&lt;p&gt;Cos I&#39;m thinking about Treasury re-usable process I used my &lt;a href=&quot;https://gitlab.com/xurizaemon/drupal-builder&quot;&gt;drupal-builder&lt;/a&gt; tool to kick things off. This is a tool which implements the &amp;quot;New Drupal Site&amp;quot; process, but is IMO tidier and more reproducible on account of demonstrating ideas from &lt;a href=&quot;https://chris.bur.gs/post/2020-12-17-drupal-new-project-proposal&quot;&gt;Drupal new project proposal&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I also implemented &lt;a href=&quot;https://gitlab.com/xurizaemon/commerce-demo/-/blob/f0cf53d13ff06388f1cb1208e87165533e18c10b/.gitlab-ci.yml#L71-75&quot;&gt;inserting creds into Drupal configuration from Gitlab variables to CI runs&lt;/a&gt;, because I didn&#39;t want to expose them on a public project.&lt;/p&gt;
&lt;p&gt;I included some example things such as &lt;a href=&quot;https://gitlab.com/xurizaemon/commerce-demo/-/blob/main/tests/behat/features/screenshots.feature&quot;&gt;taking screenshots&lt;/a&gt; (see CI/CD =&amp;gt; latest pipeline =&amp;gt; test behat =&amp;gt; files) and &lt;a href=&quot;https://gitlab.com/xurizaemon/commerce-demo/-/blob/main/tests/behat/features/purchase.feature&quot;&gt;checking out via configured DPS gateway&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Next step: fix the Commerce DPS tests :)&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Drupal Code Checks</title>
    <link href="https://chris.bur.gs/drupal-code-checks/"/>
    <updated>2021-03-26T18:02:00Z</updated>
    <id>https://chris.bur.gs/drupal-code-checks/</id>
    <content xml:lang="en" type="html">&lt;p&gt;There are a bunch of Drupal code check tools about the place (and then plenty more generic PHP ones). These are a few ... I should write them up in more detail?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://mglaman.dev/blog/drupal-check-and-phpstan-drupal-are-drupal-9-and-php-8-ready&quot;&gt;https://mglaman.dev/blog/drupal-check-and-phpstan-drupal-are-drupal-9-and-php-8-ready&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/mglaman/phpstan-drupal&quot;&gt;https://github.com/mglaman/phpstan-drupal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/mglaman/drupal-check&quot;&gt;https://github.com/mglaman/drupal-check&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mortenson.coffee/blog/drupal-security-testing-everyone/&quot;&gt;https://mortenson.coffee/blog/drupal-security-testing-everyone/&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/mortenson/psalm-plugin-drupal&quot;&gt;https://github.com/mortenson/psalm-plugin-drupal&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.palantir.net/blog/jumpstart-your-drupal-9-upgrade-drupal-rector&quot;&gt;https://www.palantir.net/blog/jumpstart-your-drupal-9-upgrade-drupal-rector&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/palantirnet/drupal-rector&quot;&gt;https://github.com/palantirnet/drupal-rector&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/phpro/grumphp/&quot;&gt;https://github.com/phpro/grumphp/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Hugo archetypes</title>
    <link href="https://chris.bur.gs/hugo-archetypes/"/>
    <updated>2021-03-02T04:05:00Z</updated>
    <id>https://chris.bur.gs/hugo-archetypes/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I should update this version of Hugo and get myself using archetypes to create new posts ...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://gohugo.io/content-management/archetypes/&quot;&gt;https://gohugo.io/content-management/archetypes/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then I won&#39;t have to manually set the date every time.&lt;/p&gt;
&lt;p&gt;...&lt;/p&gt;
&lt;p&gt;OMFG, it works already. I no longer need to set the date. See false.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hugo new --kind post content/posts/2021-02-25-bleh-bleh.md
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now I no longer need to trim out the date at the start of the title.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Automating Gitlab comments in Hugo</title>
    <link href="https://chris.bur.gs/automating-hugo-comments-in-gitlab/"/>
    <updated>2021-02-24T20:05:00Z</updated>
    <id>https://chris.bur.gs/automating-hugo-comments-in-gitlab/</id>
    <content xml:lang="en" type="html">&lt;p&gt;OK, let&#39;s think about Gitlab as a comments platform for Hugo.&lt;/p&gt;
&lt;p&gt;I have an environment where all staff (theoretically) have access to Gitlab.&lt;/p&gt;
&lt;p&gt;If they don&#39;t, I&#39;ll happily post comments on behalf.&lt;/p&gt;
&lt;p&gt;Gitlab API permits opening issues and fetching / rendering issues.&lt;/p&gt;
&lt;p&gt;This should happen via JS so the comments are current. If not via JS, then we just link to the thread. Maybe copy the article text into the issue description?&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>nb (notebook), a CLI taker of notes, journal, etc</title>
    <link href="https://chris.bur.gs/nb/"/>
    <updated>2021-02-24T04:05:00Z</updated>
    <id>https://chris.bur.gs/nb/</id>
    <content xml:lang="en" type="html">&lt;p&gt;nb is a command line note-taking, bookmarking, archiving, and knowledge base application with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;plain-text data storage,&lt;/li&gt;
&lt;li&gt;encryption,&lt;/li&gt;
&lt;li&gt;filtering and search,&lt;/li&gt;
&lt;li&gt;Git-backed versioning and syncing,&lt;/li&gt;
&lt;li&gt;Pandoc-backed conversion,&lt;/li&gt;
&lt;li&gt;global and local notebooks,&lt;/li&gt;
&lt;li&gt;customizable color themes,&lt;/li&gt;
&lt;li&gt;extensibility through plugins,&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;and more, all in a single portable, user-friendly script.&lt;/p&gt;
&lt;p&gt;See &lt;a href=&quot;https://xwmx.github.io/nb/&quot;&gt;https://xwmx.github.io/nb/&lt;/a&gt; for screenshots etc, it looks cool! No, I haven&#39;t tried it (as of writing this).&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Benefits of code review for the reviewer</title>
    <link href="https://chris.bur.gs/benefits-of-code-review/"/>
    <updated>2021-02-24T04:05:00Z</updated>
    <id>https://chris.bur.gs/benefits-of-code-review/</id>
    <content xml:lang="en" type="html">&lt;p&gt;On &lt;a href=&quot;https://www.otsukare.info/2021/02/24/code-review-benefits&quot;&gt;otsukare&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Code Review is an essential part of the process of publishing code. We often talk about the benefits of code review for projects and for people writing the code. I want to talk about the benefits for the person actually reviewing the code.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Understanding The Project&lt;/li&gt;
&lt;li&gt;Learning How To Code&lt;/li&gt;
&lt;li&gt;Instant Gratification&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;(read &lt;a href=&quot;https://www.otsukare.info/2021/02/24/code-review-benefits&quot;&gt;the full post&lt;/a&gt;, it&#39;s short)&lt;/p&gt;
&lt;p&gt;See also &lt;a href=&quot;https://chris.bur.gs/post/2020-07-02-drupal-team-upskilling/#provide-shared-spaces-for-review-and-discussion&quot;&gt;shared spaces for review and discussion&lt;/a&gt; in &amp;quot;&lt;a href=&quot;https://chris.bur.gs/post/2020-07-02-drupal-team-upskilling/&quot;&gt;Some approaches to upskilling the Drupal team&lt;/a&gt;&amp;quot; from mid 2020.&lt;/p&gt;
&lt;p&gt;I like Karl&#39;s use of Github issues, and should get around to automating same for comments on these posts.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Reproducing Gitlab CI failures in Docker Compose locally</title>
    <link href="https://chris.bur.gs/reproducing-gitlab-ci-in-docker-compose/"/>
    <updated>2021-02-11T08:46:00Z</updated>
    <id>https://chris.bur.gs/reproducing-gitlab-ci-in-docker-compose/</id>
    <content xml:lang="en" type="html">&lt;p&gt;OK, this is a fun situation.&lt;/p&gt;
&lt;p&gt;A piece of code works and passes tests locally (local dev environment is a Vagrant) but fails the Drupal install phase in Gitlab CI runner.&lt;/p&gt;
&lt;p&gt;The failure is that in the Gitlab CI job, &lt;code&gt;drush site-install&lt;/code&gt; task bails out with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;drush site:install -y --db-url=pgsql://example:example@db/example --existing-config

 You are about to:
 * DROP all tables in your &#39;example&#39; database.

 // Do you want to continue?: yes.                                                                                 

 [notice] Starting Drupal installation. This takes a while.
 [notice] Performed install task: install_select_language
 [notice] Performed install task: install_select_profile
 [notice] Performed install task: install_load_profile
 [notice] Performed install task: install_verify_requirements
 [notice] Performed install task: install_settings_form
 [notice] Performed install task: install_write_profile
 [notice] Performed install task: install_verify_database_ready
 [notice] Performed install task: install_base_system
 [notice] Performed install task: install_bootstrap_full
 [warning] The &amp;quot;block_content:b42edb07-dcb6-4be4-aa3b-480fc6e06ccf&amp;quot; was not found
 [notice] Options titles do not exist.
 [notice] Options state_province_names do not exist.

In FieldConfig.php line 312:
                                                                                                
  Attempt to create a field field_not_quite_logged_in that does not exist on entity type user.  
                                                                                                

site:install [--db-url DB-URL] [--db-prefix DB-PREFIX] [--db-su DB-SU] [--db-su-pw DB-SU-PW] [--account-name [ACCOUNT-NAME]] [--account-mail [ACCOUNT-MAIL]] [--site-mail [SITE-MAIL]] [--account-pass ACCOUNT-PASS] [--locale [LOCALE]] [--site-name [SITE-NAME]] [--site-pass SITE-PASS] [--sites-subdir SITES-SUBDIR] [--config-dir CONFIG-DIR] [--existing-config] [-h|--help] [-q|--quiet] [-v|vv|vvv|--verbose] [-V|--version] [--ansi] [--no-ansi] [-n|--no-interaction] [-d|--debug] [-y|--yes] [--no] [--remote-host REMOTE-HOST] [--remote-user REMOTE-USER] [-r|--root ROOT] [-l|--uri URI] [--simulate] [--pipe] [-D|--define DEFINE] [--notify] [--druplicon] [--xh-link XH-LINK] [--] &amp;lt;command&amp;gt; [&amp;lt;profile&amp;gt;]...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I tried to reproduce this locally in the existing Vagrant environment, and it did not appear there; &lt;code&gt;drush site-install&lt;/code&gt; completed as expected.&lt;/p&gt;
&lt;p&gt;I emulate the Gitlab CI environment (which uses a Docker CI runner environment) using Docker Compose to reproduce the failure. Here&#39;s the Gitlab CI yaml snippet:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# Reduced version of https://gitlab.example.org/example/example/-/blob/&amp;lt;hash-here&gt;/.gitlab-ci.yml&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; gitlab.example.org&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;4567/drupal/docker/php&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;7.2&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest&lt;br /&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;test behat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;stage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; test&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;needs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; build theme&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; build drupal&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; docker&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; postgres&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10.12&lt;/span&gt;&lt;br /&gt;      &lt;span class=&quot;token key atrule&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; db&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;variables&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;HOST_PORT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0.0.0.0:8888&quot;&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;DRUPAL_ENVIRONMENT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ci&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;POSTGRES_DB&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; example&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;POSTGRES_USER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; example&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; example&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;before_script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; echo &#39;&amp;lt;&lt;span class=&quot;token punctuation&quot;&gt;?&lt;/span&gt;php $environment = getenv(&quot;DRUPAL_ENVIRONMENT&quot;);&#39; &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt; web/sites/default/settings.local.php&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; drush site&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;install &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;y &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;db&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;url=pgsql&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//example&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;example@db/example &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;existing&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;config&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; php &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;S $HOST_PORT &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;t ./web &lt;span class=&quot;token important&quot;&gt;&amp;amp;&gt;&lt;/span&gt; tmp/php.log.txt &amp;amp;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; until curl &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;s http&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//$HOST_PORT; do true; done &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt; /dev/null&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; curl &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;o tmp/index.html &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;s http&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;//$HOST_PORT&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; cd tests/behat&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; behat &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;profile=gitlab_ci &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;tags=&#39;~@broken&lt;span class=&quot;token important&quot;&gt;&amp;amp;&amp;amp;~@drush&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;f junit &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;o $CI_PROJECT_DIR/behat&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;junit &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;f pretty &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;o std &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;colors&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here&#39;s the &lt;code&gt;docker-compose.yml&lt;/code&gt; I use to reproduce what the above will do:&lt;/p&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;3.1&quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;db&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; postgres&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10.12&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token key atrule&quot;&gt;POSTGRES_DB&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; example&lt;br /&gt;        &lt;span class=&quot;token key atrule&quot;&gt;POSTGRES_USER&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; example&lt;br /&gt;        &lt;span class=&quot;token key atrule&quot;&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; example&lt;br /&gt;  &lt;span class=&quot;token key atrule&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; gitlab.example.org&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;4567/drupal/docker/php&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;7.2&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token key atrule&quot;&gt;HOST_PORT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0.0.0.0:8888&quot;&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token key atrule&quot;&gt;DRUPAL_ENVIRONMENT&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ci&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;working_dir&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; /app&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;php&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;-S&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;0.0.0.0:8888&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/app/web&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;br /&gt;    &lt;span class=&quot;token key atrule&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;br /&gt;        &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; ./&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;/app&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I then need to reproduce the behaviour of the Gitlab CI &lt;code&gt;before_script&lt;/code&gt; and &lt;code&gt;script&lt;/code&gt; steps.&lt;/p&gt;
&lt;p&gt;I set the environment in &lt;code&gt;settings.local.php&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/app # echo &#39;&amp;lt;?php $environment = getenv(&amp;quot;DRUPAL_ENVIRONMENT&amp;quot;);&#39; &amp;gt; web/sites/default/settings.local.php
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then I execute the step which fails:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/app # drush site:install -y --db-url=pgsql://example:example@db/example --existing-config

 You are about to:
 * DROP all tables in your &#39;example&#39; database.

 // Do you want to continue?: yes.                                                                                 

 [notice] Starting Drupal installation. This takes a while.
 [notice] Performed install task: install_select_language
 [notice] Performed install task: install_select_profile
 [notice] Performed install task: install_load_profile
 [notice] Performed install task: install_verify_requirements
 [notice] Performed install task: install_settings_form
 [notice] Performed install task: install_write_profile
 [notice] Performed install task: install_verify_database_ready
 [notice] Performed install task: install_base_system
 [notice] Performed install task: install_bootstrap_full
 [warning] The &amp;quot;block_content:b42edb07-dcb6-4be4-aa3b-480fc6e06ccf&amp;quot; was not found
 [notice] Options titles do not exist.
 [notice] Options state_province_names do not exist.

In FieldConfig.php line 312:
                                                                                                
  Attempt to create a field field_not_quite_logged_in that does not exist on entity type user.  
                                                                                                

site:install [--db-url DB-URL] [--db-prefix DB-PREFIX] [--db-su DB-SU] [--db-su-pw DB-SU-PW] [--account-name [ACCOUNT-NAME]] [--account-mail [ACCOUNT-MAIL]] [--site-mail [SITE-MAIL]] [--account-pass ACCOUNT-PASS] [--locale [LOCALE]] [--site-name [SITE-NAME]] [--site-pass SITE-PASS] [--sites-subdir SITES-SUBDIR] [--config-dir CONFIG-DIR] [--existing-config] [-h|--help] [-q|--quiet] [-v|vv|vvv|--verbose] [-V|--version] [--ansi] [--no-ansi] [-n|--no-interaction] [-d|--debug] [-y|--yes] [--no] [--remote-host REMOTE-HOST] [--remote-user REMOTE-USER] [-r|--root ROOT] [-l|--uri URI] [--simulate] [--pipe] [-D|--define DEFINE] [--notify] [--druplicon] [--xh-link XH-LINK] [--] &amp;lt;command&amp;gt; [&amp;lt;profile&amp;gt;]...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This fails, which is a great success! From here, I debugged a little further to establish it was something like a timing issue on first run. Those details ended up in the related client-specific issue, and I kept these notes for sharing here.&lt;/p&gt;
&lt;p&gt;The real takeaway for me was that I could readily use Docker Compose to replicate issues I saw in Gitlab CI, and knowing that a significant amount of variation between environments could be eliminated by using the same container images, environment variables, and exact build steps to set up a reproduction.&lt;/p&gt;
&lt;p&gt;This experience gave me a decent nudge towards getting rid of long-lived Vagrant instances for development and coalescing practice towards more consistent (but not identical!) environments for development, test and hosting.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Youtube watches</title>
    <link href="https://chris.bur.gs/youtube-watchlist/"/>
    <updated>2020-12-21T00:56:00Z</updated>
    <id>https://chris.bur.gs/youtube-watchlist/</id>
    <content xml:lang="en" type="html">&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=-ufMwAGCgNQ&quot;&gt;How to classify and sort more than 200 documents in a single bulk upload (DrupalSouth 2020)&lt;/a&gt; - this actually didn&#39;t deliver, except to inform me that GovCMS permits custom code only via the admin theme, resulting in people using it to do complex form processing. I get why, I get how, I still think it&#39;s horrid.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=5ehEPiI_KBc&quot;&gt;Highly processed migrations - Eric Goodwin (DrupalSouth Hobart 2019)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=kQEMnk4boP4&quot;&gt;Understanding Automated Tests in Drupal (DrupalSouth 2020)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=4QdY9le4Cuw&quot;&gt;Paragraphs Without Pain - Angus Gordon (DrupalSouth 2020)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Trying CodeLingo playbooks</title>
    <link href="https://chris.bur.gs/codelingo-playbooks/"/>
    <updated>2020-12-16T22:00:00Z</updated>
    <id>https://chris.bur.gs/codelingo-playbooks/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I had a demo of CodeLingo&#39;s Playbooks today - an interesting tool they&#39;ve pivoted to from their static analysis (I think) tool.&lt;/p&gt;
&lt;p&gt;CodeLingo are a startup based in Dunedin who built a tool for reviewing and I guess &amp;quot;enshrining&amp;quot; development processes, to support team communication and knowledge sharing about correct procedure. We all like that, right?&lt;/p&gt;
&lt;p&gt;CodeLingo&#39;s v1 tool looked like this, if I get it right:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;on a merge request, a reviewer might identify a block of code that has a &amp;quot;code smell&amp;quot;&lt;/li&gt;
&lt;li&gt;reviewer adds a note, saying &amp;quot;This block should use the internal HTTP client rather than this more verbose instantiation of a custom client&amp;quot;&lt;/li&gt;
&lt;li&gt;reviewer pings a bot in the MR, saying &lt;code&gt;@codelingobot add rule prefer internal HTTP client&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;codelingobot adds a rule (name?) to the project ruleset&lt;/li&gt;
&lt;li&gt;team expands the rule docs, test and guidance&lt;/li&gt;
&lt;li&gt;in future, the code smell is automatically identified&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This was done with a custom &lt;a href=&quot;https://en.wikipedia.org/wiki/Abstract_syntax_tree&quot;&gt;AST&lt;/a&gt; and a query language which comprehends syntax using the AST, so that your rule ends up as an XPath-like description of the incorrect pattern (finding a custom HTTP client instantiation inside classes known to have an HTTP client available).&lt;/p&gt;
&lt;p&gt;CodeLingo Playbooks are a variation of this; it&#39;s now about capturing blocks of code and describing them in playbooks, so focusing less on rules and more on documenting and communicating (but with the rules capability still available).&lt;/p&gt;
&lt;p&gt;This feels partly like functionality I&#39;d enjoy, and partly like functionality I&#39;m already familiar with - I can link to code in context using line numbers so that&#39;s pretty easy to communicate amongst team members. But the ability for this documentation to track the referenced code via the AST query selectors is cool.&lt;/p&gt;
&lt;p&gt;I&#39;ve committed to give it a go documenting some things and see how useful it is to me as part of my workflow.&lt;/p&gt;
&lt;p&gt;I do think the &amp;quot;playbook&amp;quot; name is a bit confusing to me, and it probably conveys an aspect of the product I&#39;m missing.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>DrupalGov Sprint and Sector</title>
    <link href="https://chris.bur.gs/drupalgov-sprint-and-sector/"/>
    <updated>2020-11-04T05:23:00Z</updated>
    <id>https://chris.bur.gs/drupalgov-sprint-and-sector/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I joined the DrupalGov Sprint yesterday - it was my first online-only sprint ... not quite the same as an IRL one, that&#39;s for sure!&lt;/p&gt;
&lt;p&gt;However :) I got to meet one key contact, Daniel Veza (team lead on Sector) and we had a good chat, he seemed enthusiastic about contributions and was open to&lt;/p&gt;
&lt;p&gt;Here&#39;s my planned schedule for Thursday:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;9:20 KEYNOTE: Government as a Platform&lt;/li&gt;
&lt;li&gt;10:05 Unsure&lt;/li&gt;
&lt;li&gt;10:30am How to classify &amp;amp; sort more than 200 documents in a single bulk upload&lt;/li&gt;
&lt;li&gt;10:55am Unsure ... Meet the Moment: Delivering Results with WebOps?&lt;/li&gt;
&lt;li&gt;11:15am Drupal 9 and CiviCRM&lt;/li&gt;
&lt;li&gt;11:40am  KEYNOTE: 2020. That was epic! A tale of gargantuan scale.&lt;/li&gt;
&lt;li&gt;1:10pm  Drupal Security Panel&lt;/li&gt;
&lt;li&gt;1:35pm  Managing large (and smaller) site portfolios with Lagoon Insights&lt;/li&gt;
&lt;li&gt;2:00pm  Strategies for Managing a Fleet of Drupal Projects&lt;/li&gt;
&lt;li&gt;2:20pm  Networking &amp;amp; Break&lt;/li&gt;
&lt;li&gt;2:40pm  Securing and Scaling Drupal with Kubernetes Panel&lt;/li&gt;
&lt;li&gt;3:25pm  Networking &amp;amp; Break&lt;/li&gt;
&lt;li&gt;3:05pm  Understanding Automated Tests in Drupal OR GovCMS and Drupal 9, roadmap and Q&amp;amp;A&lt;/li&gt;
&lt;li&gt;4:10pm  Closing Remarks&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Renovate - Automated update MRs</title>
    <link href="https://chris.bur.gs/renovatebot/"/>
    <updated>2020-10-27T06:45:00Z</updated>
    <id>https://chris.bur.gs/renovatebot/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I found the docs and error output quite unclear, to be honest - perhaps because the first result referred to a Github token, and I wasn&#39;t using Github at all. Turned out (I think) the Github token is required for reading package changelogs ... OK then.&lt;/p&gt;
&lt;p&gt;So:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GITHUB_COM_TOKEN=&amp;lt;token from github&amp;gt;
RENOVATE_TOKEN=&amp;lt;token for your project&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Gitlab token can be a project token with API access, rather than a user token, so I&#39;d recommend that; then the MRs appear to come from the bot rather than your user.&lt;/p&gt;
&lt;p&gt;The tool acts on the remote repository, &lt;em&gt;not your local codebase&lt;/em&gt;. I spent a while trying &lt;code&gt;renovate .&lt;/code&gt; and wondering why it didn&#39;t see the renovate config ... cos that config wasn&#39;t in the repo.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Which Drupal config split is currently active</title>
    <link href="https://chris.bur.gs/which_drupal_config_split_is_it/"/>
    <updated>2020-09-25T00:00:00Z</updated>
    <id>https://chris.bur.gs/which_drupal_config_split_is_it/</id>
    <content xml:lang="en" type="html">&lt;p&gt;This information is also shown at &lt;code&gt;admin/config/development/configuration/config-split&lt;/code&gt;. I have to figure this one out periodically for CLI debugging, so here&#39;s a note.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$environments = [
  &#39;development&#39;,
  &#39;ci&#39;,
  &#39;staging&#39;,
  &#39;uat&#39;,
  &#39;production&#39;,
];
foreach ($environments as $environment) {
  if (&#92;Drupal::config(&amp;quot;config_split.config_split.$environment&amp;quot;)-&amp;gt;get(&#39;status&#39;)) {
    print &amp;quot;$environment&amp;quot;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
</content>
  </entry>
  <entry>
    <title>Patching modules from D8 to D9</title>
    <link href="https://chris.bur.gs/patching_drupal8_modules_for_d9/"/>
    <updated>2020-09-25T00:00:00Z</updated>
    <id>https://chris.bur.gs/patching_drupal8_modules_for_d9/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Currently there&#39;s an interesting obstacle to working with Drupal 9: if a D8 module doesn&#39;t yet have D9 support, and a patch is sitting in the issue queue, cweagans/composer-patches can&#39;t handle the scenario ... because the  composer dependency tree prevents the module version being installed installed. What can you do?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Contribute! Step up as a co-maintainer. But if the module is already slow to merge D9 patches, you might find it&#39;s not maintained.&lt;/li&gt;
&lt;li&gt;Fork the module to modules/custom ... please don&#39;t do this.&lt;/li&gt;
&lt;li&gt;Work around it in composer.json&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The last option looks like this:&lt;/p&gt;
&lt;p&gt;Add a package entry specifically for this Drupal module to your composer.json:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &amp;quot;type&amp;quot;: &amp;quot;package&amp;quot;,
    &amp;quot;package&amp;quot;: {
        &amp;quot;name&amp;quot;: &amp;quot;drupal/cache_control_override&amp;quot;,
        &amp;quot;type&amp;quot;: &amp;quot;drupal-module&amp;quot;,
        &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
        &amp;quot;source&amp;quot;: {
            &amp;quot;type&amp;quot;: &amp;quot;git&amp;quot;,
            &amp;quot;url&amp;quot;: &amp;quot;https://git.drupalcode.org/project/cache_control_override.git&amp;quot;,
            &amp;quot;reference&amp;quot;: &amp;quot;8db91684a427366d8f9c51f60cbac10c2d586d95&amp;quot;
        }
    }
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&#39;ve used a Git SHA so Composer won&#39;t bother with version specifics, and the presence of the package entry means we won&#39;t look this up in &lt;a href=&quot;http://packages.drupal.org/&quot;&gt;packages.drupal.org&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://getcomposer.org/doc/05-repositories.md#packages&quot;&gt;Composer packages entries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.drupal.org/project/ideas/issues/3168047#comment-13806926&quot;&gt;d.o#3168047: Handling undermaintained projects from Drupal.org&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Gitlab&#39;s Directed Acyclic Graph functionality</title>
    <link href="https://chris.bur.gs/directed-acyclic-graph-gitlab-ci/"/>
    <updated>2020-07-14T00:00:00Z</updated>
    <id>https://chris.bur.gs/directed-acyclic-graph-gitlab-ci/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Gitlab has a DAG (Directed Acyclic Graph) feature for managing dependencies between jobs, which improves on the previous behaviour of stage precedence. The old behaviour is that jobs are grouped in stages, and jobs from a later stage only start if the previous stages complete successfully. The new behaviour is that with a &lt;code&gt;depends:&lt;/code&gt; property on a job, you can name other jobs which much be completed, and that job can commence as soon as its dependencies have been met.&lt;/p&gt;
&lt;p&gt;By way of example, I tested this on a project which had three stages: lint, build, test.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Stage &lt;code&gt;lint&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Job &lt;code&gt;grumphp&lt;/code&gt; would run &lt;code&gt;composer install&lt;/code&gt; then &lt;code&gt;grumphp&lt;/code&gt;, run time ~ 60&amp;quot;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Stage &lt;code&gt;build&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Job &lt;code&gt;drush make&lt;/code&gt; would build the site codebase, run time ~ 1&#39;45&amp;quot;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Stage &lt;code&gt;test&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Job &lt;code&gt;behat&lt;/code&gt; would install the site (using MySQL service) then run Behat, run time ~ 1&#39;11&amp;quot;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This was totalling to about 3&#39;30&amp;quot; on average (excluding the Behat test time).&lt;/p&gt;
&lt;p&gt;In the above, &lt;code&gt;behat&lt;/code&gt; depends on &lt;code&gt;drush make&lt;/code&gt;, &lt;code&gt;composer install&lt;/code&gt; and the DB being loaded. Because Gitlab CI services currently can&#39;t be shared between jobs, the DB load has to happen in the same job as Behat. &lt;code&gt;composer install&lt;/code&gt; can be decoupled from &lt;code&gt;grumphp&lt;/code&gt;, but this appears to offer little time saving: initialising an additional job adds some overhead, so you might think there&#39;s not much to be gained from splitting the two. With stages only that&#39;s true - but in this case it also permits us to run &lt;code&gt;composer install&lt;/code&gt; and &lt;code&gt;drush make&lt;/code&gt; simultaneously. And that&#39;s where the win is!&lt;/p&gt;
&lt;p&gt;Reorganised:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Stage &lt;code&gt;build&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Job &lt;code&gt;composer install&lt;/code&gt; prepares for running &lt;code&gt;behat&lt;/code&gt; and &lt;code&gt;grumphp&lt;/code&gt;, run time ~ 1&#39;.&lt;/li&gt;
&lt;li&gt;Job &lt;code&gt;drush make&lt;/code&gt;, run time ~ 1&#39;15&amp;quot;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Stage &lt;code&gt;lint&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Job &lt;code&gt;grumphp&lt;/code&gt; checks the codebase, run time ~ 45&amp;quot;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Stage &lt;code&gt;test&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Job &lt;code&gt;behat&lt;/code&gt; loads the DB and runs tests, run time ~ 1&#39;20&amp;quot;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The real advantage in the above, which totals to a longer run time (and more charges against your Gitlab allocated CI minutes, if that&#39;s a consideration), is that we can start &lt;code&gt;composer install&lt;/code&gt; and &lt;code&gt;drush make&lt;/code&gt; immediately, and then start &lt;code&gt;grumphp&lt;/code&gt; and &lt;code&gt;behat&lt;/code&gt; as soon as the first two jobs are completed. Totalling the change, the &lt;code&gt;behat&lt;/code&gt; task can be taken as a constant since it doesn&#39;t shift, and what we can vary is how fast &lt;code&gt;behat&lt;/code&gt; starts. In the first scenario, completing &lt;code&gt;grumphp&lt;/code&gt; then &lt;code&gt;drush make&lt;/code&gt; took ~ 2&#39;30&amp;quot; - 2&#39;45&amp;quot;. In the second scenario, that&#39;s down to 2&#39;.&lt;/p&gt;
&lt;h2&gt;Samples&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;job name&lt;/th&gt;
&lt;th&gt;run times observed&lt;/th&gt;
&lt;th&gt;notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;grumphp + composer&lt;/td&gt;
&lt;td&gt;45&amp;quot;, 46&amp;quot;, 46&amp;quot;, 52&amp;quot;&lt;/td&gt;
&lt;td&gt;includes composer install&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;composer install&lt;/td&gt;
&lt;td&gt;50&amp;quot;, 57&amp;quot;, 58&amp;quot;, 58&amp;quot;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;grumphp&lt;/td&gt;
&lt;td&gt;32&amp;quot;, 36&amp;quot;, 38&amp;quot;, 39&amp;quot;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;drush make&lt;/td&gt;
&lt;td&gt;1&#39;05&amp;quot;, 1&#39;16&amp;quot;, 1&#39;16&amp;quot;, 1&#39;16&amp;quot;, 1&#39;17&amp;quot;, 1&#39;19&amp;quot;, 1&#39;20&amp;quot;, 1&#39;27&amp;quot;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;behat&lt;/td&gt;
&lt;td&gt;(not significant here)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This is a really simple example of the advantages of the DAG model. In this case it&#39;s somewhat constructed; rearranging with the &lt;code&gt;grumphp&lt;/code&gt; into the &lt;code&gt;test&lt;/code&gt; stage could have the same time saving. But for more complex CI layouts where you might have developers waiting or attending to the outcome of results, this is a real win in terms of keeping focus up and development progressing. Attention switching is a cost and long CI run times can be costly for that.&lt;/p&gt;
&lt;p&gt;I can&#39;t explain why the measured run times of a task which included &lt;code&gt;composer install&lt;/code&gt; and &lt;code&gt;grumphp&lt;/code&gt; went up when the task was reduced to just &lt;code&gt;composer install&lt;/code&gt;. Maybe with more samples this would iron itself out ... I&#39;m intrigued. Also keen to measure a few CI runs of a job which does just &lt;code&gt;echo hello world&lt;/code&gt; so I get a feel for the overhead of any single job added to the CI process - this obviously depends on the image used, and artifacts each end of the job too.&lt;/p&gt;
&lt;p&gt;The DAG feature isn&#39;t (yet, as of July 2020) visible / available on Catalyst&#39;s Gitlab instances; the CE docs indicate that it&#39;s something to manually enable while in beta. I&#39;m looking forward to being able to use it!&lt;/p&gt;
&lt;p&gt;Details &amp;amp; links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.gitlab.com/ce/ci/directed_acyclic_graph/&quot;&gt;Directed Acyclic Graph in Gitlab Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chris.bur.gs/directed-acyclic-graph-gitlab-ci/&quot;&gt;gitlab-org/gitlab#215517: DAG Visualization MVC&lt;/a&gt;(&lt;a href=&quot;https://gitlab.com/gitlab-org/gitlab/-/issues/215517&quot;&gt;https://gitlab.com/gitlab-org/gitlab/-/issues/215517&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gitlab.com/gitlab-org/gitlab/-/issues/220368&quot;&gt;gitlab-org/gitlab#220368: Feedback for beta version of DAG (directed acyclic graph)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Behat, Drupal and related tools</title>
    <link href="https://chris.bur.gs/behat-drupal-and-pals/"/>
    <updated>2020-07-07T00:00:00Z</updated>
    <id>https://chris.bur.gs/behat-drupal-and-pals/</id>
    <content xml:lang="en" type="html">&lt;p&gt;This is really a brain dump for a developer joining one of many projects I&#39;m using Behat, Drupal &amp;amp; some related tools on at the moment. But ... if I write it up generally instead of as a stream in some chat program, it won&#39;t be lost quite as quickly. Or I can link to it next time.&lt;/p&gt;
&lt;p&gt;First of all: Why Behat? For me, after a few times clicking through the same webform, I start to feel a little ... impaired. Seriously. I cannot manually test effectively; my brain is gone elsewhere. But that&#39;s fine, because repetitive manual testing is not something we need humans to be doing, and we can deliver it repeatably and reliably for Drupal using Behat.&lt;/p&gt;
&lt;p&gt;Behat abstracts the steps of testing into Feature and Scenario definitions written in Cucumber syntax. I recommend absorbing the way that Cucumber / Gherkin is written &lt;a href=&quot;https://behat.org/en/latest/user_guide/writing_scenarios.html&quot;&gt;1&lt;/a&gt;, &lt;a href=&quot;https://cucumber.io/docs/gherkin/reference/&quot;&gt;2&lt;/a&gt;,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Scenario: Wilson posts to his own blog
  Given I am logged in as Wilson
  When I try to post to &amp;quot;Expensive Therapy&amp;quot;
  Then I should see &amp;quot;Your article was published.&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The Steps in the Scenario above (and the associated components like Feature, Background etc) describe in plain language how something is used.&lt;/p&gt;
&lt;h2&gt;Developing with Behat&lt;/h2&gt;
&lt;p&gt;What&#39;s ideally going to happen in your development cycle looks like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You&#39;re asked to implement a feature&lt;/li&gt;
&lt;li&gt;You understand enough about the requirements to write a meaningful Feature / Scenario&lt;/li&gt;
&lt;li&gt;This won&#39;t pass at first, and that&#39;s OK&lt;/li&gt;
&lt;li&gt;You&#39;ll then modify the application / website to implement the requirements&lt;/li&gt;
&lt;li&gt;The test will begin to pass, and you&#39;ll improve the test as the implementation develops&lt;/li&gt;
&lt;li&gt;You&#39;ll submit an MR with the test and the code changes&lt;/li&gt;
&lt;li&gt;The test will be proven to pass in CI before it is merged&lt;/li&gt;
&lt;li&gt;You&#39;ll have confidence that the functionality continues to work in future&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Understanding Behat structure&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;project&amp;gt;
+ composer.json
  + tests
    + behat
      - behat.yml
      - features/bootstrap/FeatureContext.php
      - features/*/foo.feature
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;composer.json&lt;/code&gt; - In &lt;code&gt;composer.json&lt;/code&gt; we reference composer libraries for Behat, and a set of other supporting components. Some of these might be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;behat/behat&lt;/code&gt; - the main Behat library&lt;/li&gt;
&lt;li&gt;&lt;code&gt;behat/mink&lt;/code&gt; - mink drives various browser implementations such as Goutte, Chromedriver, Selenium.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;behat/mink-goutte-driver&lt;/code&gt; - Goutte is a PHP-only &amp;quot;browser&amp;quot; which implements the HTTP spec and can do basics, but lacks JS support or a rendering display.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bex/behat-screenshot&lt;/code&gt; - A Behat extension which watches for failed Steps and takes screenshots on failure&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dmore/behat-chrome-extension&lt;/code&gt; - Allows Behat to drive Chromium via Chromedriver&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rpkamp/mailhog-behat-extension&lt;/code&gt; - Connects Behat to Mailhog, so we can capture and read emails sent during testing&lt;/li&gt;
&lt;li&gt;&lt;code&gt;drupal/drupal-extension&lt;/code&gt; - integration layer between Behat, Mink and Drupal. Provides many common Drupal-specific Steps (eg &lt;code&gt;Given I create an Article node with title &amp;quot;Foo&amp;quot;&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;drupal/drupal-driver&lt;/code&gt; - Drupal Driver for Behat to talk to a Drupal site. Contains Drupal, Drush and Blackbox connectors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Install Behat libraries using &lt;code&gt;composer require --dev&lt;/code&gt; so they don&#39;t get sent to production.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;behat.yml&lt;/code&gt; - This is the configuration file for Behat. It has configuration sections for Behat packages, and supports multiple profiles so you can (for example) switch profiles for different environments. It can sprawl a bit, but mostly you won&#39;t need to change it.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;features/bootstrap/FeatureContext.php&lt;/code&gt; - each project has a FeatureContext like this, although you can add your own custom contexts either in the Behat directory or as extensions. For example, if you had a set of sites to test together you could create an BauersiteContext class which encapsulated functionality specific to that suite of sites.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;features/*/foo.feature&lt;/code&gt; - this is where the Features live. Organise as you wish (if they don&#39;t auto-load, check the top of your &lt;code&gt;behat.yml&lt;/code&gt;). Tag your features and use tags to run specific features: &lt;code&gt;behat --tags=@payment&lt;/code&gt;. I often tag tests with the associated WR during development, so I can &lt;code&gt;behat --tags=@wr277853&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Writing good Scenarios&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;This is advice to myself too and in some of the linked examples I won&#39;t have done perfectly. It&#39;s OK to do what works! But this is a list of what&#39;s &lt;em&gt;good&lt;/em&gt; too.&lt;/li&gt;
&lt;li&gt;Write idempotent tests. Don&#39;t hardcode in &lt;code&gt;Given I log in as user test@example.com with password &amp;quot;fruity&amp;quot;&lt;/code&gt; because your tests will break when the content (user account) they depend on&lt;/li&gt;
&lt;li&gt;Try to avoid &lt;code&gt;When I click on the element with ID &amp;quot;#user-1-send-message&amp;quot;&lt;/code&gt;, instead writing &lt;code&gt;When I click on &amp;quot;Send message&amp;quot; for user 1&lt;/code&gt; and creating a StepDefinition to match. Your Scenarios should ideally not incorporate low-level details such as DOM IDs because these things may change in the application / content and you don&#39;t want that to break your Scenario. Ideally your StepDefinition should parse the document rather than just packaging up the lower-level details, but even the latter is better than putting hard to comprehend language into your Scenarios.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Good blog posts / docs / videos&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://behat.org/en/latest/user_guide/writing_scenarios.html&quot;&gt;Behat&#39;s Writing Scenarios&lt;/a&gt; is worth your time.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=2TJfbGYKBiM&quot;&gt;Making your life easier with Behat + Mink + Drupal Extension (youtube)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=i6-940AnZxc&quot;&gt;Automated testing in Drupal with Behat (youtube)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://mink.behat.org/en/latest/at-a-glance.html&quot;&gt;Mink at a glance&lt;/a&gt; to understand Mink&#39;s place in things&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gorannikolovski.com/blog/drupal-8-and-behat-tests&quot;&gt;Drupal 8 and Behat tests&lt;/a&gt; - introductory, but a decent walkthrough / intro&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://behat-drupal-extension.readthedocs.io/&quot;&gt;Docs for the Drupal Extension&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;To do&lt;/h2&gt;
&lt;p&gt;Things I could expand on more here.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How to use a clean install to run your tests (no requirement for a DB dump) - ideal scenario&lt;/li&gt;
&lt;li&gt;How to use DB dumps and use those (no examples yet)
&lt;ul&gt;
&lt;li&gt;How to sanitise / reduce DBs in preparation for CI runs&lt;/li&gt;
&lt;li&gt;Kirill recommends to not do this - I think for existing sites this is a pragmatic path towards testing; otherwise the leap can be too big for projects to justify&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Behat in CI (probably another post), incl
&lt;ul&gt;
&lt;li&gt;Switching configuration for CI (eg showing backtraces, capturing error logs)&lt;/li&gt;
&lt;li&gt;Long install times / short job runs&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Drupal configuration management and sensitive credentials</title>
    <link href="https://chris.bur.gs/drupal-configuration-management-and-sensitive-credentials/"/>
    <updated>2020-07-04T00:00:00Z</updated>
    <id>https://chris.bur.gs/drupal-configuration-management-and-sensitive-credentials/</id>
    <content xml:lang="en" type="html">&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.drupalcommerce.org/commerce2/developer-guide/payments/overriding-payment-config&quot;&gt;https://docs.drupalcommerce.org/commerce2/developer-guide/payments/overriding-payment-config&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.drupal.org/project/drupal/issues/2501901&quot;&gt;https://www.drupal.org/project/drupal/issues/2501901&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Working out loud?</title>
    <link href="https://chris.bur.gs/working-out-loud/"/>
    <updated>2020-02-28T00:00:00Z</updated>
    <id>https://chris.bur.gs/working-out-loud/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I was introduced to the book &lt;a href=&quot;https://www.goodreads.com/book/show/25800993-working-out-loud&quot;&gt;Working Out Loud&lt;/a&gt; at an open source conference in Denver 2016,  when &lt;a href=&quot;https://twitter.com/openmatt&quot;&gt;Matt Thompson&lt;/a&gt; keynoted. The ideas behind Working Out Loud seemed so familiar and he expressed them so well.&lt;/p&gt;
&lt;p&gt;Working Out Loud&#39;s blurb makes it sound like it&#39;s a career development book but to me it&#39;s more about us all being better together. If you&#39;re reading this (hosted as it is internally to Catalyst), I can assume you&#39;re familiar with open source.&lt;/p&gt;
&lt;p&gt;So - what&#39;s Working Loud, this blog, for me? It&#39;s a process of encouraging myself to share my learning and my work by trying to be open by default. If I&#39;m discovering how to implement something, I&#39;ll try to share my journey somehow. This might be a write-up of a technique, or some thoughts about how I might do better in future.&lt;/p&gt;
&lt;p&gt;Working Open (as a philosophy, as opposed to this blog) is the idea that we learn more when we share more, and that there&#39;s rarely a good reason to make things hard to discover. I learned so much from open source and am grateful for it; it seems odd to &lt;em&gt;not&lt;/em&gt; share what I&#39;ve freely been given by this community of people working together. (Sure, I&#39;ve had to work to learn too, but that&#39;s not a reason to not pass the learning on.)&lt;/p&gt;
&lt;p&gt;Working Open has side effects, such as being more able to pick up from where I left off in six months (treating my future self as a team member), and reducing the effort to share process when I find I need to. It&#39;s refreshing and rewarding to answer a question with &amp;quot;oh, I wrote that up, here you go&amp;quot; and give someone all the details they need; it feels like a win.&lt;/p&gt;
&lt;p&gt;Working Open also reduces siloing and improves bus factoring (I forget which direction the bus factor is supposed to go, but if I&#39;ve kept notes, I&#39;ve moved it the right way).&lt;/p&gt;
&lt;p&gt;That&#39;s what this is all about.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Breaking YouTube &amp; friends</title>
    <link href="https://chris.bur.gs/breaking-youtube/"/>
    <updated>2020-02-26T00:00:00Z</updated>
    <id>https://chris.bur.gs/breaking-youtube/</id>
    <content xml:lang="en" type="html">&lt;p&gt;We have a couple of young kids (currently 10 and 12) and &amp;quot;devices&amp;quot; in the home. We have a pretty good arrangement but one thing that&#39;s hard for them - one in particular - to resist is YouTube in the mornings.&lt;/p&gt;
&lt;p&gt;We also have a &lt;a href=&quot;https://pi-hole.net/&quot;&gt;PiHole&lt;/a&gt; device on the network, and it&#39;s quite a bit more flexible (being &amp;quot;plain old unix&amp;quot;, as far as I care) for scripting behaviours.&lt;/p&gt;
&lt;p&gt;I added a couple of cronjobs to prevent access to certain sites during certain hours. This has resulted in the occasional &amp;quot;Dad, SITENAME doesn&#39;t work&amp;quot; but I&#39;m firm that Dad doesn&#39;t do technical support before 9am (not domestic tech support anyway).&lt;/p&gt;
&lt;p&gt;Here&#39;s the recipe - nothing special, but it works nicely enough for my needs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;/etc/cron.d/blocked-sites&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-php&quot;&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; pi bash &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;usr&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;local&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;bin&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;pihole &lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;regex &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;(^|&#92;.)(googlevideo|snapchat|tiktok|youtube)&#92;.com$&#39;&lt;/span&gt;&lt;br /&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;9&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; pi bash &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;usr&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;local&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;bin&lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt;pihole &lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;regex &lt;span class=&quot;token operator&quot;&gt;--&lt;/span&gt;delmode &lt;span class=&quot;token string single-quoted-string&quot;&gt;&#39;(^|&#92;.)(googlevideo|snapchat|tiktok|youtube)&#92;.com$&#39;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Previously this used a file &lt;code&gt;/etc/pihole/blocked-sites&lt;/code&gt; but that got fiddly and required wrapper scripts to work from cron ... this Just Works.&lt;/p&gt;
&lt;p&gt;I haven&#39;t found a commenting solution for this static-hosted blog yet, you can use &lt;a href=&quot;https://gitlab.wgtn.cat-it.co.nz/chrisburgess/working-loud/issues&quot;&gt;chrisburgess/working-load issue queue&lt;/a&gt; for comments if you have a suggestion.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Hugo custom shortcode for WR links</title>
    <link href="https://chris.bur.gs/hugo-custom-shortcode/"/>
    <updated>2020-02-26T00:00:00Z</updated>
    <id>https://chris.bur.gs/hugo-custom-shortcode/</id>
    <content xml:lang="en" type="html">&lt;p&gt;false adds a shortcode implementation for Hugo.&lt;/p&gt;
&lt;p&gt;This commit adds a shortcode implementation for commits in this repo 😁&lt;/p&gt;
&lt;p&gt;You can use it like: false.&lt;/p&gt;
&lt;p&gt;See &lt;a href=&quot;https://gitlab.wgtn.cat-it.co.nz/chrisburgess/working-loud/-/tree/main/layouts/shortcodes&quot;&gt;layouts/shortcodes&lt;/a&gt; for this.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Hugo, Gitlab, let&#39;s go!</title>
    <link href="https://chris.bur.gs/hugo-gitlab-lets-go/"/>
    <updated>2020-02-24T00:00:00Z</updated>
    <id>https://chris.bur.gs/hugo-gitlab-lets-go/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I caught Ben Bradshaw&#39;s &lt;a href=&quot;https://wiki.wgtn.cat-it.co.nz/wiki/Pizza_Thursday&quot;&gt;Pizza Thursday&lt;/a&gt; talk on &lt;a href=&quot;https://alfresco.wgtn.cat-it.co.nz/share/page/site/pizza-thursday/document-details?nodeRef=workspace://SpacesStore/5240b7a3-76b2-4bf9-b8a6-50ca93a27d4b&quot;&gt;How to turn Gitlab into a &#39;90s web host&lt;/a&gt; and decided I should get around to doing this.&lt;/p&gt;
&lt;p&gt;Here&#39;s how I set this up:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cloned the &lt;a href=&quot;https://gitlab.com/pages/hugo&quot;&gt;example Hugo Gitlab Pages repo&lt;/a&gt; to a local folder&lt;/li&gt;
&lt;li&gt;Blew away and re-inited the git repo in that folder&lt;/li&gt;
&lt;li&gt;Created a new personal repo on the internal Gitlab&lt;/li&gt;
&lt;li&gt;Messed around with this repo until I was happy&lt;/li&gt;
&lt;li&gt;Added the personal repo as the remote origin&lt;/li&gt;
&lt;li&gt;Pushed my code and watched the CI task build&lt;/li&gt;
&lt;li&gt;Via the repo sidebar, I found the Pages URL&lt;/li&gt;
&lt;li&gt;Updated config.toml to reflect the base URL&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>I&#39;m leaving Fuzion today!</title>
    <link href="https://chris.bur.gs/thanks-fuzion/"/>
    <updated>2017-06-06T07:45:00Z</updated>
    <id>https://chris.bur.gs/thanks-fuzion/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Today is my last day working for Fuzion! It&#39;s been a great experience. For the last four years I&#39;ve enjoyed being part of the Fuzion team, supporting non-profit and charitable orgs (and the odd business!), working to support their efforts in online campaigns. I&#39;ve also had a fantastic opportunity to work with the CiviCRM community worldwide, and it&#39;s been a privilege to be part of this.&lt;/p&gt;
&lt;p&gt;I joined Fuzion in 2013, but my connection to Pete runs back to a decade ago, and hopefully far into the future too. He&#39;s been a hugely supportive employer and really has his priorities screwed on right. Thanks Pete :D&lt;/p&gt;
&lt;p&gt;I&#39;ve enjoyed the Drupal &amp;amp; CiviCRM parts of our work, but more importantly the community aspects of what we achieve. Not just &amp;quot;today I helped an org I believe in&amp;quot;, but more that through our contributions to the CiviCRM project, we&#39;re able to help today&#39;s organisation, and many around the world, and some in future we don&#39;t even anticipate today. The most important part of that is not the code we contribute, but the &lt;em&gt;community which builds that code&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I&#39;m so grateful for the support to learn and contribute that Fuzion has offered me in my time here, and for all the many lessons I&#39;ve learned along the way.&lt;/p&gt;
&lt;p&gt;I don&#39;t know yet whether my next gig will include any CiviCRM requirements, but I do plan to stay involved. Civi folks - should you ever find yourself near the bottom of the globe in Dunedin, get in touch! We have some excellent breweries I&#39;d like to show you ...&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Hubot SSLLabs</title>
    <link href="https://chris.bur.gs/hubot-ssllabs/"/>
    <updated>2017-06-06T07:45:00Z</updated>
    <id>https://chris.bur.gs/hubot-ssllabs/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Tonight I spent a few minutes on the couch rolling a &lt;a href=&quot;https://github.com/xurizaemon/hubot-ssllabs&quot;&gt;hubot-ssllabs plugin&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It uses Qualys SSL Labs API to check your site&#39;s SSL config and report back in your project chat.&lt;/p&gt;
&lt;p&gt;Enjoy :)&lt;/p&gt;
&lt;p&gt;[ lost image ]&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>CiviCon 2017 recap</title>
    <link href="https://chris.bur.gs/civicon-2017/"/>
    <updated>2017-05-21T21:32:00Z</updated>
    <id>https://chris.bur.gs/civicon-2017/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Oops.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>John Key is watching ...</title>
    <link href="https://chris.bur.gs/john-key-is-watching/"/>
    <updated>2017-05-21T21:32:00Z</updated>
    <id>https://chris.bur.gs/john-key-is-watching/</id>
    <content xml:lang="en" type="html">&lt;p&gt;It seems I made this at some point, with the help of my 8yr / 6yr assistants. That&#39;s the (then) Prime Minister of NZ enjoying your viewing selections.&lt;/p&gt;
&lt;p&gt;(I think between when I made it and now, browsers started supporting &lt;code&gt;clip-path&lt;/code&gt; properly?)&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://chris.bur.gs/john-key-is-watching-raw/&quot;&gt;Mobile view&lt;/a&gt;&lt;/p&gt;
&lt;iframe src=&quot;https://chris.bur.gs/john-key-is-watching-raw/&quot; width=&quot;640&quot; height=&quot;1136&quot;&gt;&lt;/iframe&gt;
</content>
  </entry>
  <entry>
    <title>Blackbird nest &amp; rPi cam</title>
    <link href="https://chris.bur.gs/blackbird-nestcam/"/>
    <updated>2015-12-04T17:45:00Z</updated>
    <id>https://chris.bur.gs/blackbird-nestcam/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Outside our bedroom window and beside the front door, a blackbird has nested in the rose bushes. After watching her for a few days at breakfast and saying hello out the bedroom window, we added a nest cam for a better viewpoint.&lt;/p&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;http://www.youtube.com/c/ChrisBurgess/live&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot; style=&quot;border: 1px solid #666; margin-bottom: 20px;&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://youtu.be/JKlW9DHOFYg&quot;&gt;nest cam on YouTube&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I was cautious about installing the camera, as I thought there were no eggs and had read that birds are more likely to abandon their nesting if they aren&#39;t invested fully. Turned out there were four eggs but the nest was quite deep and they weren&#39;t visible when holding a camera out the window to get a view.&lt;/p&gt;
&lt;p&gt;If making any changes near the nest, I&#39;ll watch her from the breakfast table, and get to work with any changes when she breakfasts soon after dawn. She knows we&#39;re here as she can see us in the bedroom window, so I think she&#39;s decided we&#39;re mostly harmless, if noisy.&lt;/p&gt;
&lt;p&gt;There was a bit of crawling around in the roof to get a Raspberry Pi within range of the nest, and some small holes drilled - yay for being a homeowner, I can drill where I like! Have to go back up there and wire the Pi power off the porch light so I can remove the extension cable snaking into the laundry ceiling.&lt;/p&gt;
&lt;p&gt;The rPi is running Raspbian Jessie, and the camera is a Microsoft HD-3000. From the feel of the base I thought I&#39;d be able to drill mounting holes into the camera, but it turns out the base was steel and broke a drill bit. I make a small wooden mount that the base slots into, so I can affix that to the underside of the eave.&lt;/p&gt;
&lt;p&gt;Initially I used &lt;a href=&quot;http://www.lavrsen.dk/foswiki/bin/view/Motion/WebHome&quot;&gt;Motion&lt;/a&gt; to capture the stream, which worked pretty much out of the box. For security &amp;amp; bandwidth reasons I decided to push the stream out to a CDN for viewing, so after a day of Motion I swapped to &lt;code&gt;avconv&lt;/code&gt; last night, and am now pushing it to YouTube (hence the embed above).&lt;/p&gt;
&lt;p&gt;Since night is too dark to see activity, I&#39;m deactivating the stream from dark (10pm) until dawn (5:30am). Running the &lt;code&gt;avconv&lt;/code&gt; stream inside a &lt;code&gt;tmux&lt;/code&gt; session to observe output - expected to have to do more to keep it running TBH, since video encoding, but so far it&#39;s fine.&lt;/p&gt;
&lt;p&gt;There&#39;s not really a lot of &amp;quot;how to&amp;quot; to this, since it worked out of the box after installing &lt;code&gt;libav-tools&lt;/code&gt;. Still I can share the successful results of some trial and error :)&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/xurizaemon/e338363d550016bcc8d6.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;&lt;em&gt;Cron entries to start &amp;amp; stop a named tmux session for the stream.&lt;/em&gt;&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/xurizaemon/795f1f8fab9dab448f74.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;&lt;em&gt;Shell script to run the actual encode.&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;YouTube seems to ignore streams without audio, so this adds silent audio channel.&lt;/li&gt;
&lt;li&gt;Submits FLV - YouTube grumbles about &amp;quot;unsupported codec&amp;quot; occasionally in the video manager, but &lt;code&gt;-c:v libx264&lt;/code&gt; doesn&#39;t encode efficiently on the Pi. Need to play with this.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pushing off to CDN and back for viewing introduces a short delay, so if I need to adjust the camera I swap back to Motion for a few minutes - it&#39;s much faster for local only delivery. It&#39;s probably possible to run the two side by side.&lt;/p&gt;
&lt;p&gt;Quite impressed with what the rPi and cheap camera can do here, and happy that YouTube appear to be offering unlimited free rtmp:// broadcast. I did check out &lt;a href=&quot;http://twitch.tv/&quot;&gt;Twitch.tv&lt;/a&gt; and Ustream, but found it easier to navigate YouTube&#39;s live broadcast setup than the others.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Letter to Santa</title>
    <link href="https://chris.bur.gs/letter-to-santa/"/>
    <updated>2015-12-01T18:45:00Z</updated>
    <id>https://chris.bur.gs/letter-to-santa/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Just a quick post &#39;cos I&#39;m doing &lt;a href=&quot;https://nzsecretsanta.nzpost.co.nz/&quot;&gt;#nzsecretsanta&lt;/a&gt; this year and have been wishing my secretsanta recipient posted more about what they want. So here&#39;s me paying it forward by saying what I like/where I&#39;m at!&lt;/p&gt;
&lt;p&gt;Last year I got some Sharpie sunglasses and a set of a dozen Sharpies - I really enjoyed the strong bright tones of the pens, which went well with my contour drawing style and liking for strong blocks of colour.&lt;/p&gt;
&lt;p&gt;In 2016 I will be -&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Crafting with wood (rediscovering this)&lt;/li&gt;
&lt;li&gt;Sketching&lt;/li&gt;
&lt;li&gt;Playing with technology lots&lt;/li&gt;
&lt;li&gt;Building (or finding) a team environment that supports ongoing learning&lt;/li&gt;
&lt;li&gt;Studying Mandarin (with Rowan, 6)&lt;/li&gt;
&lt;li&gt;Practicing Tae Kwon Do (with Hunter, 8)&lt;/li&gt;
&lt;li&gt;Riding bikes and playing outdoors with family&lt;/li&gt;
&lt;li&gt;Playing futsal / indoor netball / anything with balls&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://chris.bur.gs/blackbird-nestcam&quot;&gt;Watching blackbirds hatch outside our window&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We&#39;re going up to Auckland in a few weeks, looking forward to a summer in the north.&lt;/p&gt;
&lt;p&gt;Right now I&#39;m building the 2016 election website for the Australian Green Party, enjoying working with the various tools in play. They have a great distributed team and a lively mix of skills on deck.&lt;/p&gt;
&lt;p&gt;I may update this, it feels like a good &amp;quot;forward reflection&amp;quot; :)&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>KoalaSafe first impressions</title>
    <link href="https://chris.bur.gs/koalasafe-part-1/"/>
    <updated>2015-11-13T22:36:00Z</updated>
    <id>https://chris.bur.gs/koalasafe-part-1/</id>
    <content xml:lang="en" type="html">&lt;p&gt;A few months back I purchased a KoalaSafe off Kickstarter - at the time I didn&#39;t feel a pressing need for it, but the project hit a chord with me and I wanted to support it.&lt;/p&gt;
&lt;p&gt;After it arrived I reflected on how we use devices as a family, and decided not to plug it in. Cue forward to a couple of months later (last night) and Saira and I were talking about Minecraft, playing versus watching videos, and how to positively engage kids towards active engagement.&lt;/p&gt;
&lt;p&gt;I proposed that instead of prescriptive, negative approach (&amp;quot;get off the device&amp;quot;, &amp;quot;you should be playing not watching&amp;quot;), we should be embracing our kids interests and engaging positively (&amp;quot;show me how to build a snow golem&amp;quot;, &amp;quot;tell me what&#39;s happening here&amp;quot;).&lt;/p&gt;
&lt;p&gt;Saira asked me to keep an eye on usage over the weekends (peak times - I&#39;d &lt;em&gt;estimate&lt;/em&gt; the boys have about 3H/day screen time on weekends, and about 1H weekdays), and for us to review it after a bit of play. That reminded me that the KoalaSafe recorded usage stats (this device spent this much time on youtube) and that it was pretty much for this purpose.&lt;/p&gt;
&lt;p&gt;Also, parents use devices too, and seeing that usage might help us engage our own habits in ways that set good examples for our kids.&lt;/p&gt;
&lt;p&gt;So I found the KoalaSafe (which I&#39;d taken to work and stashed), and plugged it in this morning. It&#39;s going OK.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Internet seems to be a bit flaky on some devices.&lt;/li&gt;
&lt;li&gt;Wish I could budget an amount to be consumed.&lt;/li&gt;
&lt;li&gt;Not sure how to control from laptop!&lt;/li&gt;
&lt;li&gt;Can I hack on the platform?&lt;/li&gt;
&lt;li&gt;Can I change the SSID?&lt;/li&gt;
&lt;li&gt;Shared device - Parent mode&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=HUgv5MDF0cQ&quot;&gt;https://www.youtube.com/watch?v=HUgv5MDF0cQ&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Mandarin lessons</title>
    <link href="https://chris.bur.gs/mandarin-lessons/"/>
    <updated>2015-11-13T22:16:00Z</updated>
    <id>https://chris.bur.gs/mandarin-lessons/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Hunter got a do-bok for Tae Kwon Do and I knew Rowan was impressed with it, so in Australia I picked up a kung-fu suit for Rowan at the Sydney markets.&lt;/p&gt;
&lt;p&gt;About a week later he announced at dinner that he&#39;d like to learn Chinese, please. Saira taught him to count to ten, say hello and thankyou, and that was as best we could do with not knowing much Chinese to speak.&lt;/p&gt;
&lt;p&gt;Rowan persisted in asking, and via the NZ China Friendly Society&#39;s free weekly adult introductory classes (contact details) I found out that there is a Chinese school on Sundays in Dunedin, where you can learn Chinese art, Mandarin language, and Chinese dance.&lt;/p&gt;
&lt;p&gt;We tried a language lesson, and he was keen but the teacher indicated it would be hard for a student with no background to support him to catch up with a class of students who have Mandarin at home. Saira and I talked about this and decided to see if the art classes held his interest and gave him a foothold, so we did a couple of weeks of that.&lt;/p&gt;
&lt;p&gt;He thought that was ok, but after a couple of weeks announced from the shower the night before a Sunday session that he&#39;d be going back to the language class now. I&#39;m impressed by this determination.&lt;/p&gt;
&lt;p&gt;So - I&#39;ve doubled down on learning Mandarin myself (enjoying the HelloChinese app on mobile), Rowan is practicing hard and his teacher is delighted to see that he&#39;s as keen as he is.&lt;/p&gt;
&lt;p&gt;I didn&#39;t expect to end up learning Mandarin myself, but after studying Te Reo Maori last year I&#39;m finding I really enjoy the challenge and the experience. Even though I&#39;m not officially a member of the class - just sitting in to help Rowan get his feet on the ground for now.&lt;/p&gt;
&lt;p&gt;(There are also &lt;a href=&quot;http://nzchinasociety.org.nz/21991/mandarin-lessons-for-beginners-2/&quot;&gt;free adult classes in Dunedin&lt;/a&gt;, I haven&#39;t checked them out yet.)&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Dunedin, let&#39;s fly the flags</title>
    <link href="https://chris.bur.gs/dunedin-lets-fly-the-flags/"/>
    <updated>2015-09-28T06:38:00Z</updated>
    <id>https://chris.bur.gs/dunedin-lets-fly-the-flags/</id>
    <content xml:lang="en" type="html">&lt;p&gt;A year or two back, walking Dunedin and enjoying the details of our buildings, I started tuning into the the abandoned flagpoles that spike the skyline of our city. With our great stock of older architecture, we have a lot of flagpoles - and most of them are bare today.&lt;/p&gt;
&lt;p&gt;My proposal to Dunedin City Council and the property owners of Dunedin is that we run flags up all those poles.&lt;/p&gt;
&lt;p&gt;Right now, New Zealand is &lt;a href=&quot;http://flag.govt.nz/&quot;&gt;discussing the flag we&#39;ll fly in the future&lt;/a&gt;, and Dunedin has an opportunity to highlight our city&#39;s architecture and at the same time invite our citizens to engage more in discussions about the future of New Zealand&#39;s flag.&lt;/p&gt;
&lt;p&gt;Most New Zealanders have seen static representations of the various flags under discussion, in print or online. But flags aren&#39;t static things, they twist and fold and hang and stand in the breeze, and they are seen against the sky - not on paper or screen.&lt;/p&gt;
&lt;p&gt;Imagine if, a few weeks from the start of the referendum, every one of those flagpoles across our city had one of the six flags above flying from it. For the next several weeks, flags could swap places every few nights, giving people across the city the chance to see each of the new flags flying. Many would also notice their environment and surrounds anew.&lt;/p&gt;
&lt;p&gt;During the second referendum, we&#39;d repeat the process with the current and proposed flags.&lt;/p&gt;
&lt;p&gt;People here would notice aspects of the city anew as their eyes were drawn to the sky. We&#39;d build appreciation of the urban environment, which I think would benefit property owners. And over the summer tourism period it would be a stand-out feature of our city for visitors to notice.&lt;/p&gt;
&lt;p&gt;By engaging people in the voting process in late 2015 (and early 2016), familiarity with postal voting would be boosted close to the local elections in late 2016. I think boosting postal vote turnout in the flag referendum could only benefit turnout in the local elections to follow.&lt;/p&gt;
&lt;p&gt;What would it take to make this happen? I imagine getting the property owners of buildings across the city to see value in it, and a backer like the DCC supporting it by providing official backing, numerous flags and crew to keep the flags flying around the city. Probably some other ingredients too, but it seems within reach and worth trying.&lt;/p&gt;
&lt;p&gt;Those bare poles have long had me toying with the idea of how an art project might repurpose the abandoned open spaces where the flags once flew through the city ... I&#39;d love to see our city skies come to life like that.&lt;/p&gt;
&lt;p&gt;Image credits: Bad photoshoppery by yours truly, the middle four via &lt;a href=&quot;https://www.govt.nz/browse/engaging-with-government/the-nz-flag-your-chance-to-decide/&quot;&gt;Flag Consideration Project&lt;/a&gt;, current NZ flag via &lt;a href=&quot;http://thefourcornersclassroom.wikispaces.com/Group+9+-+Pre-Project&quot;&gt;Four Corners Classroom&lt;/a&gt;, Red Peak via &lt;a href=&quot;http://eveningreport.nz/2015/09/25/bryce-edwards-political-roundup-the-politics-of-nzs-red-peak/&quot;&gt;Evening Report NZ&lt;/a&gt;. No subliminal placement, only for my convenience.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Online Voting Trial - submission to Dunedin City Council &amp; outcome</title>
    <link href="https://chris.bur.gs/dunedin-online-voting-submission/"/>
    <updated>2015-09-21T09:20:00Z</updated>
    <id>https://chris.bur.gs/dunedin-online-voting-submission/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Until recently, a government working group and several local councils had been (fairly quietly) contemplating a live trial of online voting at the 2016 local body elections. Aside from a few articles in the ODT back in 2013, there had been little coverage of this.&lt;/p&gt;
&lt;p&gt;Dunedin City Council&#39;s meeting was today, and over the weekend I had the opportunity to talk to Chris Morris of the ODT and share some concerns about what online voting might mean. I spoke in the public forum at the meeting, and was really happy with the engaged and informed questions that the councillors asked.&lt;/p&gt;
&lt;p&gt;Sadly I don&#39;t have the questions asked in reply, but here&#39;s my submission to the council from today -&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Mayor Cull, Councillors.&lt;/p&gt;
&lt;p&gt;Thankyou for the opportunity to speak today. I want to applaud your motivations in contemplating online voting. I believe that you seek to improve representation by increasing access and engagement. I wish that online voting would address those issues, but I am here today because the internet is not a secure environment for democracy.&lt;/p&gt;
&lt;p&gt;I&#39;m an IT developer with clients in many countries, all of whom demand security for their data. Our work has been subjected to penetration testing and to active attacks on the internet, and I&#39;m familiar with the issues involved in securing systems online from the different methods of interference that can be attempted.&lt;/p&gt;
&lt;p&gt;I&#39;m concerned about a move to online voting because I feel the significant risks involved are being underplayed, and because many of the benefits cited do not hold up to investigation.&lt;/p&gt;
&lt;p&gt;Security researchers and experts generally agree that online voting has substantial risks of vulnerability. A secret ballot poses an especially hard problem for securing when there is no physical evidence of the actual votes placed. There are multiple opportunities for attackers, and little scope for remedy or recourse if the electoral process is abused.&lt;/p&gt;
&lt;p&gt;A group of American researchers describe their investigation of a 2010 online voting trial as follows: &amp;quot;Within 48 hours of the system going live, we had gained near- complete control of the election server. We successfully changed every vote and revealed almost every secret ballot. Election officials did not detect our intrusion for nearly two business days—and might have remained unaware for far longer had we not deliberately left a prominent clue.&amp;quot; (&lt;a href=&quot;https://jhalderm.com/pub/papers/dcvoting-fc12.pdf&quot;&gt;Halderman et al. &amp;quot;Attacking the Washington, D.C. Internet Voting System&amp;quot; Proc. 16th Conference on Financial Cryptography &amp;amp; Data Security, Feb. 2012&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Security expert Bruce Schneier summed up the state of his field in 2001. &amp;quot;Building a secure Internet-based voting system is a very hard problem, harder than all the other computer security problems we&#39;ve attempted and failed at. I believe that the risks to democracy are too great to attempt it.&amp;quot; (&lt;a href=&quot;https://www.schneier.com/crypto-gram/archives/2001/0215.html#10&quot;&gt;Bruce Schneier, Crypto-gram newsletter, 15 Feb 2001&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Online voting depends on the security not only of the systems running the voting process, but also the systems used to vote - and the majority of those systems, home and work PCs and mobile devices in the community are vulnerable to multiple software vulnerabilities.&lt;/p&gt;
&lt;p&gt;Users of computers fall prey to online deception frequently; a year ago, large numbers of users being tricked into opening a malicious attachment triggered a nationwide outage for Spark, our largest internet provider. (&lt;a href=&quot;http://www.nzherald.co.nz/business/news/article.cfm?c_id=3&amp;amp;objectid=11320100&quot;&gt;NZ Herald: Spark users experience internet meltdown&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;With online voting, individual users will be susceptible to a targetted attack, especially when a large number of internet users are attempting an unfamiliar task together. An attacker can falsify apparent voting environments and put those in front of voters, hijacking credentials and then re-using them to make the attackers vote selections on the live system.&lt;/p&gt;
&lt;p&gt;Attackers would have multiple ways to interfere with online voting - preventing votes, falsifying votes, destroying vote records, or denying access to voting systems. All of these techniques could be used to affect the outcome of an election in various ways.&lt;/p&gt;
&lt;p&gt;Securing and assuring a secret ballot in a digital environment is a particularly hard issue, and one which many nations and larger organisations than ours have attempted to address and walked away from. Australia experienced a significant security issue; in France faked votes were demonstrated; the Netherlands banned online voting outright as a result; Norway abandoned their attempt; Portugal, Spain and the UK all discontinued their efforts. Washington DC&#39;s trial - an actual trial, not a live test - was hacked within 48 hours. Most security experts agree that this is no easy task, and I see risks but little advantage for us in breaking new ground here.&lt;/p&gt;
&lt;p&gt;Proponents of online voting claim it may increase engagement - but experience has not shown this is a result to expect. When surveyed about reasons for not voting in recent general elections, NZ voters primary reason was &amp;quot;didn&#39;t get around to it, not interested or forgot&amp;quot;, and disengagement was the underlying cause for 40% of non-voters. (&lt;a href=&quot;http://www.stats.govt.nz/browse_for_stats/people_and_communities/Well-being/civic-human-rights/non-voters-2008-2011-gen-elections.aspx&quot;&gt;Stats NZ: Non-voters in 2008 and 2011 General Elections&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Engagement, not access, is the primary issue for voter turnout. If the council seeks to improve vote turnout, then that is where I believe the council should direct this funding. Again, I do applaud the DCC&#39;s interest in increasing engagement from citizens and in considering this approach.&lt;/p&gt;
&lt;p&gt;$165,000 to be part of a test with actual council elections is a significant investment, but online voting will carry a much higher pricetag for full implementation.&lt;/p&gt;
&lt;p&gt;My recommendation is that Dunedin not proceed with the proposed online voting pilot.&lt;/p&gt;
&lt;p&gt;Thanks for your attention.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Beau Murrah and Stu Fleming also spoke against online voting, and representatives from the NZ Blind Foundation (I think Diane, and another man whose name I forgot) spoke in favour of it on the grounds of how it enables more full representation of all voters. Councillor Wilson identified, I think correctly, that &amp;quot;accessible voting&amp;quot; and &amp;quot;online voting&amp;quot; are separate decisions, but I do appreciate that having had a taste of accessible voting online, the appeal would be great. I hope we&#39;ll see solutions here which don&#39;t require the risks of OV.&lt;/p&gt;
&lt;p&gt;The meeting decision was that Dunedin decided to not continue with the trial, which was the outcome I hoped for. Councillors discussed it in depth and I was glad after following the discussions in other cities to observe that many councillors understood the concerns that informed experts share about moving democratic process online. I was concerned to hear a few flag that cost rather than security was a primary motivator, but learned that in our democratic system, it comes down to how people vote - not why they vote!&lt;/p&gt;
&lt;p&gt;There was some media coverage, but the best moment of my day was getting an unexpected text from my Dad, probably having heard my comments on the radio, saying that he couldn&#39;t agree with me more. That felt great!&lt;/p&gt;
&lt;p&gt;I&#39;m really happy to have taken this first step in engaging with local council - not something I&#39;d aspired to, but it was inspiring and empowering to have an opportunity to be heard by the council members, and to see that they followed what I was saying. And I think that the outcome was a very positive thing for most Dunedin citizens.&lt;/p&gt;
&lt;p&gt;Some related coverage -&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.odt.co.nz/news/dunedin/356534/fears-voting-security&quot;&gt;ODT: Fears for online voting security - Chris Morris&lt;/a&gt;. After the last interview a few weeks ago, I managed my habit of double-negative kiwi-isms (&amp;quot;not such a stupid idea&amp;quot; =&amp;gt; &amp;quot;a good idea&amp;quot;). Great!&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.radionz.co.nz/news/regional/284835/dunedin-withdraws-from-online-voting-trial&quot;&gt;National Radio NZ: Dunedin Council pulls out of online voting trial - Ian Telfer, on Jim Mora&#39;s Afternoons show&lt;/a&gt;. (Six jurisdictions abandoned the process, but only one &lt;em&gt;banned&lt;/em&gt; it.)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.dunedintv.co.nz/news/dcc-will-not-take-part-nationwide-online-voting-trial&quot;&gt;Channel 9: DCC will not take part in nationwide online voting trial&lt;/a&gt;; bonus points for using &amp;quot;for the LOLz&amp;quot; in a TV interview!&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.odt.co.nz/news/dunedin/356656/council-says-no-online-voting-trial&quot;&gt;ODT: Council says no to online voting trial - Craig Borley&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;DCC should put the council meeting on &lt;a href=&quot;https://www.youtube.com/playlist?list=PLFEtwCPmuUfBu_mRafaPANv9GKXkiXjRw&quot;&gt;their YouTube&lt;/a&gt; eventually, should you want to watch the whole thing.&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Archimedes - a tool for tracking Drupal Sites</title>
    <link href="https://chris.bur.gs/archimedes-a-tool-for-tracking-drupal-sites/"/>
    <updated>2015-09-01T09:36:00Z</updated>
    <id>https://chris.bur.gs/archimedes-a-tool-for-tracking-drupal-sites/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Visited the Drupal Wellington Meetup tonight and did a quick presentation of a couple of integrations we&#39;re using with Archimedes to monitor a network of Drupal sites. Here are my notes on the same, in case anyone wants to look further!&lt;/p&gt;
&lt;h2&gt;Setup&lt;/h2&gt;
&lt;p&gt;Running Archimedes involves setting up an Archimedes server (&lt;a href=&quot;https://github.com/xurizaemon/archimedes_server&quot;&gt;profile&lt;/a&gt;) and installing a copy of the Archimedes module (&lt;a href=&quot;https://github.com/xurizaemon/archimedes&quot;&gt;module&lt;/a&gt;) on each client site. (Run &lt;code&gt;drush pm-update --security-only&lt;/code&gt; on the server at a minimum, since I&#39;ve not been updating the modules in the profile. You might also have to add some Bootstrap libs?)&lt;/p&gt;
&lt;p&gt;You&#39;ll then need to configure each site by copying the &lt;em&gt;Public Key&lt;/em&gt; from the server to each client, and setting the &lt;em&gt;Submission URL&lt;/em&gt; to point to the server&#39;s &lt;code&gt;$base_url&lt;/code&gt; + &lt;code&gt;/archimedes/update&lt;/code&gt;. These details are available from the server at &lt;code&gt;admin/reports/archimedes/settings&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Updates&lt;/h2&gt;
&lt;p&gt;Updates will happen daily per default, or whenever you run &lt;code&gt;drush arch-send&lt;/code&gt; on the client site. The server cron will process the incoming submissions via Drupal cron, so you may want to run the Archimedes&#39;s server&#39;s cron task frequently when there are updates in the queue - typically early morning.&lt;/p&gt;
&lt;h2&gt;Additional Integrations&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;I&#39;ll publish more about this shortly&lt;/strong&gt;, but we use some additional Drupal Views to expose data over the web to -&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hubot&lt;/li&gt;
&lt;li&gt;Dashing&lt;/li&gt;
&lt;li&gt;Munin&lt;/li&gt;
&lt;li&gt;Nagios&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&#39;s Hubot reporting on which sites use a module, and what versions they have installed. This is helpful on Thursdays, when security updates drop.&lt;/p&gt;
&lt;p&gt;[ lost image ]&lt;/p&gt;
&lt;p&gt;Here&#39;s Dashing telling us the current state of play on a Thursday, after some core and contrib security updates have shipped. We can click on these numbers to see a (protected) View of which sites need attention.&lt;/p&gt;
&lt;p&gt;[ lost image ]&lt;/p&gt;
&lt;p&gt;I don&#39;t have demo images for these, but we also generate Nagios and Munin config snippets from data gathered in Archimedes, and that lets us monitor site outages, track site load times, and graph additional figures like DB size, user registrations and other indicators of site activity.&lt;/p&gt;
&lt;h2&gt;Resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xurizaemon/archimedes_server&quot;&gt;Archimedes Server&lt;/a&gt;, the server profile.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xurizaemon/archimedes&quot;&gt;Archimedes&lt;/a&gt;, the client module.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xurizaemon/archimedes_extras&quot;&gt;Archimedes Extras&lt;/a&gt;, some additional checks you might want to explore :)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://dashing.io/&quot;&gt;Dashing&lt;/a&gt;, an exceptionally handsome dashboarding toolkit.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hubot.github.com/&quot;&gt;Hubot&lt;/a&gt;, your personal robot assistant.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://munin-monitoring.org/&quot;&gt;Munin&lt;/a&gt; and  &lt;a href=&quot;https://www.nagios.org/&quot;&gt;Nagios&lt;/a&gt;, tools to monitor your things.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/andyhmltn/frosty-meadow&quot;&gt;Frosty Meadow&lt;/a&gt;, which I used to bulk-rename our client sites for the purpose of demonstration. I used its &lt;a href=&quot;https://github.com/andyhmltn/frosty-meadow/blob/master/lib/data/metal.json&quot;&gt;metal mode&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Credits&lt;/h2&gt;
&lt;p&gt;We did a bit of this work, but Catalyst did most of the work with the release of Archimedes v1, which Josh Waihi presented back in 2010 (?) at Drupal Down Under in Brisbane.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>GovHack 2015 NZ Awards!</title>
    <link href="https://chris.bur.gs/govhack-2015-awards/"/>
    <updated>2015-08-17T06:32:00Z</updated>
    <id>https://chris.bur.gs/govhack-2015-awards/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Sooooo we went to the GovHackNZ awards, and came home with some awards. It was fun! Bit of a delayed update here, on account of a hangover and flights home yesterday.&lt;/p&gt;
&lt;p&gt;GovHackNZ flew us (Vincent Rijlaarsdam, Beau Murrah and I) to Wellington for the NZ awards ceremony at Te Papa this last weekend.&lt;/p&gt;
&lt;p&gt;We arrived and met some of the other teams &amp;amp; organisers - as mentioned previously we were the only team at the Dunedin event so we&#39;d not met any other teams as part of the weekend.&lt;/p&gt;
&lt;p&gt;Several of the projects that I liked most (School Stories, Project Lighthouse and GrocerFree) had been shortlisted, and this was especially good because I got to catch up with &lt;a href=&quot;https://twitter.com/the_nittygritty&quot;&gt;Robin&lt;/a&gt; from GrocerFree there and heard that Hyun-Jin and he were now seven weeks from the due date of their child. That moment was probably the absolute high point, and it was funny that Robin and I had been talking about many other things by email / IRC this year without touching on it.&lt;/p&gt;
&lt;p&gt;Anyway, as you may see from the image above, our weekend&#39;s work earned second place for Best National Team and a National Spirit of GovHack award, as well as (without competition!) the Local Spirit of GovHack award for Dunedin.&lt;/p&gt;
&lt;p&gt;We had a great night, enjoyed meeting the other participants, and felt really boosted by taking away lots of people commenting positively on the concept of the project. In particular Cherie gave some really kind comment when awarding the Spirit of GovHack award, observing - I&#39;m going from memory here -&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A lot of projects focussed on &amp;quot;home and heart&amp;quot;, as it&#39;s clearly a high priority to ensure the best possible place to live, schools to send your children to and so forth.&lt;br /&gt;
This project is one that makes it possible for the average person to go further than those decisions - it enables people to not only understand the legislative process but have an opinion and a say in how the greater decisions are being made and how the country is run.&lt;br /&gt;
It is truly important that everyone has that opportunity, which makes this project a shining example of what the spirit of GovHack really is.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That was ... wow, exciting to hear. I had an awesome time, we were very happy to come away with some awards, and are excited about continuing this year&#39;s project &amp;amp; helping make GovHack something bigger next year.&lt;/p&gt;
&lt;p&gt;And this evening I&#39;ve got an email from the Parliamentary Counsel Office (who run &lt;a href=&quot;https://legislation.govt.nz/&quot;&gt;legislation.govt.nz&lt;/a&gt;) asking about what we&#39;re doing next, so tomorrow I&#39;ll have to gather some thoughts for that conversation!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Getting TiddlyBot on the wireless</title>
    <link href="https://chris.bur.gs/tiddlybot-alternate-wifi-modules/"/>
    <updated>2015-08-01T03:56:00Z</updated>
    <id>https://chris.bur.gs/tiddlybot-alternate-wifi-modules/</id>
    <content xml:lang="en" type="html">&lt;p&gt;&lt;a href=&quot;http://www.pibot.org/tiddlybot/&quot;&gt;TiddlyBot&lt;/a&gt; arrived in the mail a week or two back! The boys and I had a fun time assembling it (really simple kit, no breakages) although there was a few days pause when we tried to mount the Pi and realised we needed a v2 Pi not a v1 ... &lt;a href=&quot;http://nicegear.co.nz/&quot;&gt;nicegear&lt;/a&gt; shipped it quickly and we picked up the next weekend. Once we had it assembled, the AP mode didn&#39;t work out of the box, so I&#39;m writing a quick post on how we got it to work so far.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;The TiddlyBot image v1.1 contains hostapd v0.8.x_rtw_r7475.20130812_beta while Debian currently has v1.0; the Pibot crew recommend the Edimax EW-7811Un and confirm it works with a few others. We had a miscellany of wifi devices for pi, all of which have worked out of the box on Raspbian. I didn&#39;t want to wait for another part to ship, so here&#39;s the process I&#39;m using to bring up &lt;code&gt;hostapd&lt;/code&gt; starting with TiddlyBot and a v1.1 image.&lt;/p&gt;
&lt;p&gt;My wifi card is: &lt;code&gt;Bus 001 Device 004: ID 148f:5370 Ralink Technology, Corp. RT5370 Wireless Adapter&lt;/code&gt; - this device requires the &lt;code&gt;nl80211&lt;/code&gt; driver in hostapd.&lt;/p&gt;
&lt;p&gt;The version of hostapd in TiddlyBot v1.1 doesn&#39;t include RT5370 support, so I want to swap in the binary from Debian&#39;s current package of the same without changing Tiddly&#39;s startup configuration by installing the full Debian package.&lt;/p&gt;
&lt;p&gt;My current process is to -&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ethernet the device&lt;/li&gt;
&lt;li&gt;find Tiddly&#39;s DHCP assigned ethernet IP from the router&lt;/li&gt;
&lt;li&gt;ssh into Tiddly&lt;/li&gt;
&lt;li&gt;start raspi-config and grow the filesystem (default install is close to 4GB)&lt;/li&gt;
&lt;li&gt;reboot, ssh back in&lt;/li&gt;
&lt;li&gt;aptitude upgrade all the packages&lt;/li&gt;
&lt;li&gt;aptitude install libnl-route-3-200&lt;/li&gt;
&lt;li&gt;apt-get download hostapd&lt;/li&gt;
&lt;li&gt;dpkg -x the downloaded hostapd .deb to a temp dir&lt;/li&gt;
&lt;li&gt;copy the deb&#39;s binaries over the shipped ones in /usr/local/bin&lt;/li&gt;
&lt;li&gt;modify /etc/hostapd/hostapd.conf to use the correct driver for this USB wifi&lt;/li&gt;
&lt;li&gt;sudo hostapd -dd /etc/hostapd/hostapd.conf to check it works&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the above process works for you and you can connect to Tiddly over wifi at this point, then the steps to turning the bot on and connecting are still a little longer than &amp;quot;just works&amp;quot;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ethernet the device, turn it on&lt;/li&gt;
&lt;li&gt;ssh in as above&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sudo service hostapd restart&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;connect to Tiddly wifi&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I suspect at this point &lt;code&gt;hostapd&lt;/code&gt; is starting on boot, but works on second / delayed start; I don&#39;t yet know why. Still fiddly but will do for tonight - I have a working TiddlyBot today instead of when a new USB device would have arrived.&lt;/p&gt;
&lt;p&gt;Might not hurt to have hostapd logging to a specific file, that way we have a bit more to look at when things don&#39;t work. I&#39;ve been running it with -dd in a tmux session but not out of the init scripts.&lt;/p&gt;
&lt;p&gt;I&#39;d recommend asking questions on the &lt;a href=&quot;http://pibot.org/forum&quot;&gt;TiddlyBot forum&lt;/a&gt; rather than posting questions as comments here :)&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>GovHack 2015: We&#39;re Going to Wellington!</title>
    <link href="https://chris.bur.gs/govhack-2015-were-going-to-wellington/"/>
    <updated>2015-07-31T12:51:00Z</updated>
    <id>https://chris.bur.gs/govhack-2015-were-going-to-wellington/</id>
    <content xml:lang="en" type="html">&lt;p&gt;My phone rang today - I was home in bed with a cracker of a headache and wearing inflight sleep goggles to shield my brain from the terrible sunlight. An unfamiliar number, Cherie from GovHack calling to tell us that we&#39;d been shortlisted for a national prize as well as winning the regional Spirit of GovHack award.&lt;/p&gt;
&lt;p&gt;(If we picked up the regional award by entering a project &lt;em&gt;at all&lt;/em&gt; because there were no other teams in the South Island, we may owe that to &lt;a href=&quot;https://twitter.com/hackdunedin&quot;&gt;@hackdunedin&lt;/a&gt; for organising the event here - thanks again Phil and Josh! On the other hand, this kicked in the impostor syndrome klaxon: what if turning up was all we really achieved? But I don&#39;t really believe that.)&lt;/p&gt;
&lt;p&gt;Anyway - I&#39;d already booked my tickets figuring that participating in the awards is part of the package, but now all three of us - myself, &lt;a href=&quot;https://twitter.com/vinlew&quot;&gt;Vin&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/airbridgenz&quot;&gt;Beau&lt;/a&gt; - will be able to get to the awards ceremony.&lt;/p&gt;
&lt;p&gt;My motivation to enter was to get to do cool things, meet some other local hackers and work on a team. Being invited to be there for the awards as well is super exciting.&lt;/p&gt;
&lt;p&gt;See also: &lt;a href=&quot;https://chris.bur.gs/govhack-2015-legolas/&quot;&gt;post on Legolas&lt;/a&gt; and &lt;a href=&quot;https://chris.bur.gs/govhack-2015-my-writeup/&quot;&gt;on being part of GovHack&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>CiviCRM Contribute Form JS: Don&#39;t Rely on Priceset IDs.</title>
    <link href="https://chris.bur.gs/civicrm-contribute-form-js-dont-rely-on-priceset-ids/"/>
    <updated>2015-07-28T22:32:00Z</updated>
    <id>https://chris.bur.gs/civicrm-contribute-form-js-dont-rely-on-priceset-ids/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Since a lot of CiviCRM work is around supporter donations / memberships, at Fuzion we end up doing quite a lot of customisation. It&#39;s tempting when writing stylesheets and javascript to depend on the IDs and values provided in the page, but with CiviCRM this can lead to trouble down the track.&lt;/p&gt;
&lt;p&gt;CiviCRM manages price sets internally as a list of values in a DB table, and the assigned IDs for each price option may change if the priceset is edited. This can mean that a client change on a live site breaks a customisation that was previously working, for no reason other than them changing a different price on the same form.&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/xurizaemon/905e8363e4ace958ffb5.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;Where possible, try to check something which is not one of CiviCRM&#39;s autogenerated IDs relating to the priceset or the priceset value (=id of that price option). The above change is hardly any better - if someone changes &amp;quot;3 years&amp;quot; to &amp;quot;3 yrs&amp;quot; it&#39;ll still break, but at least it&#39;s more tied to the actual criteria we&#39;re checking than an ephemeral id from the database.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>NZ PHP Conf 2015 Speaker Lineup</title>
    <link href="https://chris.bur.gs/nz-php-conference-2015-speaker-lineup/"/>
    <updated>2015-07-16T08:28:00Z</updated>
    <id>https://chris.bur.gs/nz-php-conference-2015-speaker-lineup/</id>
    <content xml:lang="en" type="html">&lt;p&gt;In a few weeks NZ will have it&#39;s second (I think?) PHP conference. The topics look good and it&#39;s one of a few opportunities for NZ PHP developers to get together and learn. I saw a post on a mailing list promoting it today, and thought this would be a good opportunity to get together with other NZ developers and share some knowledge. (I know a few local developers through Codecraft, but it doesn&#39;t feel to me like there&#39;s a &lt;em&gt;community&lt;/em&gt; of PHP devs in Dunedin.)&lt;/p&gt;
&lt;p&gt;So - I headed over to the site, and &lt;a href=&quot;http://2015.phpconference.org.nz/speakers/&quot;&gt;checked out the speakers page to see what was on the cards&lt;/a&gt;, and I found myself thinking twice about going. I opted not to jump on the early bird pricing after all (I&#39;m not saying I won&#39;t still go).&lt;/p&gt;
&lt;p&gt;I reflected a while before posting this, because I&#39;m not confident that it&#39;s for me to speak on diversity at conferences when I&#39;m white &amp;amp; male, and I&#39;m not confident it&#39;s my job to look at a &lt;a href=&quot;http://2015.phpconference.org.nz/speakers/&quot;&gt;set of headshots&lt;/a&gt; and count gender or race. I&#39;m genuinely unsure about this tension; if I&#39;m silent then I may be tacitly enabling the status quo; if I speak out am I &amp;quot;white knighting&amp;quot; or denying someone who is more directly affected by lack of diversity the chance to speak?&lt;/p&gt;
&lt;p&gt;I am OK with sharing my feels (even if/when I&#39;m wrong), and I&#39;m a human goddamnit, and when &lt;em&gt;I&lt;/em&gt; look at the speakers page like that, I boggle at how un-representative of humanity a group of 22 people could possibly be after someone flys them to NZ from around the world.&lt;/p&gt;
&lt;p&gt;I guess this makes me really appreciate the great work that conferences I&#39;ve attended in the last twelve months (Kiwicon, NetHui, OS//OS, DrupalSouth, DevMob) have done towards keeping diversity in mind when they are organised, and I&#39;m intrigued to see how Kiwi PyCon 2015 (same weekend!) will do on the same front. Maybe I&#39;ll diversify myself to a Python conf instead.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>GovHack 2015: Legolas</title>
    <link href="https://chris.bur.gs/govhack-2015-legolas/"/>
    <updated>2015-07-06T22:32:00Z</updated>
    <id>https://chris.bur.gs/govhack-2015-legolas/</id>
    <content xml:lang="en" type="html">&lt;p&gt;(see also: &lt;a href=&quot;https://chris.bur.gs/govhack-2015-my-writeup/&quot;&gt;GovHack 2015: My writeup&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Law is commonly represented as a rigid &amp;quot;thing&amp;quot; that happens to us, and changes reduced to simple catchphrases that don&#39;t mean the same things that laws say. We become polarised and we are disengaged from taking part in how laws are built.&lt;/p&gt;
&lt;p&gt;Making law a conversation which all citizens can engage in might just enable the kinds of behaviour that open source has demonstrated; open source empowers users to contribute to fix bugs, and open laws can enable citizens to build a better society.&lt;/p&gt;
&lt;p&gt;Laws are a code, and our GovHack project for 2015 seeks to bring citizens onboard as stakeholders.&lt;/p&gt;
&lt;h2&gt;How it works (the concept)&lt;/h2&gt;
&lt;p&gt;Legislation is imported into an environment which supports free and nuanced discussion. This platform aims to provide -&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;clarity and visibility to inform citizens,&lt;/li&gt;
&lt;li&gt;a discussion model to permit informed public debate,&lt;/li&gt;
&lt;li&gt;SCM-style presentation of &amp;quot;fork and submit&amp;quot; to clarify proposed changes to laws,&lt;/li&gt;
&lt;li&gt;side-by-side &amp;quot;diff&amp;quot; views of amendments and legislative changes over time,&lt;/li&gt;
&lt;li&gt;submission tools to permit community organisations to canvas submissions, and&lt;/li&gt;
&lt;li&gt;support for additional data layers to allow all stakeholders to overlay additional information related to legislation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From there, we support various uses of the platform according to different needs -&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Susan wants to understand her legal responsibilities as an employer.&lt;/strong&gt; Using Legolas search and comment tools, she can use identify using terms familiar to her (&lt;em&gt;&amp;quot;holiday&amp;quot;, &amp;quot;sick leave&amp;quot;&lt;/em&gt;) and find both specific legislation and citizens experiences on relevant topics.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bruce and Alice are discussing a technical point of law on social media.&lt;/strong&gt; They can refer to specific aspects of various laws, linking directly to specific clauses of existing legislation as references in their discussion. Legislative changes over time are exposed using a Github-like &amp;quot;diff&amp;quot; interface which clarifies the dates and amendments made to laws.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dave feels that local laws on keeping chickens are unjust.&lt;/strong&gt; He can use Legolas to inform himself of the specifics of poultry law in his region, and to discover that there is already a lively community active around driving change in this area.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Members of a local RSA wish to submit to a bill on pension amendments.&lt;/strong&gt; The platform lets them gather for a meeting and read through the bill as a group, formulating responses to each section. When their input is gathered they can submit their group feedback in a clear format, contextualised alongside the proposed changes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rebecca is an MP who wants to better understand how citizens feel about an amendment before the house.&lt;/strong&gt; She can review comments on the existing law over the last few years, and identify contentious and troublesome aspects which need to be addressed in the upcoming debate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A lobby group wants to provide a library of legal advice to members.&lt;/strong&gt; The platform provides methods for additional data layers to be displayed in context with legislation, informing citizens of their responsibilities and rights under the law in ways which can be contextualised to their current needs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bob is a practicing lawyer who refers frequently to criminal law.&lt;/strong&gt; On his mobile device he has an app which exposes Legolas with additional overlays for both case law references retrieved from a popular commercial case law service and his company&#39;s internal team practice notes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Samwise is a citizen who aims to bring change to existing legislation on small business.&lt;/strong&gt; As well as discussing the law on the platform, he can submit a special form of comment which proposes an amendment to legislation; this is visible to others and can be discussed separately without having any special legal significance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Legolas is a proposal to support legislative documentation with tools for online engagement of the community - discussion relating to specific legislative &amp;quot;elements&amp;quot;, history as legislation evolves over time, search and crowd-sourced supplementary documentation, and whatever other APIs, extensions, hooks and elements may prove useful over time.&lt;/p&gt;
&lt;h2&gt;How the prototype works (under the hood)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;a &lt;a href=&quot;https://github.com/xurizaemon/legolas&quot;&gt;nodejs importer&lt;/a&gt; which retrieves legislation and does various things with it (commit XML &amp;amp; PDF to Git repo, push XML to storage on Drupal site)&lt;/li&gt;
&lt;li&gt;an XSL stylesheet which renders legislation to output formats&lt;/li&gt;
&lt;li&gt;a Drupal site (&lt;a href=&quot;https://github.com/xurizaemon/legolas/tree/drupal-7.x&quot;&gt;source&lt;/a&gt;) which as of now supports search, display and commenting&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>GovHack 2015: my writeup</title>
    <link href="https://chris.bur.gs/govhack-2015-my-writeup/"/>
    <updated>2015-07-04T13:02:00Z</updated>
    <id>https://chris.bur.gs/govhack-2015-my-writeup/</id>
    <content xml:lang="en" type="html">&lt;p&gt;GovHack 2015 was a fun weekend.&lt;/p&gt;
&lt;p&gt;We gathered on Friday night at the Sargood Centre in Logan Park - great venue, a large open space for working, and a room full of desks on wheels arranged exactly as you&#39;d expect to find them, especially if you factor in that the students are physed students.&lt;/p&gt;
&lt;p&gt;When I arrived there were two humans (@philwheeler and @vinlew) and a lot of chocolate biscuits.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I decided to jump onboard GovHack for multiple reasons. Firstly, as a hacker with a focus in social / political sphere, it&#39;s right up my alley. Secondly, my work environment has felt pretty isolated of late, and I wanted to do something as part of a team. Thirdly, I wanted a chance to explore some technology that&#39;s outside my regular work tasks.&lt;/p&gt;
&lt;p&gt;So I signed up pretty much immediately on hearing that it was happening in Dunedin, but I wasn&#39;t sure I&#39;d be able to do it until close to the last minute - it all fell together because Saira took leave from work expecting family to visit, and then they couldn&#39;t come at the last minute and I was able to commit my time to the weekend. Fortunate for me!&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Friday night, and there were three of us - Phil the organiser, and Vin and I the participants. Vin and I decided we should team up. Great! A team, first objective nailed. We were in a large nautically themed room, and decided to retire to the room full of desks and whiteboards to plan goals. We sketched out a handful of concepts that might work.&lt;/p&gt;
&lt;p&gt;Another participant arrived, Ross; he explained he was just checking it out, and probably wouldn&#39;t commit to the full weekend. (I don&#39;t think he came back.)&lt;/p&gt;
&lt;p&gt;We bounced the ideas around. I can&#39;t remember what the other ideas were, but one of them I put forward was something that had been in my head for a few years, the idea that our legislation should be more open, more of a discussion, more forum and less rulebook. This was at the forefront of my mind after OS//OS in Wellington a few weeks before, where amongst many other inspirational exchanges I saw &lt;a href=&quot;http://ben.balter.com/&quot;&gt;Ben Balter&lt;/a&gt; talk about open government and where it&#39;s going in the U.S.A.&lt;/p&gt;
&lt;p&gt;Huh, I&#39;m rambling and I haven&#39;t even touched on the idea of the project yet. Maybe I should split that out into another post. This is what happened, and it&#39;s late and I&#39;m typing like crazy. (see also: &lt;a href=&quot;https://chris.bur.gs/govhack-2015-legolas/&quot;&gt;GovHack 2015: My writeup&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;We bounced ideas around. One seemed to stand out, but I was a bit precious about it since it was an idea that I&#39;d been keeping quite close to my heart for a long time, and I wasn&#39;t sure it was the right thing to burn on a weekend project. Also I&#39;d come in hoping to contribute to someone else&#39;s idea! But ... well, we decided to go for it.&lt;/p&gt;
&lt;p&gt;(I don&#39;t think we kept a record of the other ideas.)&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;So the basic concept was: &lt;em&gt;A platform for making legislation a conversation.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;What&#39;s that mean? Well, I feel like citizens could engage more in the laws that we are bound by. To me, it seems the reason that &amp;quot;bad&amp;quot; laws stay on the rulebooks so long is that they are not obvious to us. If law was a thing you could check out, look at and discuss, would you feel differently about the laws that affect you? Would laws be written in language that were more accessible to citizens? Would laws change to reflect attitudes more quickly?&lt;/p&gt;
&lt;p&gt;So, while we officially have input into our legislation, the real process is harder than it needs to be for citizens.&lt;/p&gt;
&lt;p&gt;Damnit, I was going to write a separate post on that part.&lt;/p&gt;
&lt;p&gt;So. We picked a goal. We started doing some research and fleshing out concepts. Phil Wheeler gave us some great guidance on how to achieve results over the course of the weekend - identifying the whole set of tasks, which would include making a video and other requirements of entry before the close of play Sunday evening.&lt;/p&gt;
&lt;p&gt;I can&#39;t remember what time we left the Sargood Centre, but I went home and stayed up late, pulling apart some prior works, exploring the government dataset (NZ legislation) we were working with, and knocking together a crawler that could haul it into some platform to host discussion.&lt;/p&gt;
&lt;p&gt;Saturday morning came around, and I was up early and hacking, then took an hour to walk in to Logan Park on the slightly icy roads. Walking helps me think, especially when I haven&#39;t had a full complement of sleep. I reached out to Beau and got him onboard; three is better than two, and Beau&#39;s legal training and perspective brought a lot of useful understanding to what Vin and I had achieved.&lt;/p&gt;
&lt;p&gt;Saturday and Sunday were a whirl. I remember&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;turning up at the venue Sunday before the organisers had it open, tapping away on my laptop.&lt;/li&gt;
&lt;li&gt;reluctantly deciding to use Drupal as a content store - turned out it was a fine choice for what we needed it to do, and thanks to existing community contributions (thanks Dan Morrison) came with the tools to handle the legislation XML and output it nicely. (Writing your own XSL is another story, though, and an ongoing one.)&lt;/li&gt;
&lt;li&gt;deciding in the last few hours that it was OK to make something with just demo in mind.&lt;/li&gt;
&lt;li&gt;Clare Curran visiting us and saying heartwarmingly encouraging things. Thanks Clare!&lt;/li&gt;
&lt;li&gt;enjoying hacking in NodeJS a lot.&lt;/li&gt;
&lt;li&gt;finding XSL a headfuck.&lt;/li&gt;
&lt;li&gt;regretting the choice of project name pretty much from the get-go.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And I learned some interesting things too.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I enjoyed working with NodeJS; I still really like Javascript.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://drupal.org/project/services&quot;&gt;Drupal Services&lt;/a&gt; is pretty cool for remote actions; I&#39;ll be hanging on to this tool.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/drupal-services-api&quot;&gt;Drupal Services API npm package&lt;/a&gt; does a great job.&lt;/li&gt;
&lt;li&gt;With legislation whizzing past my eyes, I got to ponder about things like &lt;a href=&quot;https://twitter.com/xurizaemon/status/617244527812722688&quot;&gt;our treatment of undischarged bankrupts&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;I occasionally sat back to keep up via Twitter as other teams nationwide experienced the full gamut of GovHack feels.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And then it was Sunday evening, and we&#39;d turned in a video and made some commits in a repo, and imported a bunch of legislation and served it up, and thrown a site up around that to &amp;quot;sell&amp;quot; the idea, and made friends, and eaten all the chocolate biscuits, and we had to down tools.&lt;/p&gt;
&lt;p&gt;Today it&#39;s the school holidays - I probably won&#39;t have a chance to revisit this for a while, but it was great fun &amp;amp; you should get in on it next year.&lt;/p&gt;
&lt;p&gt;Here&#39;s a &lt;a href=&quot;https://storify.com/xurizaemon/govhack-dunedin-2015&quot;&gt;Storify of our weekend&lt;/a&gt;, probably missing bits.&lt;/p&gt;
&lt;p&gt;Thanks &lt;a href=&quot;https://twitter.com/hackdunedin&quot;&gt;@hackdunedin&lt;/a&gt; for organising it, and Phil Wheeler &amp;amp; Josh Jenkins for all the time you put into helping make it happen for us.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>South Dunedin Flooding</title>
    <link href="https://chris.bur.gs/south-dunedin-flooding/"/>
    <updated>2015-06-13T00:00:00Z</updated>
    <id>https://chris.bur.gs/south-dunedin-flooding/</id>
    <content xml:lang="en" type="html">&lt;p&gt;There was a lot of rain, and it flooded. Parts of Dunedin were not built for this - reclaimed swampland wants to be swamp once more.&lt;/p&gt;
&lt;p&gt;I got to see it first hand while sandbagging flooded houses, and it made me angry to see how much housing had been built with disregard for the environment it was being put in - concrete floor pensioner flats at ground level, with not even a door jamb to stop the water.&lt;/p&gt;
&lt;p&gt;My favourite moment of the night was carrying sandbags past an elderly couple, lights on and watching the show happen around them, on the other side of a ranch slider door. They seemed to be having fun, and their recliner chairs were positioned to watch through their glass window, and I hoped they were warm and appreciating a shared moment as I was carrying bags of sand to the houses behind them.&lt;/p&gt;
&lt;p&gt;(You might see a video below this line? If I haven&#39;t stuffed up the embed, and you aren&#39;t blocking content from that site ... or it&#39;s &lt;a href=&quot;https://plus.google.com/+ChrisBurgess/posts/LW7n4Cb66Ut&quot;&gt;here&lt;/a&gt; but it&#39;s not all that fancy anyway.)&lt;/p&gt;
&lt;!-- Place this tag in your head or just before your close body tag. --&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;https://apis.google.com/js/plusone.js&quot;&gt;&lt;/script&gt;
&lt;!-- Place this tag where you want the widget to render. --&gt;
&lt;div class=&quot;g-post&quot; data-href=&quot;https://plus.google.com/+ChrisBurgess/posts/LW7n4Cb66Ut&quot;&gt;&lt;/div&gt;
</content>
  </entry>
  <entry>
    <title>Archimedes - Drupal platform for managing Drupal platforms</title>
    <link href="https://chris.bur.gs/archimedes-1/"/>
    <updated>2015-06-13T00:00:00Z</updated>
    <id>https://chris.bur.gs/archimedes-1/</id>
    <content xml:lang="en" type="html">&lt;p&gt;When you have Drupal sites you have Drupal updates, and if you have update notification emails turned on then you&#39;ll have lots of notices in your INBOX too.&lt;/p&gt;
&lt;p&gt;At first I thought more informative update notices would help, but it can be a weekly deluge. We used Droptor for a while too, but it was hosted so it only did what was in the box.&lt;/p&gt;
&lt;p&gt;Archimedes allows you to centralise this deluge of notices / &amp;quot;critical business intelligence&amp;quot; into a single website. It allows Drupal sites to report in to a central (Drupal) service which collates info about each site - update notices but also other site data (number of nodes, users; DB, file sizes; etc).&lt;/p&gt;
&lt;p&gt;The site data comes in as nodes for each website and records upgrade status for each module / theme so you can view your sites in cross-section instead of digging through emails -&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;which production sites are reporting views at version less than 7.x-3.11?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Once this data is collected together you can query it much more easily, and make pretty dashboards or chatbot commands to look up useful facts about how bad thursday just got.&lt;/p&gt;
&lt;p&gt;Archimedes was first presented at &lt;a href=&quot;http://2011.drupaldownunder.org/session/archimedes-application-monitoring&quot;&gt;Drupal Down Under 2011&lt;/a&gt; by Josh Waihi, who developed it as an internal tool for Catalyst NZ. I started using it that year and&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Atom Remote Edit UI &amp; speed of opening remote files</title>
    <link href="https://chris.bur.gs/atom-remote-edit-vs-tramp/"/>
    <updated>2015-04-24T09:28:00Z</updated>
    <id>https://chris.bur.gs/atom-remote-edit-vs-tramp/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I quite like Atom as an editor - it ticks most of the boxes for an editor I&#39;ll enjoy using. I&#39;ve been using Emacs for a Very Long Time and it&#39;s one of the harder habits to kick in this world.&lt;/p&gt;
&lt;p&gt;Atom has a dialog UI which it seems to encourage - most Atom plugins follow suit and use the same interface to present a list of options. It&#39;s very reminiscent of the sheet that Sublime Text presented for option lists, and maybe in turn that interface came from another editor I don&#39;t recognise.&lt;/p&gt;
&lt;p&gt;I use Tramp in Emacs to open files on remote servers. Sometimes you want to edit a file on a remote server from the editor instance you&#39;re already using. I noticed how much slower this experience seemed to be when using Atom than when using Emacs, so I made a quick screencapture of it and was surprised to see how much longer it really was.&lt;/p&gt;
&lt;p&gt;Note the benefit of tab completion here too - in opening the same file path using Tramp, I made two typos which were corrected so fast I didn&#39;t recall making them until watching the animation play back.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Emacs + Tramp&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;[ lost image ]&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Atom + Remote Edit&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;[ lost image ]&lt;/p&gt;
&lt;p&gt;The time to set up the SSH connection is similar both times, but the delay IMO is introduced where Atom&#39;s sheet interface doesn&#39;t have as quick a tab-selection (even though it presents a visual list of options to choose from, a feature Tramp lacks).&lt;/p&gt;
&lt;p&gt;Another interesting observation watching those animations is the time Atom&#39;s Remote Edit spends saying &amp;quot;no matches found&amp;quot;. While the plugin isn&#39;t &amp;quot;selling&amp;quot; anything, the psychology of showing a &amp;quot;no matches found&amp;quot; message while waiting for files to show might be looked into - I expect just changing the message would improve perceived performance. (Brings &lt;a href=&quot;http://www.smashingmagazine.com/2015/11/why-performance-matters-part-2-perception-management/&quot;&gt;this recent article about website experience and perception management&lt;/a&gt; to mind for me.)&lt;/p&gt;
&lt;p&gt;This has also shown me how much I tend to keep a memory of file paths on remote systems; to me the visual interface just slows things down, but I also tend to perceive unexpected layouts as a bit of a bug (mostly this comes up when a predictable structure isn&#39;t kept to consistently). If I didn&#39;t have that internal reference, I&#39;d probably not mind picking from a list each time. As it is, I haven&#39;t the patience!&lt;/p&gt;
&lt;p&gt;Both Atom &amp;amp; its Remote Edit plugin are great tools - thanks @sveale and the Atom team for your work. I&#39;m still flapping between Atom and Emacs on the desktop, but I&#39;ll be sticking with Tramp for this task. Implementing a Tramp style UX on Atom would be a fun project.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Trajectory, 2015</title>
    <link href="https://chris.bur.gs/trajectory-2015/"/>
    <updated>2015-02-19T08:44:00Z</updated>
    <id>https://chris.bur.gs/trajectory-2015/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Hi 2015! I&#39;ve got plans for you.&lt;/p&gt;
&lt;p&gt;Life&#39;s good at present - I enjoyed 2014 working with &lt;a href=&quot;https://fuzion.co.nz/&quot;&gt;Fuzion Aotearoa&lt;/a&gt;, I&#39;m excited to see what 2015 can bring.&lt;/p&gt;
&lt;p&gt;Workwise I&#39;ve spent a lot of the summer break thinking about our company&#39;s improving our internal systems for collaboration and teamwork - we&#39;re a small distributed team and I&#39;m&lt;/p&gt;
&lt;p&gt;OK so here&#39;s a set of 2015 goals.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Walk _____m / week&lt;/li&gt;
&lt;li&gt;Stretch session &lt;em&gt;n&lt;/em&gt; times a week&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&#39;s a set of intents, things I&#39;m still working out how to make measurable.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Teamwork&lt;/li&gt;
&lt;li&gt;Diligence&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Teamwork&lt;/h2&gt;
&lt;p&gt;I like that 2015 is the Year of the Sheep. Henry tells me &amp;quot;it&#39;s the worst year of the Chinese Zodiac, they hate it over there&amp;quot;. I love &lt;em&gt;Caprinae&lt;/em&gt;, and the isolation of remote work was a big takeaway from 2014 for me (and a driver for focussing on improving our company teamplay). Sheep and goats are about community for me, and one of my goals for this year is being part of an everyday community.&lt;/p&gt;
&lt;h2&gt;Diligence&lt;/h2&gt;
&lt;p&gt;Recently I took a day to focus on my professional development trajectory for the year, and as part of the day&#39;s reflection took a walk through the Botanical Gardens. In the flowing waters of the Leith was a forgotten shopping trolley, which I sketched, and ended up with this.&lt;/p&gt;
&lt;p&gt;I have no idea why. Anyway ...&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>2014 Year in review</title>
    <link href="https://chris.bur.gs/year-in-review/"/>
    <updated>2015-01-06T23:14:00Z</updated>
    <id>https://chris.bur.gs/year-in-review/</id>
    <content xml:lang="en" type="html">&lt;p&gt;It&#39;s the start of 2015 and I figured why not follow on from the year&lt;br /&gt;
in reviews offered everywhere with my own. Hi! I&#39;m writing this for my&lt;br /&gt;
own reflection more than for public consumption, but sharing it might&lt;br /&gt;
just encourage me to be a bit more engaged. MAYBE.&lt;/p&gt;
&lt;p&gt;At the start of 2015 I&#39;m thinking most about family - we had Christmas&lt;br /&gt;
in Dunedin with Saira&#39;s parents and my brother James visited, but we&lt;br /&gt;
missed being in Auckland with the rest of our families. Living in&lt;br /&gt;
Dunedin puts us further from our siblings and parents, but living here&lt;br /&gt;
means more time for our family nucleus - something we both feel pretty&lt;br /&gt;
strongly about. Times like Christmas throw this choice into pretty&lt;br /&gt;
stark relief though.&lt;/p&gt;
&lt;p&gt;In 2014 I reflected a lot on my career path after some conversations&lt;br /&gt;
with our school principal when she had her regular principal&#39;s&lt;br /&gt;
appraisal. I saw her approach to career which takes a longer term view&lt;br /&gt;
to professional development than I had considered, and wanted to&lt;br /&gt;
establish similar perspective for myself. This forced me to ask&lt;br /&gt;
whether a larger workplace might give more opportunity to learn from&lt;br /&gt;
teammates - currently I&#39;m in a solitary office with a team of four~six&lt;br /&gt;
distributed about NZ, and I sense that I could be learning more FIXME&lt;br /&gt;
&amp;quot;be the lions tail&amp;quot;.&lt;/p&gt;
&lt;p&gt;My approach to this was heavily influenced by a single tweet FIXME&lt;br /&gt;
which I saw from @br3nda while attending FIXME. I value both our&lt;br /&gt;
family&#39;s happy life in Dunedin and my supportive employer, and I&lt;br /&gt;
realised that by doubling down on that commitment and improving my&lt;br /&gt;
existing situation rather than seeking to replace it, I could ramp up&lt;br /&gt;
my learning and improve things for our team.&lt;/p&gt;
&lt;p&gt;I got sunk into improving our processes - something I&#39;d felt strongly&lt;br /&gt;
about since joining the team a year or two ago, but had lacked&lt;br /&gt;
confidence about driving change with, not wanting to impose change&lt;br /&gt;
on a team that I was the newest member of.&lt;/p&gt;
&lt;p&gt;In 2015 I&#39;ll work to strengthen this aspect of my work, and extend it&lt;br /&gt;
into other areas of my life.&lt;/p&gt;
&lt;p&gt;One element of the work environment which I think we&#39;d struggled with&lt;br /&gt;
was a sense of teamplay - making sure that team members are informed&lt;br /&gt;
of changes and the reasons for them, that configurations are&lt;br /&gt;
documented and so forth. My aim was to improve visibility across the&lt;br /&gt;
organisation so that we all had a sense of what the wider team was&lt;br /&gt;
engaged in. This is an ongoing project but I think the results have&lt;br /&gt;
been good for us so far. Over the summer break I&#39;ve taken advantage of&lt;br /&gt;
the quiet time to further improve systems by automating monitoring,&lt;br /&gt;
reporting and informing - aspects of the business infrastructure which&lt;br /&gt;
I think had previously left a lot of the operation &amp;quot;off the radar&amp;quot;.&lt;/p&gt;
&lt;p&gt;Election 2014 was a big part of the year. Being in the engine room of&lt;br /&gt;
the Green Party&#39;s election process also helped inform some of my&lt;br /&gt;
thoughts on teamplay in projects. The election result wasn&#39;t stellar&lt;br /&gt;
but it was the predictable outcome.&lt;/p&gt;
&lt;p&gt;I studied Te Ara Reo Maori level 2 with Te Wananga o Aotearoa, which&lt;br /&gt;
was a really good experience. I recommend the course massively (it was&lt;br /&gt;
a zero-fee course, in the local community rooms with about two dozen&lt;br /&gt;
students). Kia ora Ritchie and Shaun for your great work this year&lt;br /&gt;
... I learned a lot, including a good reminder that being a student&lt;br /&gt;
requires commitment. I came away with much more confidence than I&lt;br /&gt;
started, which was what I hoped to achieve; I don&#39;t think I&#39;ll enrol&lt;br /&gt;
again next year - I&#39;d love to but I want to focus my efforts closer to&lt;br /&gt;
family and work for now.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Election 2014&lt;/li&gt;
&lt;li&gt;BoT chair&lt;/li&gt;
&lt;li&gt;BCAC rates&lt;/li&gt;
&lt;li&gt;Happy teams tweet&lt;/li&gt;
&lt;li&gt;NZG computing, wordpress hack&lt;/li&gt;
&lt;li&gt;Drupal South 2014&lt;/li&gt;
&lt;li&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Learning where you are</title>
    <link href="https://chris.bur.gs/learning-at-work/"/>
    <updated>2014-11-25T07:20:00Z</updated>
    <id>https://chris.bur.gs/learning-at-work/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I&#39;ve been reflecting on work lately. I enjoy the team I work with, the crews we collaborate with and our clients who are all pretty damn good people. But I feel like I could be learning a lot more, and wondering about something more challenging and which I&#39;ll learn more from.&lt;/p&gt;
&lt;p&gt;Then: how can I make my current work more challenging and more learning oriented, so that I&#39;m learning and being challenged in new ways without having to lose the great team I&#39;m with.&lt;/p&gt;
&lt;p&gt;Tonight was graduation ceremony for TWoA Te Ara Reo Maori Level 2 which I studied this year. Great to complete the course (I had a lot of re-learning to do with regard to study habits!) but tonight signalled a new space in my life too (for now - Level 4 next year?).&lt;/p&gt;
&lt;p&gt;So for now I can see a few opportunities to build things up where I&#39;m at -&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Continuing improvements to our technical systems, which I&#39;m enjoying&lt;/li&gt;
&lt;li&gt;Improving team collab within the company, which I think is something we could improve on&lt;/li&gt;
&lt;li&gt;Growing things to a point where we can scale up beyond the current size, which I hope is a goal we share&lt;/li&gt;
&lt;li&gt;Learning new tech to apply within our company, which I realise was easier to justify when I was self-employed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not quite the same as being on team with people I can learn from, but I&#39;m finding opportunities for improvement where I&#39;m at all the same. And there are lots of good reasons to be happy here too.&lt;/p&gt;
&lt;p&gt;Also: Tonight I started jotting down small things I&#39;ve learned during the day, partly to combat the nag that &amp;quot;I&#39;m not learning anything&amp;quot;. I am - it&#39;s just easy to not notice sometimes. (Maybe I want to be learning things I value more; maybe I want to be learning more deeply.)&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>IBM Watson Personality Insights is a horoscope machine at the Victoria Park Markets</title>
    <link href="https://chris.bur.gs/ibm-watson-dotmatrix/"/>
    <updated>2014-11-23T06:32:00Z</updated>
    <id>https://chris.bur.gs/ibm-watson-dotmatrix/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Here&#39;s what it says about me, based on being fed a randomly handy post from this here blog.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You are sentimental.&lt;/p&gt;
&lt;p&gt;You are empathetic: you feel what others feel and are compassionate towards them. You are proud: you hold yourself in high regard, satisfied with who you are. And you are assertive: you tend to speak up and take charge of situations, and you are comfortable leading groups.&lt;/p&gt;
&lt;p&gt;You are motivated to seek out experiences that provide a strong feeling of connectedness.&lt;/p&gt;
&lt;p&gt;You are relatively unconcerned with tradition: you care more about making your own path than following what others have done. You consider helping others to guide a large part of what you do: you think it is important to take care of the people around you.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I suspect it&#39;s probably fairly right, for the instance of me which existed while writing &lt;a href=&quot;https://chris.bur.gs/foo&quot;&gt;that post&lt;/a&gt;, but these sweeping &amp;quot;you are X&amp;quot; claims seem to have all the credibility of this yellow machine at the Victoria Park Markets which took your DOB and spat out a page of dotmatrix horoscopic proclamations.&lt;/p&gt;
&lt;p&gt;I kinda liked that machine too, and computers are pretty good at horoscopes anyway since it&#39;s just maths right? But I didn&#39;t &lt;em&gt;trust&lt;/em&gt; it.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Making team-talk a part of everyday work</title>
    <link href="https://chris.bur.gs/why-hipchat/"/>
    <updated>2014-11-23T06:32:00Z</updated>
    <id>https://chris.bur.gs/why-hipchat/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Lately I&#39;ve been studying my task focus, and noticing which activities are &amp;quot;interruptive&amp;quot; in the sense that they break the flow of productive work. Number one on my hitlist is Skype, which is the primary comms channel at Fuzion currently.&lt;/p&gt;
&lt;p&gt;I don’t know why Skype is “more interruptive” for me than other apps. I know that I’ve talked before with Eileen about preferring IRC, where I feel quite comfortable ignoring a question until I finish the task at hand - and that’s fine IRC etiquette. Doesn’t feel so on Skype. For me, one of these is asynchronous I/O and one of them is not.&lt;/p&gt;
&lt;p&gt;(Something here about the tyranny of the red badge notification icon. Another time.)&lt;/p&gt;
&lt;p&gt;Anyway, we&#39;re a distributed team, we need a way to communicate as one, and teamtalk is important I feel. When Skype is the primary comms, and it&#39;s quite an interruptive one, it encourages siloed communication - I&#39;m talking to Eileen, Pete&#39;s talking to Gemma, but it&#39;s direct and 1:1 unless we drag everybody in.&lt;/p&gt;
&lt;p&gt;Team conversation isn&#39;t like that; it&#39;s something that everyone can opt into, and of which we can comfortably tune out as appropriate. The same rules that apply to overhearing workmates converse across the room; you can chime in and drop out and there&#39;s virtually no mental cost to doing so, because it&#39;s part of being a human.&lt;/p&gt;
&lt;p&gt;(Very relevant: &lt;a href=&quot;https://www.evernote.com/shard/s2/sh/e54ea520-55a2-41ca-887a-36c73ed622fb/9ea14577655c7fb4fa545060e565d86d&quot;&gt;this &#39;shard&#39; from #devmob by @br3nda today&lt;/a&gt;, &lt;a href=&quot;https://chris.bur.gs/images/happy-teams-by-br3nda.png&quot;&gt;localcopy&lt;/a&gt; - thanks Brenda for sharing as much of your process as you do.)&lt;/p&gt;
&lt;p&gt;I&#39;d like to see each of our team members to be part of a shared company space, make being present &lt;em&gt;as a team&lt;/em&gt; a thing that is part of normal daily process, and have a place where we can discuss what&#39;s happening. There are great integrations available for us to leverage a tool like Slack or HipChat with the other tools that are part of our daily flow.&lt;/p&gt;
&lt;p&gt;So! I proposed today via email to the team that we try swapping out Skype for HipChat for a month or so, and see how it works out. I&#39;ll update after a month and give our experience (well, my experience and what I hear back).&lt;/p&gt;
&lt;p&gt;I&#39;m also +1 on Eileen&#39;s response to this suggestion, that we should try and reconvene around our daily standups. Again I think having a common meeting place is critical to understanding where your fellow humans are at. A chatroom and a standup gather us in different dimensions; with standups a daily roundup of &amp;quot;what&#39;s happening for me&amp;quot; and especially sharing of what we&#39;ve been learning is really valuable. Working alongside the principal at our school has shown me how important it is to value shared learning and open reflection.&lt;/p&gt;
&lt;p&gt;I found the idea of standups challenging (don&#39;t put me on the spot!) when I first experienced it at Webscope, but I now think it&#39;s so important to support that space and make it a fixture of process, at least until it becomes something natural.&lt;/p&gt;
&lt;p&gt;I&#39;m enjoying engaging this happy team thing - it&#39;s reinvigorated me with regard to my work, and I&#39;m hoping that I can make a real positive change in our work environment.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Speedcurve</title>
    <link href="https://chris.bur.gs/speedcurve/"/>
    <updated>2013-10-10T21:22:00Z</updated>
    <id>https://chris.bur.gs/speedcurve/</id>
    <content xml:lang="en" type="html">&lt;p&gt;A couple of weeks ago I got to try out Speedcurve, which has just launched today.&lt;/p&gt;
&lt;p&gt;It&#39;s a smooth product which gives both a good high-level view of site performance over time, review the major speed factors (differing browser render time etc) and phases of page load, and index against other competing sites and the wider &amp;quot;ecosystem&amp;quot; of your site&#39;s sector. Also, the historic detail is like having a daily request trace (super useful) along with a very-low-resolution &lt;a href=&quot;http://archive.org/&quot;&gt;archive.org&lt;/a&gt; / screengrab (awww!) all of your own.&lt;/p&gt;
&lt;p&gt;I&#39;ve been doing some basic monitoring of site performance using Munin, and what I missed in Speedcurve were the ability to monitor the same set of sites (from previous posts, it&#39;s probably obvious that I&#39;m currently obsessing over NZ political party sites in the leadup to the NZ election) and the ability to manually adjust colours (I know, but it just doesn&#39;t make sense if the Green Party are orange and the National Party are green).&lt;/p&gt;
&lt;p&gt;Very small complaints there - one a beta account limitation (&lt;a href=&quot;http://speedcurve.com/pricing/&quot;&gt;Speedcurve plans and pricing&lt;/a&gt;), the other down the bottom of &lt;a href=&quot;https://speedcurve.uservoice.com/forums/193594-general/suggestions/4493314-allow-configurable-colours-per-site&quot;&gt;a list of things people want&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The one-off monitoring options will be a really good way to give sites a periodic review ... congrats to Mark on getting it out the door!&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Cloudspotter</title>
    <link href="https://chris.bur.gs/cloudspotter/"/>
    <updated>2013-10-10T11:38:00Z</updated>
    <id>https://chris.bur.gs/cloudspotter/</id>
    <content xml:lang="en" type="html">&lt;p&gt;&lt;code&gt;cloudspotter&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/xurizaemon/jekyll_cloudspotter_tag&quot;&gt;A Cloudspotter Spottings tag on Github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Might get pretty big - the list of images I mean, not the Jekyll plugin - so probably don&#39;t want this as a gallery so much as an ongoing stream. Will do for a first stab.&lt;/p&gt;
&lt;p&gt;Should have called it &amp;quot;CLOUDTAG&amp;quot; though.&lt;/p&gt;
&lt;p&gt;Based on &lt;a href=&quot;https://github.com/tsmango/jekyll_flickr_set_tag&quot;&gt;Thomas Mango&#39;s Flickr Set plugin for Jekyll&lt;/a&gt;.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Pow</title>
    <link href="https://chris.bur.gs/pow/"/>
    <updated>2013-09-10T21:16:00Z</updated>
    <id>https://chris.bur.gs/pow/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I know it&#39;s pretty old news, but I just have to say that as someone who&#39;s messed around with hosts files and apache configs for some years now, I&#39;m freaking love the convenience and lazy simplicity of &lt;a href=&quot;http://pow.cx/&quot;&gt;Pow&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Even when it&#39;s just proxying back to LAMP apps, it&#39;s still saving me a bunch of effort. (I&#39;ve automated that effort with .sh in the past, but Pow is just soooo easy.)&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Political sites speedtested (again!)</title>
    <link href="https://chris.bur.gs/political-sites-speedtested-again/"/>
    <updated>2013-07-08T12:36:00Z</updated>
    <id>https://chris.bur.gs/political-sites-speedtested-again/</id>
    <content xml:lang="en" type="html">&lt;p&gt;I&#39;m not bored of this yet. I&#39;m just getting started. Trying &lt;a href=&quot;http://www.webpagetest.org/&quot;&gt;WebPageTest.org&lt;/a&gt; this time to compare results - it isn&#39;t quite as pretty but I think it&#39;s more detailed.&lt;/p&gt;
&lt;p&gt;Times are initial / second load. These results are substantially different to the previous two rounds.&lt;/p&gt;
&lt;h2&gt;Labour&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://www.webpagetest.org/result/130708_D0_Q89/&quot;&gt;1.871s / 1.270s.&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;United Future&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://www.webpagetest.org/result/130708_1D_QAN/&quot;&gt;2.876s / 1.094s&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Greens&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://www.webpagetest.org/result/130708_BX_QAY/&quot;&gt;2.983s / 1.900s&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;NZ First&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://www.webpagetest.org/result/130708_6H_QQY/&quot;&gt;4.310s / 2.405s&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;ACT&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://www.webpagetest.org/result/130708_3Z_Q8Z/&quot;&gt;6.545s / 4.272s&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Mana&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://www.webpagetest.org/result/130708_KD_Q9C/&quot;&gt;7.514s&lt;/a&gt; (did we lose the test machine with the initial load time?)&lt;/p&gt;
&lt;h2&gt;National&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://www.webpagetest.org/result/130708_YF_QA6/&quot;&gt;10.121s / 6.093s&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Maori&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://www.webpagetest.org/result/130708_D4_Q9S/&quot;&gt;14.619s / 4.078s&lt;/a&gt;&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>PGP in Parliament.</title>
    <link href="https://chris.bur.gs/mps-with-pgp/"/>
    <updated>2013-06-28T12:34:00Z</updated>
    <id>https://chris.bur.gs/mps-with-pgp/</id>
    <content xml:lang="en" type="html">&lt;p&gt;With greater awareness of the visibility of email content and traffic, I got wondering whether any NZ elected representatives had published PGP keys in order to give the public a means of communicating with some level of privacy.&lt;/p&gt;
&lt;p&gt;S/MIME? I don&#39;t know of a way to look up Parliamentary certificates to obtain the recipients&#39; cert, and otherwise you&#39;d have to get an email from an MP or EA with their cert. (If such a directory exists, I&#39;ll be happy to update this.)&lt;/p&gt;
&lt;p&gt;I checked the MIT PGP keyserver for results in the @parliament.govt.nz domain as well as seven @partyname.org.nz domains (sorry if I forgot your favourite party, I&#39;ve got that wrong before). The results are few, out of date, and not encouraging.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://pgp.mit.edu:11371/pks/lookup?search=parliament.govt.nz&quot;&gt;PGP keys in @parliament.govt.nz&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rodney Hide&lt;/li&gt;
&lt;li&gt;EAs belonging to (I think) ACT, Labour and National (1 each)&lt;/li&gt;
&lt;li&gt;A few others&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;http://pgp.mit.edu:11371/pks/lookup?search=national.org.nz&quot;&gt;PGP keys in @national.org.nz&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One person&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;http://pgp.mit.edu:11371/pks/lookup?search=labour.org.nz&quot;&gt;PGP keys for @labour.org.nz&lt;/a&gt;, &lt;a href=&quot;http://pgp.mit.edu:11371/pks/lookup?search=greens.org.nz&quot;&gt;@greens.org.nz&lt;/a&gt;, &lt;a href=&quot;http://pgp.mit.edu:11371/pks/lookup?search=act.org.nz&quot;&gt;@act.org.nz&lt;/a&gt;, &lt;a href=&quot;http://pgp.mit.edu:11371/pks/lookup?search=maoriparty.org&quot;&gt;@maoriparty.org&lt;/a&gt;, &lt;a href=&quot;http://pgp.mit.edu:11371/pks/lookup?search=unitedfuture.org.nz&quot;&gt;@unitedfuture.org.nz&lt;/a&gt;, &lt;a href=&quot;http://pgp.mit.edu:11371/pks/lookup?search=mana.org.nz&quot;&gt;@mana.org.nz&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Nobody.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Friends in Parliamentary Service tell me that encryption is a hard proposition for use within Parliament, because there is tight control over what software can and can&#39;t be installed. I&#39;m not sure what encryption options are offered by Parliamentary Services IT, but it doesn&#39;t look like it&#39;s PGP.&lt;/p&gt;
&lt;p&gt;I emailed a handful of MPs to enquire about their current approaches to private / secure comms with consituents. National MP Amy Adams, the Communications and Information Technology Minister, referred me to Parliamentary Services. Through a response from PS I learned about &lt;a href=&quot;http://ict.govt.nz/common-capabilities/communications/seemail&quot;&gt;SEEMail&lt;/a&gt; (&amp;quot;Secure Electronic Environment Mail&amp;quot;), which provides secure mail gateways to communicate between SEEMail-using agencies. By my reading of &lt;a href=&quot;http://ict.govt.nz/common-capabilities/communications/seemail&quot;&gt;this document&lt;/a&gt;) it seems like SEEMail&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;is only available to participating organisations,&lt;/li&gt;
&lt;li&gt;has mail gateways apply rules to communications (eg recipient&lt;br /&gt;
constraints on a document marked [RESTRICTED],&lt;/li&gt;
&lt;li&gt;outsources PKI management to Dimension Data,&lt;/li&gt;
&lt;li&gt;encrypts and secures based on &lt;em&gt;agency&lt;/em&gt; certificates rather than&lt;br /&gt;
certificates for individual recipients&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;References -&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.computerworld.co.nz/article/472424/smx_opens_cloud-based_email_service_govt_agencies/&quot;&gt;SMX announcement of cloud-based email for govt agencies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.scoop.co.nz/stories/PO0406/S00093.htm&quot;&gt;SEEMail keeps delivering (2004 PR)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://ict.govt.nz/guidance-and-resources/standards-compliance/authentication-standards/security-assertion-messaging-framework/appendix-overview-in/&quot;&gt;ICT.govt.nz overview of internet messaging&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>Hello Pete &amp; CiviCRM.</title>
    <link href="https://chris.bur.gs/hello-civicrm/"/>
    <updated>2013-06-28T10:41:00Z</updated>
    <id>https://chris.bur.gs/hello-civicrm/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Pete and I met several years back - I&#39;d been doing a bit of consulting&lt;br /&gt;
for the Green Party of Aotearoa NZ and Pete was working for them at&lt;br /&gt;
Parliamentary Services.&lt;/p&gt;
&lt;p&gt;They were looking towards a system for managing their contact&lt;br /&gt;
systems. Nandor had got a toehold for open source representation in&lt;br /&gt;
Wellington.&lt;/p&gt;
&lt;p&gt;Someone (probably Mark) had asked me for input on the options on the&lt;br /&gt;
table, and realising that no-one was batting for a web-based, open&lt;br /&gt;
source CRM option, I went to the AGM that year in Nelson to show what&lt;br /&gt;
you could do with CiviCRM.&lt;/p&gt;
&lt;p&gt;(Until that point, I hadn&#39;t heard of CiviCRM, but it seemed like a&lt;br /&gt;
better philosophical fit for the org than the options they were&lt;br /&gt;
considering - all very windows, not very open in either the web&lt;br /&gt;
delivered or open source aspects.)&lt;/p&gt;
&lt;p&gt;I demoed CiviCRM, and then skipped the rest of the AGM (introvert!).&lt;/p&gt;
&lt;p&gt;Saira and I went over to stay near Farewell Spit, far away from the&lt;br /&gt;
meetings and technology and stuff. When we came back the party had&lt;br /&gt;
decided that CiviCRM was a good option and could they have one&lt;br /&gt;
please. And that was how I ended up doing a lot of work with CiviCRM&lt;br /&gt;
over the five years since.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Friday night, delighted.</title>
    <link href="https://chris.bur.gs/friday-night-delighted/"/>
    <updated>2013-06-28T10:41:00Z</updated>
    <id>https://chris.bur.gs/friday-night-delighted/</id>
    <content xml:lang="en" type="html">&lt;p&gt;It&#39;s Friday night, I&#39;m delighted. Home and quiet, the fire warms the&lt;br /&gt;
room, it&#39;s midwinter cold outside. About a month ago I started working&lt;br /&gt;
for &lt;a href=&quot;http://fuzion.co.nz/&quot;&gt;Fuzion&lt;/a&gt;, run by a long-time colleague and&lt;br /&gt;
friend Peter Davis. I&#39;m loving it - a great crew to work with, it&lt;br /&gt;
feels like a good fit.&lt;/p&gt;
&lt;p&gt;Right now, my delight is 100% because I get to &lt;em&gt;leave work at&lt;br /&gt;
work&lt;/em&gt;. I&#39;ve always had a &amp;quot;protestant work ethic&amp;quot; thing going - I&#39;m not&lt;br /&gt;
a particularly &lt;em&gt;hard&lt;/em&gt; worker, mind - just kind of driven to work in&lt;br /&gt;
order to reassure myself that I&#39;m doing my bit.&lt;/p&gt;
&lt;p&gt;So tonight, I finished work around five, and then I went on a spree of&lt;br /&gt;
custom code removal (11K LoC gone in 1:15, yo - what a freaking mess&lt;br /&gt;
that was), and then I hopped the bus home. And I&#39;m home, and putting&lt;br /&gt;
the kids to bed, and feeling absolutely healthy because I don&#39;t feel&lt;br /&gt;
like I have a pathological need to pick up some unfinished business&lt;br /&gt;
later on in the evening.&lt;/p&gt;
&lt;p&gt;(Sorry, peeps with unfinished business, see ya Monday! Unless it&#39;s&lt;br /&gt;
urgent enough that you &lt;em&gt;really&lt;/em&gt; need me. You&#39;ve got my number.)&lt;/p&gt;
&lt;p&gt;I love having a job. It took me a long time to realise it was a&lt;br /&gt;
healthy option for me.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>A second survey of NZ political party sites</title>
    <link href="https://chris.bur.gs/a-second-survey-of-nz-political-party-sites/"/>
    <updated>2013-06-28T09:47:00Z</updated>
    <id>https://chris.bur.gs/a-second-survey-of-nz-political-party-sites/</id>
    <content xml:lang="en" type="html">&lt;p&gt;A second survey, to follow &lt;a href=&quot;https://chris.bur.gs/posts/2013-04-06-unscientific-survey&quot;&gt;the first unscientific survey of NZ political sites&lt;/a&gt;. On the eve of the Ikiroa-Rawhiti by-election no less.&lt;/p&gt;
&lt;p&gt;I&#39;m only following this up out of duty to my past and future selves, you understand. Also, it occurs to me that FPT probably offers a permalink to the report, and I could skip the images copypasta. But hey, consistency?&lt;/p&gt;
&lt;p&gt;I am a little bit excited to see Mr Zeman&#39;s been busy putting together &lt;a href=&quot;http://speedcurve.com/&quot;&gt;Speedcurve&lt;/a&gt;, it looks pretty fab from the demo. Hi Mark!&lt;/p&gt;
&lt;p&gt;EDIT: Oh dear! I forgot ACT existed both times I ran this survey. I&#39;m belatedly adding them in third place below, well done Berend, fastest of the Drupal sites here.&lt;/p&gt;
&lt;h3&gt;United Future&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://www.cactuslab.com/work/type/supermodel-cms/&quot;&gt;Supermodel&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[ lost image ]&lt;/p&gt;
&lt;h3&gt;Mana&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://wordpress.org/&quot;&gt;Wordpress&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[ lost image ]&lt;/p&gt;
&lt;h3&gt;ACT&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://drupal.org/&quot;&gt;Drupal 7&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[ lost image ]&lt;/p&gt;
&lt;h3&gt;Maori Party&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://www.nzwebsites.info/&quot;&gt;Website Administration System&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[ lost image ]&lt;/p&gt;
&lt;h3&gt;NZ Labour&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://drupal.org/&quot;&gt;Drupal 6&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[ lost image ]&lt;/p&gt;
&lt;h3&gt;NZ First&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://drupal.org/&quot;&gt;Drupal 7&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[ lost image ]&lt;/p&gt;
&lt;h3&gt;NZ Greens&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://drupal.org/&quot;&gt;Drupal 6&lt;/a&gt;. Lots of room for improvement here!&lt;/p&gt;
&lt;p&gt;[ lost image ]&lt;/p&gt;
&lt;h3&gt;NZ National Party&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;http://www.vadmin.co.nz/&quot;&gt;VAdmin 3.0 Enterprise&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[ lost image ]&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>NZ673 flight deck</title>
    <link href="https://chris.bur.gs/nz673-flight-deck/"/>
    <updated>2013-06-05T04:36:00Z</updated>
    <id>https://chris.bur.gs/nz673-flight-deck/</id>
    <content xml:lang="en" type="html">&lt;p&gt;The lads got invited onto the flight deck of NZ673 by the burser, and met Arjen the pilot.&lt;/p&gt;
&lt;div class=&quot;grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4&quot;&gt;
&lt;p&gt;&lt;a href=&quot;https://chris.bur.gs/photos/nz673-flight-deck/IMG_3206/&quot; class=&quot;&quot;&gt;&lt;br /&gt;
&lt;div class=&quot;aspect-square relative&quot;&gt;&lt;br /&gt;
&lt;img eleventy:widths=&quot;200,600&quot; src=&quot;https://chris.bur.gs/media/nz673-flight-deck/IMG_3206.JPG&quot; alt=&quot;IMG_3206&quot; class=&quot;object-cover w-full h-full group-hover:scale-105 transition-transform duration-300&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;
&lt;/div&gt;&lt;br /&gt;
&lt;div class=&quot;p-2 text-center&quot;&gt;&lt;br /&gt;
IMG_3206&lt;br /&gt;
&lt;/div&gt;&lt;br /&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://chris.bur.gs/photos/nz673-flight-deck/IMG_3207/&quot; class=&quot;&quot;&gt;&lt;br /&gt;
&lt;div class=&quot;aspect-square relative&quot;&gt;&lt;br /&gt;
&lt;img eleventy:widths=&quot;200,600&quot; src=&quot;https://chris.bur.gs/media/nz673-flight-deck/IMG_3207.JPG&quot; alt=&quot;IMG_3207&quot; class=&quot;object-cover w-full h-full group-hover:scale-105 transition-transform duration-300&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;
&lt;/div&gt;&lt;br /&gt;
&lt;div class=&quot;p-2 text-center&quot;&gt;&lt;br /&gt;
IMG_3207&lt;br /&gt;
&lt;/div&gt;&lt;br /&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://chris.bur.gs/photos/nz673-flight-deck/IMG_3208/&quot; class=&quot;&quot;&gt;&lt;br /&gt;
&lt;div class=&quot;aspect-square relative&quot;&gt;&lt;br /&gt;
&lt;img eleventy:widths=&quot;200,600&quot; src=&quot;https://chris.bur.gs/media/nz673-flight-deck/IMG_3208.JPG&quot; alt=&quot;IMG_3208&quot; class=&quot;object-cover w-full h-full group-hover:scale-105 transition-transform duration-300&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;
&lt;/div&gt;&lt;br /&gt;
&lt;div class=&quot;p-2 text-center&quot;&gt;&lt;br /&gt;
IMG_3208&lt;br /&gt;
&lt;/div&gt;&lt;br /&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://chris.bur.gs/photos/nz673-flight-deck/IMG_3209/&quot; class=&quot;&quot;&gt;&lt;br /&gt;
&lt;div class=&quot;aspect-square relative&quot;&gt;&lt;br /&gt;
&lt;img eleventy:widths=&quot;200,600&quot; src=&quot;https://chris.bur.gs/media/nz673-flight-deck/IMG_3209.JPG&quot; alt=&quot;IMG_3209&quot; class=&quot;object-cover w-full h-full group-hover:scale-105 transition-transform duration-300&quot; loading=&quot;lazy&quot; /&gt;&lt;br /&gt;
&lt;/div&gt;&lt;br /&gt;
&lt;div class=&quot;p-2 text-center&quot;&gt;&lt;br /&gt;
IMG_3209&lt;br /&gt;
&lt;/div&gt;&lt;br /&gt;
&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Turns out that on the domestic 747-400s at least, it&#39;s not all shiny shiny Pan Am Raybans up front. Just raw functional machinery.&lt;/p&gt;
&lt;p&gt;Lucky boys - I&#39;d thought that cockpit visits were a thing of the past, but we&#39;re not that far gone.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Letting go is easy</title>
    <link href="https://chris.bur.gs/crmapi-in-smarty/"/>
    <updated>2013-04-05T23:37:00Z</updated>
    <id>https://chris.bur.gs/crmapi-in-smarty/</id>
    <content xml:lang="en" type="html">&lt;p&gt;CiviCRM blows me away. I love the people who use it and the development team, but I kind of hate the codebase. It&#39;s insane that I&#39;ve been working with it as long as I have.&lt;/p&gt;
&lt;p&gt;Still I&#39;m drawn back ... why is that? I&#39;m not sure.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>CiviCRM, payment, and processing</title>
    <link href="https://chris.bur.gs/civicrm-payment-processors/"/>
    <updated>2013-04-05T23:37:00Z</updated>
    <id>https://chris.bur.gs/civicrm-payment-processors/</id>
    <content xml:lang="en" type="html">&lt;p&gt;CiviCRM&#39;s contribution pages are no Obama campaign. Certainly not out of the box. On the backend, there are issues too - like the fact that CiviCRM&#39;s forms layer drops submitted data to the DB while processing submissions, and that contribution form submissions contain credit card details. (It&#39;s OK, provided your contribution doesn&#39;t fail, CiviCRM will clear your PAN and CVV &lt;em&gt;really soon&lt;/em&gt;, promise.)&lt;/p&gt;
&lt;p&gt;Unless a client has a dedicated security team (in the CiviCRM space, probably only the EFF) I push them to use a hosted processor. I&#39;d rather you use PayPal than stored PANs in the DB. But onsite transactions are a thing, and if we&#39;re building modern contribution pages, we&#39;re not going to 302 you off to paypal mid process.&lt;/p&gt;
&lt;p&gt;So - how to get some contributions into CiviCRM and sidestep the horrors of CiviCRM&#39;s forms layer? Glad you asked :)&lt;/p&gt;
&lt;p&gt;For the AU Green Party in the 2010 Australian Federal Election, we added an API method to CiviCRM so that we could access transaction processing via the API. (Nothing to do with PAN storage, all about the horrors of Smarty at the time.) We used Drupal FAPI to build a simple form for the donate interface, then called &lt;code&gt;civicrm_api()&lt;/code&gt; from Drupal&#39;s form submit hook to do the CiviCRM layer stuff.&lt;/p&gt;
&lt;p&gt;This year is election year in Australia as well, and so the same question comes up. I expect we&#39;ll face this in 2014 with both Labour and the Greens in NZ using the Drupal/CiviCRM combo.&lt;/p&gt;
&lt;p&gt;If I was rewriting that three years later, I&#39;d be thinking about these things -&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How can CiviCRM&#39;s Contribute forms be better modularised? Last I saw of the contribute flow, it was a sprawling mess (and getting messier) of pledge blocks, profile includes, contribution and donation fieldsets, event and membership generation, and all sorts of other things.&lt;/li&gt;
&lt;li&gt;What parts &lt;em&gt;can&#39;t&lt;/em&gt; be done yet at API layer? This is the real kicker. I&#39;d really rather not have to clean up the various elements of form processing, but I can help make sure the right replacement elements come into play.&lt;/li&gt;
&lt;li&gt;How can payment processors expose themselves to the API? Currently CiviCRM payment processors implement some magic function names, but really seem to be a morass of special cases and checks.&lt;/li&gt;
&lt;/ul&gt;
</content>
  </entry>
  <entry>
    <title>CiviCRM, Smarty looking better</title>
    <link href="https://chris.bur.gs/civicrm-smarty-improving/"/>
    <updated>2013-04-05T12:24:00Z</updated>
    <id>https://chris.bur.gs/civicrm-smarty-improving/</id>
    <content xml:lang="en" type="html">&lt;p&gt;Tim Gummer asked me to look at a template tweak for Natural Areas a few days ago - they wanted to display a little extra contact info (membership type, status, what have you) on the contact summary page.&lt;/p&gt;
&lt;p&gt;I&#39;d been resistant to CiviCRM&#39;s Smarty syntax from days of yore when it was a crazy nested mess of .tpl files, and a right pain to substantially rejig in any shape except the defaults. I did not expect to have much fun ... in fact my initial impulse was to sidestep Smarty entirely and use JS + CiviCRM REST API to get the data. Yeah, that resistant.&lt;/p&gt;
&lt;p&gt;Well, since Tim asked nicely I figured we should take a look at it. And as it turned out, it wasn&#39;t so bad after all. It was still pretty crazy - I didn&#39;t look to see what happens on the backend, but we end up with three &lt;code&gt;crmapi&lt;/code&gt; calls here. It&#39;s still Smarty, and it&#39;s still CiviCRM.&lt;/p&gt;
&lt;script src=&quot;https://gist.github.com/xurizaemon/5318647.js&quot;&gt;&lt;/script&gt;
&lt;p&gt;That worked.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>CiviCRM upgrades via Vagrant</title>
    <link href="https://chris.bur.gs/vagrant-civicrm/"/>
    <updated>2013-04-05T12:24:00Z</updated>
    <id>https://chris.bur.gs/vagrant-civicrm/</id>
    <content xml:lang="en" type="html">&lt;p&gt;The last few weeks I&#39;ve been working for the NZ Green Party on an update to their CiviCRM DB. I expect a bit of suffering in any CiviCRM upgrade, and I wasn&#39;t disappointed. (I accept suffering as inevitable, but that doesn&#39;t mean I need to embrace it.)&lt;/p&gt;
&lt;p&gt;I wanted to kick off with a recipe which would let me reproducibly bring up upgraded versions of CiviCRM with a fresh copy of the site data. (See @todo Obfuscating CiviCRM test data for more on that.)&lt;/p&gt;
&lt;p&gt;(@todo I should write an embargo plugin for Jekyll so I can say &lt;code&gt;embargo: +3mo&lt;/code&gt; in my post&#39;s YAML.)&lt;/p&gt;
&lt;p&gt;(@todo I should write a @todo plugin too.)&lt;/p&gt;
&lt;p&gt;Ideally, all this would also build towards a more reproducible infrastructure for the NZ Green Party to hand out to developers. The current setup (a dev / staging box) was bottlenecking new developer contributions, and there were no quick answers to the &amp;quot;how do we bring a new team member onboard&amp;quot; available.&lt;/p&gt;
&lt;p&gt;I look forward to a situation where a new volunteer can be given instructions to deploy a Drupal/CiviCRM instance, fetch a sample dataset (defanged of sensitive info), and start hacking for world domination.&lt;/p&gt;
&lt;p&gt;The tools chosen so far for this round turned out to be -&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lots of RAM.&lt;/li&gt;
&lt;li&gt;Vagrant and VirtualBox for VMs, chef-solo to deployed instances. (Baby steps.)&lt;/li&gt;
&lt;li&gt;Librarian to manage a set of fairly standard LAMP (&amp;amp; drush) cookbooks.&lt;/li&gt;
&lt;li&gt;Drush make to build the Drupal / CiviCRM &amp;quot;app&amp;quot;.&lt;/li&gt;
&lt;li&gt;A custom DB upgrade script, which output on provision &amp;amp; called on demand.&lt;/li&gt;
&lt;li&gt;Patience.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The interesting elements turned out to be -&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CiviCRM upgrades are fraught. If your DB isn&#39;t in the exact configuration CiviCRM expects, it chokes hard on upgrade. CiviCRM upgrades and DB issues.&lt;/li&gt;
&lt;li&gt;CiviCRM relocation requires a few gentle taps in the right place. Deploying CiviCRM.&lt;/li&gt;
&lt;li&gt;Obfuscating DB content for sharing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;(@todo I plan on writing more.)&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Just the medicine</title>
    <link href="https://chris.bur.gs/just-the-meds/"/>
    <updated>2013-04-05T12:22:00Z</updated>
    <id>https://chris.bur.gs/just-the-meds/</id>
    <content xml:lang="en" type="html">&lt;p&gt;For the last several years I&#39;ve been rigging websites largely for NGOs and dot-orgs who are campaign focussed - typically political or non-govt, and as often as possible lefty liberals who are trying to save the world.&lt;/p&gt;
&lt;p&gt;Aldo asked me once to put my finger on what my aims were - I&#39;d said I wanted to do good, but not what. His question was the right one; I realised that rather than finding something to be passionate about, I&#39;d outsourced my motivations to orgs that were doing something &amp;quot;about right&amp;quot;.&lt;/p&gt;
&lt;p&gt;Realising that I was letting the situations and client pressures dictate what tools I was using was a good first step. I&#39;m not toolyvangelist but you need to be selective about what you&#39;re working with, especially if you want to maintain passion about your work.&lt;/p&gt;
&lt;p&gt;And I&#39;m not all that passionate about some of the tools I&#39;ve been using. I can make use of that - it&#39;s good to be critical and to be able to be dispassionate - but you don&#39;t want to go cold, and I want to relish working with the things on my bench.&lt;/p&gt;
&lt;p&gt;My favourite people are tool makers, not tool users. That&#39;s an evolutionary thing.&lt;/p&gt;
&lt;p&gt;And dogs. Dogs are good.&lt;/p&gt;
&lt;p&gt;Oh, the medicine thing in the title, that was another post. It can stay.&lt;/p&gt;
</content>
  </entry>
</feed>