<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.2">Jekyll</generator><link href="https://www.wassupy.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.wassupy.com/" rel="alternate" type="text/html" /><updated>2026-03-23T14:04:43+00:00</updated><id>https://www.wassupy.com/feed.xml</id><entry><title type="html">Microsoft Ergonomic Keyboard Acting Weird</title><link href="https://www.wassupy.com/2026/03/microsoft-ergo-keyboard-acting-weird.html" rel="alternate" type="text/html" title="Microsoft Ergonomic Keyboard Acting Weird" /><published>2026-03-05T00:00:00+00:00</published><updated>2026-03-05T00:00:00+00:00</updated><id>https://www.wassupy.com/2026/03/microsoft-ergo-keyboard-acting-weird</id><content type="html" xml:base="https://www.wassupy.com/2026/03/microsoft-ergo-keyboard-acting-weird.html">&lt;p&gt;&lt;img src=&quot;/assets/2026/IMG_3652.jpeg&quot; width=&quot;3811&quot; height=&quot;1720&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Here’s something that’s happened to me enough times that I’m writing about it:&lt;/p&gt;

&lt;p&gt;My MS Ergo Sculpt keyboard is acting funny. It double types, or misses key strokes, adds unexpected tildes, flashes my
terminal, or messes up my keyboard shortcuts, such as PasteBot’s cmd-shift-v.&lt;/p&gt;

&lt;p&gt;In my &lt;em&gt;many years&lt;/em&gt; of using this device I have found two solutions to the various ways my favorite keyboard acts up:&lt;/p&gt;

&lt;h2 id=&quot;rf-interferencemy-most-common-problem&quot;&gt;RF interference—my most common problem&lt;/h2&gt;

&lt;p&gt;Try moving the little USB dongle closer to the keyboard closer to the keyboard. Also try a regular USB-2 port if possible
(i.e. not a blue-colored port, if available). Ever since I started connecting the dongle with a cheap USB extension cable,
things have been great&lt;/p&gt;

&lt;h2 id=&quot;a-stuck-key&quot;&gt;A stuck key&lt;/h2&gt;

&lt;p&gt;I’m embarrassed to admit that each time this has happened, it’s taken me &lt;em&gt;far too long&lt;/em&gt; to figure out. This usually
happens to me when I have just cleaned my desk and wiped off my keyboard with a damp towel. &lt;strong&gt;The half-height function
keys on this keyboard are easily jammed.&lt;/strong&gt; Check each one to see if they are free. I seem to be really good at getting
the F5 key stuck.&lt;/p&gt;

&lt;p&gt;When one of those is jammed, my computer acts &lt;em&gt;very strangely&lt;/em&gt;. When this happened today it took writing a quick little keylogger to figure out what was happening.&lt;/p&gt;

&lt;p&gt;I wrote this:&lt;/p&gt;

&lt;div class=&quot;language-py highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;#!/usr/bin/env python3
&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Quartz&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;key_callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;proxy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;type_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;refcon&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;type_&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Quartz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kCGEventKeyDown&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;keycode&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Quartz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;CGEventGetIntegerValueField&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Quartz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kCGKeyboardEventKeycode&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;chars&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Quartz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;CGEventKeyboardGetUnicodeString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;# chars returns tuple(count, string)
&lt;/span&gt;        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chars&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chars&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chars&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;

        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;keycode=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;keycode&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; text=&apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;flush&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tap&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Quartz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;CGEventTapCreate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Quartz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kCGSessionEventTap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Quartz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kCGHeadInsertEventTap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Quartz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kCGEventTapOptionDefault&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Quartz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;CGEventMaskBit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Quartz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kCGEventKeyDown&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;key_callback&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed to create event tap. Grant Accessibility permissions.&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stderr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;sys&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;runloop_source&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Quartz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;CFMachPortCreateRunLoopSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Quartz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;CFRunLoopAddSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Quartz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;CFRunLoopGetCurrent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;runloop_source&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Quartz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kCFRunLoopCommonModes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;Quartz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;CGEventTapEnable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tap&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Listening for global key presses...&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Quartz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;CFRunLoopRun&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__name__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;__main__&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And ran it with this:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;python3 &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; venv .venv
&lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt; .venv/bin/activate
python &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; pip &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--upgrade&lt;/span&gt; pip
python &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;pyobjc-framework-Quartz
python ./keylogger.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The first time you run it, you’ll need to approve it in Accessibility settings. Then run it again.&lt;/p&gt;

&lt;p&gt;Then hammer the keyboard until the weirdness happens. In my case it immediately went nuts:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-plain&quot;&gt;ckeycode=9 text=&apos;v&apos;
vkeycode=7 text=&apos;x&apos;
xkeycode=8 text=&apos;c&apos;
ckeycode=9 text=&apos;v&apos;
keycode=96 text=&apos;&apos;
v^[[15~keycode=96 text=&apos;&apos;
^[[15~keycode=96 text=&apos;&apos;
^[[15~keycode=96 text=&apos;&apos;
^[[15~keycode=96 text=&apos;&apos;
^[[15~keycode=96 text=&apos;&apos;
^[[15~keycode=96 text=&apos;&apos;
^[[15~keycode=96 text=&apos;&apos;

# ...lots of repeating, without me pressing anything
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then I looked up keycode &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;96&lt;/code&gt; and saw it was &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;F5&lt;/code&gt;. Sure enough, that key was jammed.&lt;/p&gt;

&lt;p&gt;I was pretty close to buying a new keyboard 😮‍💨.&lt;/p&gt;</content><author><name></name></author><category term="technology" /><category term="code" /><summary type="html"></summary></entry><entry><title type="html">Crudely capture working files</title><link href="https://www.wassupy.com/2026/02/capture-working-files-before-they-disappear.html" rel="alternate" type="text/html" title="Crudely capture working files" /><published>2026-02-27T00:00:00+00:00</published><updated>2026-02-27T00:00:00+00:00</updated><id>https://www.wassupy.com/2026/02/capture-working-files-before-they-disappear</id><content type="html" xml:base="https://www.wassupy.com/2026/02/capture-working-files-before-they-disappear.html">&lt;p&gt;We recently had a situation where a tool was:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Creating a work directory, e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;job-1234&lt;/code&gt; (randomly generated)&lt;/li&gt;
  &lt;li&gt;Doing some stuff there&lt;/li&gt;
  &lt;li&gt;Deleting the work directory&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That middle step wasn’t working quite right so we wanted to inspect it before it vanished. For complicated reasons, it was tricky to pause the tool before step 3 so here’s what we did:&lt;/p&gt;

&lt;p&gt;Watch for the working folder and just clone it every 1 second:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# PowerShell script to backup directories with timestamp extensions&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Usage: &lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;#   cd C:\path\to\directory&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;#   .\monitor-folder.ps1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Use current working directory where the script is run from&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$TargetDirectory&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Get-Location&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Monitoring directory: &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$TargetDirectory&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ForegroundColor&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Green&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Backing up directories every 1 second...&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ForegroundColor&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Green&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Press Ctrl+C to stop...&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ForegroundColor&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Yellow&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;while&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$timestamp&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Get-Date&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Format&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;HH.mm.ss&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    
    &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Get all directories directly in the target directory (not recursive)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$directories&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Get-ChildItem&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Path&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$TargetDirectory&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Directory&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    
    &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;foreach&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$dir&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;in&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$directories&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Skip directories that are already backups&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$dir&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-like&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;*.bak&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        
        &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$backupName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$dir&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$timestamp&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.bak&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$backupPath&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Join-Path&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$TargetDirectory&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$backupName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        
        &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Copy directory recursively - continue even if some files are in use&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Copy-Item&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Path&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$dir&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FullName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Destination&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$backupPath&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Recurse&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Force&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ErrorAction&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Continue&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Get-Date&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Format&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;HH:mm:ss&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;] Backed up: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$dir&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; -&amp;gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$backupName&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ForegroundColor&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Cyan&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Get-Date&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Format&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;HH:mm:ss&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;] WARNING: Partial backup of &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$dir&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; (some files may be skipped)&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ForegroundColor&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Yellow&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    
    &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Start-Sleep&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Seconds&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With that running, the job came and went, and left behind what we needed:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;- job-1234-10.57.12.bak
- job-1234-10.57.13.bak
- job-1234-10.57.14.bak
- job-1234-10.57.15.bak
- job-1234-10.57.16.bak
- job-1234-10.57.17.bak
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There are lots of ways to solve this problem, but this one was good enough for what we needed and extremely simple to generate and execute.&lt;/p&gt;</content><author><name></name></author><category term="technology" /><category term="code" /><category term="windows" /><summary type="html">We recently had a situation where a tool was:</summary></entry><entry><title type="html">Drain Octopus Deploy workers for patching</title><link href="https://www.wassupy.com/2025/03/drain-octopus-workers-for-patching.html" rel="alternate" type="text/html" title="Drain Octopus Deploy workers for patching" /><published>2025-03-11T00:00:00+00:00</published><updated>2025-03-11T00:00:00+00:00</updated><id>https://www.wassupy.com/2025/03/drain-octopus-workers-for-patching</id><content type="html" xml:base="https://www.wassupy.com/2025/03/drain-octopus-workers-for-patching.html">&lt;p&gt;If you reboot a Linux Octopus worker while a deploy is running, that deploy will fail. It’s better to decluster it during routine patching/maintenance. The following Ansible tasks do that:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Patch octopus workers&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;workers&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;gather_facts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;vars&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;api_url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;https://octopusserver&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;api_key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;TODO&quot;&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Get worker status&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;api_url&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}/api/workers/{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;octopus_id&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;GET&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;X-Octopus-ApiKey&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;api_key&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;body_format&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;json&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;status_code&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;200&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;worker_status&lt;/span&gt;

    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Determine if worker requires draining&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;set_fact&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;is_draining_required&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;worker_status.json.IsDisabled&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&quot;&lt;/span&gt;

    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Is draining required?&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;Draining&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;required:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;is_draining_required&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&quot;&lt;/span&gt;

    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Disable worker&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;is_draining_required&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;api_url&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}/api/workers/{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;octopus_id&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;PUT&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;X-Octopus-ApiKey&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;api_key&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;worker_status.json&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;combine({&apos;IsDisabled&apos;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;true})&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;to_json&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;body_format&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;json&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;status_code&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;200&lt;/span&gt;

    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Wait for worker to drain&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;is_draining_required&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;block&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Check for active leases&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;api_url&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}/api/Spaces-1/workertaskleases?skip=0&amp;amp;take=1000&quot;&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;GET&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;X-Octopus-ApiKey&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;api_key&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&quot;&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;status_code&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;200&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;lease_status&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# if you have jmespath installed this can be improved&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;until&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;not&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;lease_status&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;is&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;search(octopus_id)&quot;&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;retries&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;60&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;15&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;# do patching&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Sleep to simulate patching&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;pause&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;seconds&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;5&lt;/span&gt;

    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Enable worker&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;is_draining_required&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;api_url&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}/api/workers/{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;octopus_id&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;PUT&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;X-Octopus-ApiKey&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;api_key&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;{{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;worker_status.json&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;combine({&apos;IsDisabled&apos;:&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;false})&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;to_json&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s&quot;&gt;}}&quot;&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;body_format&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;json&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;status_code&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;200&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Example inventory:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;workers&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;hosts&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;worker1a&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;octopus_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Workers-1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This attempts to leave the worker status in the same state it finds it, but that handling could be improved to better
handle failures.&lt;/p&gt;</content><author><name></name></author><category term="technology" /><category term="code" /><summary type="html">If you reboot a Linux Octopus worker while a deploy is running, that deploy will fail. It’s better to decluster it during routine patching/maintenance. The following Ansible tasks do that:</summary></entry><entry><title type="html">Download GEO IP Maxmind Database with Powershell or Bash on Windows/Mac/Linux</title><link href="https://www.wassupy.com/2025/03/download-geoip-maxmind-database.html" rel="alternate" type="text/html" title="Download GEO IP Maxmind Database with Powershell or Bash on Windows/Mac/Linux" /><published>2025-03-11T00:00:00+00:00</published><updated>2025-03-11T00:00:00+00:00</updated><id>https://www.wassupy.com/2025/03/download-geoip-maxmind-database</id><content type="html" xml:base="https://www.wassupy.com/2025/03/download-geoip-maxmind-database.html">&lt;p&gt;Here’s a tiny shell script for downloading a Maxmind database if you can use something scrappy:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;LICENSE_KEY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;YOUR_LICENSE_KEY&apos;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# GeoLite2-City, GeoLite2-Country, GeoIP2-City, GeoIP2-Country&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;DATABASE_TYPE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;GeoIP2-Country&apos;&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;MAXMIND_URL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://download.maxmind.com/app/geoip_download?edition_id=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$DATABASE_TYPE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;amp;license_key=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$LICENSE_KEY&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;amp;suffix=tar.gz&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;CURL_OPTIONS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;-Ls --fail&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;TAR_OPTIONS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;-Ozx --no-anchored &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$DATABASE_TYPE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.mmdb&quot;&lt;/span&gt;

curl &lt;span class=&quot;nv&quot;&gt;$CURL_OPTIONS&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$MAXMIND_URL&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;tar&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$TAR_OPTIONS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$DATABASE_TYPE&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.mmdb&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here’s a fuller version with more bells and whistles:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;#!/bin/bash&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# exit on error&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-eo&lt;/span&gt; pipefail

&lt;span class=&quot;c&quot;&gt;# Usage message&lt;/span&gt;
usage&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Usage: &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$0&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; &amp;lt;license_key&amp;gt; &amp;lt;database_type&amp;gt; [output]&quot;&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;  license_key: Your MaxMind license key&quot;&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;  database_type: One of: GeoLite2-City, GeoLite2-Country, GeoIP2-City, GeoIP2-Country&quot;&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;  output: Optional. Path to save the downloaded database file&quot;&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;exit &lt;/span&gt;1
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Check for license_key argument&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-z&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
  &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&amp;amp;2 &lt;span class=&quot;s2&quot;&gt;&quot;Error: MaxMind license key is required&quot;&lt;/span&gt;
  usage
&lt;span class=&quot;k&quot;&gt;fi

&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;license_key&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;database_type&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$2&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$3&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Validate database type if provided&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-n&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$database_type&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
  case&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$database_type&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;GeoLite2-City&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;GeoLite2-Country&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;GeoIP2-City&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;GeoIP2-Country&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;c&quot;&gt;# Valid option&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&amp;amp;2 &lt;span class=&quot;s2&quot;&gt;&quot;Error: Invalid database type &apos;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$database_type&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&apos;&quot;&lt;/span&gt;
      usage
      &lt;span class=&quot;p&quot;&gt;;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;esac&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Set default output if not provided&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-z&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
  &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$database_type&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.mmdb&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;fi&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# check requirements&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;type &lt;/span&gt;curl &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /dev/null 2&amp;gt;&amp;amp;1&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;then
  &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&amp;amp;2 &lt;span class=&quot;s2&quot;&gt;&quot;curl is required but was not found&quot;&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;exit &lt;/span&gt;1
&lt;span class=&quot;k&quot;&gt;fi

&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Downloading &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$database_type&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; database...&quot;&lt;/span&gt;

&lt;span class=&quot;nv&quot;&gt;MAXMIND_URL&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://download.maxmind.com/app/geoip_download?edition_id=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$database_type&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;amp;license_key=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$license_key&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;amp;suffix=tar.gz&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;CURL_OPTIONS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;-Ls --fail&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;TAR_OPTIONS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;-Ozx --no-anchored &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$database_type&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.mmdb&quot;&lt;/span&gt;

curl &lt;span class=&quot;nv&quot;&gt;$CURL_OPTIONS&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$MAXMIND_URL&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;tar&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$TAR_OPTIONS&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Saved to &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$output&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you save that to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update-db.sh&lt;/code&gt; (and run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chmod +x update-db.sh&lt;/code&gt;), then you can invoke it like this:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./update-db.sh &lt;span class=&quot;s1&quot;&gt;&apos;your-license-key&apos;&lt;/span&gt; GeoIP2-Country bash-country.mmdb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;p&gt;And here’s a Powershell version, which works on Server 2019+ or Windows 10+ (earlier versions of Windows require you to install
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tar&lt;/code&gt; yourself):&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kr&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Get-MaxMindDatabase&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;&amp;lt;#
    &lt;/span&gt;&lt;span class=&quot;cs&quot;&gt;.SYNOPSIS&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;
        Downloads a MaxMind GeoIP database, extracts the .mmdb file, and saves it to the specified location.
    
    &lt;/span&gt;&lt;span class=&quot;cs&quot;&gt;.DESCRIPTION&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;
        This function downloads a MaxMind database using a license key, extracts the .mmdb file
        and saves it to the specified output file.
    
    &lt;/span&gt;&lt;span class=&quot;cs&quot;&gt;.PARAMETER&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt; LicenseKey
        Your MaxMind license key.
    
    &lt;/span&gt;&lt;span class=&quot;cs&quot;&gt;.PARAMETER&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt; DatabaseType
        The type of database to download. Must be one of: GeoLite2-City, GeoLite2-Country, GeoIP2-City, GeoIP2-Country.
    
    &lt;/span&gt;&lt;span class=&quot;cs&quot;&gt;.PARAMETER&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt; OutputFile
        The path where the extracted .mmdb file should be saved.
    
    &lt;/span&gt;&lt;span class=&quot;cs&quot;&gt;.EXAMPLE&lt;/span&gt;&lt;span class=&quot;cm&quot;&gt;
        Get-MaxMindDatabase -LicenseKey &quot;your_license_key&quot; -DatabaseType &quot;GeoLite2-City&quot; -OutputFile &quot;C:\GeoIP\GeoLite2-City.mmdb&quot;
    #&amp;gt;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CmdletBinding&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;param&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Parameter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mandatory&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$LicenseKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Parameter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mandatory&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ValidateSet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;GeoLite2-City&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;GeoLite2-Country&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;GeoIP2-City&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;GeoIP2-Country&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$DatabaseType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Parameter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Mandatory&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$OutputFile&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    
    &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Create temporary files&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$tempArchiveFile&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;System.IO.Path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Combine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;System.IO.Path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GetTempPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;System.Guid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewGuid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.ToString()).tar.gz&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$tempDirectory&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;System.IO.Path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Combine&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;System.IO.Path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GetTempPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;System.Guid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewGuid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ToString&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Create the download URL&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$downloadUrl&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;https://download.maxmind.com/app/geoip_download?edition_id=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$DatabaseType&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;amp;license_key=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$LicenseKey&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&amp;amp;suffix=tar.gz&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        
        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Downloading from &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$downloadUrl&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; to &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$tempArchiveFile&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Download the file&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$webClient&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;New-Object&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;System.Net.WebClient&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$webClient&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;DownloadFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$downloadUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$tempArchiveFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Create a temporary directory for extraction&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;New-Item&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Path&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$tempDirectory&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-ItemType&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Directory&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Force&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Out-Null&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Extracting tar.gz file to &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$tempDirectory&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tar&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-xzf&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$tempArchiveFile&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-C&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$tempDirectory&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Check if the extraction was successful&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$LASTEXITCODE&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-ne&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;throw&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Failed to extract the downloaded archive.&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Find the .mmdb file&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$mmdbFile&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Get-ChildItem&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Path&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$tempDirectory&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Recurse&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Filter&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;*.mmdb&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Select-Object&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-First&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$null&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-eq&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$mmdbFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;throw&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;No .mmdb file found in the downloaded archive.&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        
        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Found .mmdb file: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$mmdbFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FullName&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        
        &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$outputDirectory&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;System.IO.Path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GetDirectoryName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$OutputFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-not&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$outputDirectory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-and&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-not&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Test-Path&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Path&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$outputDirectory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;throw&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Output directory &apos;&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$outputDirectory&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&apos; not found.&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Copy the file to the output location&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Copy-Item&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Path&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$mmdbFile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;FullName&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Destination&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$OutputFile&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Force&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        
        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Host&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Database saved to &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$OutputFile&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Return the path to the output file&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$OutputFile&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Write-Error&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Failed to download and extract MaxMind database: &lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;$_&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;throw&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;finally&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# Clean up temporary files&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Test-Path&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Path&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$tempArchiveFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Remove-Item&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Path&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$tempArchiveFile&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Force&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        
        &lt;/span&gt;&lt;span class=&quot;kr&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Test-Path&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Path&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$tempDirectory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Remove-Item&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Path&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$tempDirectory&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Recurse&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-Force&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can dot-include it, and then run it, like this:&lt;/p&gt;

&lt;div class=&quot;language-powershell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# include the function&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;/Get-MaxmindDatabase.ps1&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$licenseKey&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;your-license-key&apos;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;c&quot;&gt;# download database&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Get-MaxmindDatabase&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-LicenseKey&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$licenseKey&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-DatabaseType&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;GeoIP2-Country&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;-OutputFile&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;/country.mddb&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; these &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mddb&lt;/code&gt; endpoints only support the suffix &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.tar.gz&lt;/code&gt;. There are also &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;csv&lt;/code&gt; endpoints that support &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zip&lt;/code&gt;, but you can’t uze &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zip&lt;/code&gt; here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; at the time of this writing, those databases are approximately this big:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GeoIP2-City.mmdb&lt;/code&gt;: 117 MB&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GeoLite2-City.mmdb&lt;/code&gt;: 58 MB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GeoIP2-Country.mmdb&lt;/code&gt;: 8.6 MB&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GeoLite2-Country.mmdb&lt;/code&gt;: 8.7 MB (yes, bigger than the non-lite database!)&lt;/li&gt;
&lt;/ul&gt;</content><author><name></name></author><category term="technology" /><category term="code" /><summary type="html">Here’s a tiny shell script for downloading a Maxmind database if you can use something scrappy:</summary></entry><entry><title type="html">Command line server TLS certificate inspector</title><link href="https://www.wassupy.com/2025/03/bash-cert-inspector.html" rel="alternate" type="text/html" title="Command line server TLS certificate inspector" /><published>2025-03-05T00:00:00+00:00</published><updated>2025-03-05T00:00:00+00:00</updated><id>https://www.wassupy.com/2025/03/bash-cert-inspector</id><content type="html" xml:base="https://www.wassupy.com/2025/03/bash-cert-inspector.html">&lt;p&gt;I can never remember this command to inspect a remote server’s certificate from bash/shell/terminal:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;openssl s_client &lt;span class=&quot;nt&quot;&gt;-connect&lt;/span&gt; example.com:443 &amp;lt; /dev/null | openssl x509 &lt;span class=&quot;nt&quot;&gt;-noout&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-text&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So I wrapped it in a function and stuck it into my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.bash_profile&lt;/code&gt; like this:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cert &lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; 
&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; 
    openssl s_client &lt;span class=&quot;nt&quot;&gt;-connect&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$1&lt;/span&gt;:443 &amp;lt; /dev/null | openssl x509 &lt;span class=&quot;nt&quot;&gt;-noout&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-text&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And now I just have to remember &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cert [hostname]&lt;/code&gt;, like this:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ cert github.com
Connecting to 140.82.112.3
...
        Issuer: C=GB, ST=Greater Manchester, L=Salford, O=Sectigo Limited, CN=Sectigo ECC Domain Validation Secure Server CA
        Validity
            Not Before: Feb  5 00:00:00 2025 GMT
            Not After : Feb  5 23:59:59 2026 GMT
        Subject: CN=github.com
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And if you’re looking for something in particular, you can just &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;grep&lt;/code&gt; for it:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;cert github.com | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;github
        Subject: &lt;span class=&quot;nv&quot;&gt;CN&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;github.com
                DNS:github.com, DNS:www.github.com
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Very handy.&lt;/p&gt;</content><author><name></name></author><category term="technology" /><category term="code" /><summary type="html">I can never remember this command to inspect a remote server’s certificate from bash/shell/terminal:</summary></entry><entry><title type="html">Automatic dog food status light</title><link href="https://www.wassupy.com/2025/01/dog-food-status-light.html" rel="alternate" type="text/html" title="Automatic dog food status light" /><published>2025-01-04T00:00:00+00:00</published><updated>2025-01-04T00:00:00+00:00</updated><id>https://www.wassupy.com/2025/01/dog-food-status-light</id><content type="html" xml:base="https://www.wassupy.com/2025/01/dog-food-status-light.html">&lt;h3 id=&quot;the-goal-feed-the-dogs-2xday&quot;&gt;The goal: feed the dogs 2x/day&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2025/puppies-bed.jpeg&quot; width=&quot;1280&quot; height=&quot;721&quot; alt=&quot;two dogs on a green dog bed. One is a beagle puppy looking at the other one, who is a black puggle mix&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Ideally my two pups would be fed roughly the right amount of food at roughly the right time, twice a day.&lt;/p&gt;

&lt;p&gt;And we’d all be sure that it happened.&lt;/p&gt;

&lt;h3 id=&quot;the-problem-did-someone-feed-the-dogs&quot;&gt;The problem: did someone feed the dogs?&lt;/h3&gt;

&lt;p&gt;We faced uncertainty nearly every day. With humans coming and going to various things, it was often difficult to
know and this surely led to under- and over-feeding.&lt;/p&gt;

&lt;p class=&quot;pq&quot;&gt;This is a classic systems/process problem (with perhaps a dash of volunteer’s dilemma): the way we managed dog feeding
was designed to ellicit bad outcomes, and eliminating those bad outcomes (i.e. under/over feeding) requires a change to
the system. “Trying harder” won’t cut it. (More on this &lt;a href=&quot;#an-aside-on-systemic-problems&quot;&gt;at the end&lt;/a&gt;.)&lt;/p&gt;

&lt;h3 id=&quot;some-ideas&quot;&gt;Some ideas&lt;/h3&gt;

&lt;p&gt;As a family we talked about possible solutions to this problem:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Designate one caretaker as the person responsible for making sure the dogs get fed each day&lt;/li&gt;
  &lt;li&gt;Make a note when they’re fed&lt;/li&gt;
  &lt;li&gt;Pre-measure their food for the week, and pour it out of the day’s marked container each day&lt;/li&gt;
  &lt;li&gt;Change nothing (the dogs will tell us when we forget!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We tried all of these things and they all helped a little. But they all had significant shortcomings:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Each person is busy and has a relatively chaotic schedule&lt;/li&gt;
  &lt;li&gt;People forget, and it’s hard to fill in the gaps without knowing if the gap exists or not&lt;/li&gt;
  &lt;li&gt;Keeping notes and/or pre-measuring is extra work and isn’t necessarily reliable&lt;/li&gt;
  &lt;li&gt;My pups are unreliable witnesses&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;a-real-solution&quot;&gt;A real solution&lt;/h3&gt;

&lt;p&gt;I knew about &lt;a href=&quot;https://www.timercap.com&quot; title=&quot;Timer Cap medicine bottles&quot;&gt;medicine bottles&lt;/a&gt; that show time-since-last-open and thought something like that would be wonderful.
What we have finally arrived at is a solution that completely solves our problem with the following results:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;We have a status light that anyone can read at a glance: it turns red when the dogs need fed, and turns green when
they’ve recently beed fed&lt;/li&gt;
  &lt;li&gt;The status light updates automatically with no extra work (very important!)&lt;/li&gt;
&lt;/ul&gt;

&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; width=&quot;1280&quot; height=&quot;720&quot; aria-label=&quot;A looping animation that shows a web page loading a grid of 24 contact cards. The contact cards quickly fade into view at slightly different times, over a period of about 1 second.&quot;&gt;
  &lt;source src=&quot;/assets/2025/dog-food-light.mp4&quot; type=&quot;video/mp4&quot; /&gt;
  &lt;source src=&quot;/assets/2025/dog-food-light.webm&quot; type=&quot;video/webm&quot; /&gt;
  &lt;source src=&quot;/assets/2025/dog-food-light.ogg&quot; type=&quot;video/ogg&quot; /&gt;
  &lt;img src=&quot;/assets/2025/dog-food-light.gif&quot; loading=&quot;lazy&quot; /&gt;
&lt;/video&gt;

&lt;p&gt;It’s basically an &lt;a href=&quot;https://en.wikipedia.org/wiki/Andon_(manufacturing)&quot; title=&quot;Andon (manufacturing)&quot;&gt;andon light&lt;/a&gt;/&lt;a href=&quot;https://en.wikipedia.org/wiki/Stack_light&quot; title=&quot;Stack light&quot;&gt;stack light&lt;/a&gt;, powered by Home Assistant and two components: an open/close sensor
on the dog food container and a smart RGB night light.&lt;/p&gt;

&lt;h3 id=&quot;build-details&quot;&gt;Build details&lt;/h3&gt;

&lt;p&gt;I used an &lt;a href=&quot;https://www.aqara.com/us/product/door-and-window-sensor/&quot; title=&quot;Aqara window/door sensor&quot;&gt;Aqara door/window sensor&lt;/a&gt; ($15) attached to the lid of my dog food container, and an &lt;a href=&quot;https://3reality.com/product/matter-smart-color-night-light/&quot; title=&quot;Smart Color Night Light&quot;&gt;RGB smart night
light&lt;/a&gt; ($30). Then I set up an automation in Home Assistant to do the following:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Turn the light on, set to RED &lt;span class=&quot;nodark&quot;&gt;🔴&lt;/span&gt;, at breakfast and dinner time&lt;/li&gt;
  &lt;li&gt;Set the light GREEN &lt;span class=&quot;nodark&quot;&gt;🟢&lt;/span&gt; when a feeding occurs&lt;/li&gt;
  &lt;li&gt;Turn the light OFF &lt;span class=&quot;nodark&quot;&gt;⚪️&lt;/span&gt; two hours after a feeding&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here’s the heavily annotated &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yaml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Set dog food status&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;trigger&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# trigger in the morning and evening so we can turn&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# the light ON and RED for breakfast and dinner&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Morning&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;time&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;at&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;07:00:00&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;morning&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Evening&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;time&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;at&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;18:00:00&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;evening&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# trigger when the dog food container changes to&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# &quot;closed&quot; so we set the light to GREEN&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;platform&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;state&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;entity_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;binary_sensor.open_close_xs_sensor_window_door_is_open_3&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;off&quot;&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Fed&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;fed&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;action&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# I found that changing the light was more reliable if&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# I always turned it off first&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;light.turn_off&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;entity_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;light.dog_food_status_light_light&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Light off&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# if trigger=fed, i.e. food container was just closed,&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# set the light to GREEN...&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;condition&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;trigger&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;fed&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Turn light green&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;light.turn_on&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;rgb_color&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;
                    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;249&lt;/span&gt;
                    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;brightness_pct&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;25&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;entity_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;light.dog_food_status_light_light&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# ...and wait 2 hours...&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;delay&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;hours&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;minutes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;seconds&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;milliseconds&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# ...then turn it off. This way humans can tell long&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# after feeding time that everything is cool&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;light.turn_off&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;entity_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;light.dog_food_status_light_light&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# else, the trigger is the breakfast or dinner timer&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# so just turn it red&lt;/span&gt;
          &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;alias&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Turn light red&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;light.turn_on&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;metadata&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;{}&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;rgb_color&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;255&lt;/span&gt;
                    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;38&lt;/span&gt;
                    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;brightness_step_pct&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;100&lt;/span&gt;
            &lt;span class=&quot;na&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;na&quot;&gt;entity_id&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;light.dog_food_status_light_light&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;single&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I cannot express just how helpful this has been &lt;span class=&quot;nodark&quot;&gt;😊&lt;/span&gt;. And since it’s all in Home Assistant, it
shows up on my dashboard. Here you can see that they were last fed an hour ago, and keeps history, though that’s not
really necessary:&lt;/p&gt;

&lt;div class=&quot;--tile-300&quot;&gt;
    &lt;picture&gt;
        &lt;source srcset=&quot;/assets/2025/dog-food-dashboard.png&quot; media=&quot;(prefers-color-scheme: dark)&quot; /&gt;
        &lt;img class=&quot;border&quot; width=&quot;888&quot; height=&quot;764&quot; src=&quot;/assets/2025/dog-food-dashboard-light.png&quot; alt=&quot;A dashboard titled &amp;quot;Kitchen&amp;quot; that shows light switches, humidity, temperature, and &amp;quot;Dog Food Container&amp;quot;, last closed 1 hour ago&quot; /&gt;
    &lt;/picture&gt;
    &lt;picture&gt;
        &lt;source srcset=&quot;/assets/2025/dog-food-log.png&quot; media=&quot;(prefers-color-scheme: dark)&quot; /&gt;
        &lt;img class=&quot;border&quot; width=&quot;1124&quot; height=&quot;1038&quot; src=&quot;/assets/2025/dog-food-log-light.png&quot; alt=&quot;A log titled &amp;quot;Dog Food Container&amp;quot; that shows it was last closed 1 hour ago, with earlier entries showing opens and closes&quot; /&gt;
    &lt;/picture&gt;
&lt;/div&gt;

&lt;p&gt;Happy humans and happy pups.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/2025/puppies-jammies.jpeg&quot; width=&quot;1280&quot; height=&quot;720&quot; alt=&quot;The same two dogs from the earlier photo, but older. They are wearing pajamas with cartoon zoo animals on them. They are standing alert on a tile floor and the beagle has one ear turned back&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;an-aside-on-systemic-problems&quot;&gt;An aside on systemic problems&lt;/h3&gt;

&lt;p&gt;My most important professional skill is identifying and solving systemic problems. As W. Edwards Deming explains in &lt;em&gt;Out
of the Crisis&lt;/em&gt;, issues that occur &lt;em&gt;within&lt;/em&gt; a system need to be addressed by &lt;em&gt;changing the system&lt;/em&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Eighty-five percent of the reasons for failure are &lt;strong&gt;deficiencies in the systems and process&lt;/strong&gt; rather than the
employee. The role of management is to change the process rather than badgering individuals to do better&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And L. David Marquet writes in &lt;em&gt;Leadership Is Language&lt;/em&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;We need to always remember that the organization is perfectly tuned to deliver the behavior we see, and &lt;strong&gt;people’s
behaviors are the perfect result of the organization’s design&lt;/strong&gt;. As individuals, we should embrace our responsibility
for being the best we can be within the design of the organization. But as leaders, &lt;strong&gt;our responsibility is to design
the organization so that individuals can be the best versions of themselves.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;See also: &lt;a href=&quot;https://heathbrothers.com/books/upstream/&quot; title=&quot;Upstream book&quot;&gt;&lt;strong&gt;Upstream&lt;/strong&gt;&lt;/a&gt; by Dan Heath.&lt;/p&gt;

&lt;p&gt;Sometimes (much to my family’s chagrin), I apply these lessons at home too.&lt;/p&gt;</content><author><name></name></author><category term="technology" /><category term="code" /><category term="homelab" /><category term="family" /><summary type="html">The goal: feed the dogs 2x/day</summary></entry><entry><title type="html">The Adobe Lightroom backup story is terrible</title><link href="https://www.wassupy.com/2025/01/the-adobe-lightroom-backup-story-is-terrible.html" rel="alternate" type="text/html" title="The Adobe Lightroom backup story is terrible" /><published>2025-01-03T00:00:00+00:00</published><updated>2025-01-03T00:00:00+00:00</updated><id>https://www.wassupy.com/2025/01/the-adobe-lightroom-backup-story-is-terrible</id><content type="html" xml:base="https://www.wassupy.com/2025/01/the-adobe-lightroom-backup-story-is-terrible.html">&lt;p&gt;Backing up an Adobe Lightroom catalog is a bit of a pain. If you have local storage large enough to hold all your photos
then you can tell Lightroom to save a copy of every photo there, and then you can backup that location. That’s better
than nothing. But when you don’t have enough space (largely thanks to Apple’s exorbinate storage upgrade prices),
backing up everything, including what gets offloaded to Adobe’s cloud is difficult. As far as I can tell, there’s no
straightforward way to do this, especially not repeatedly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To be clear:&lt;/strong&gt; the cloud storage Adobe uses for Lightroom is exceptionally convenient, but it is not a backup. I want
a backup that will survive account compromise, loss of access, or a major technical issue with the provider.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;It’d be nice&lt;/em&gt; if I could restore the backup without much disruption, but I’ll settle for an inconvenient, incomplete copy of
everything.&lt;/p&gt;

&lt;p&gt;Here’s what I settled on:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Get a large external USB hard drive ($99)&lt;/li&gt;
  &lt;li&gt;Use the Adobe Lightroom Downloader to download all the photos to it (25 hours)&lt;/li&gt;
  &lt;li&gt;Repeat periodically&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;--tile-300&quot;&gt;
    &lt;picture&gt;
        &lt;source srcset=&quot;/assets/2025/lightroom-downloader-dark.png&quot; media=&quot;(prefers-color-scheme: dark)&quot; /&gt;
        &lt;img height=&quot;1360&quot; width=&quot;960&quot; src=&quot;/assets/2025/lightroom-downloader-light.png&quot; alt=&quot;a screenshot of an app titled &amp;quot;Lightroom Downloader&amp;quot;, showing image files being downloaded and a progress bar at 17%. There is a button at the bottom labeled &amp;quot;Pause Download&amp;quot;&quot; /&gt;
    &lt;/picture&gt;
&lt;/div&gt;

&lt;p&gt;I’m disappointed that this doesn’t give me the high-fidelity backup I want, and that I have to manually do it every
month or two, and that it’s wildly inefficient to re-download all the photos every time, but it’s all I have.&lt;/p&gt;

&lt;h3 id=&quot;things-i-wish-i-could-do-instead&quot;&gt;Things I wish I could do instead&lt;/h3&gt;

&lt;p&gt;I wish I could use a web service to automatically backup photos to a 3rd party, e.g. Google Photos, S3, B2, etc.&lt;/p&gt;

&lt;p&gt;I wish I could run a script or service on my home Linux server to download just the changes periodically and
automatically.&lt;/p&gt;

&lt;p&gt;I wish the Lightroom Downloader had a “sync” feature so it didn’t have to download all 125k+ photos every time.&lt;/p&gt;

&lt;p&gt;I wish the backup options didn’t lose so much data along the way, e.g. album strcuture, and I wish backups could be
easily restored.&lt;/p&gt;</content><author><name></name></author><category term="technology" /><category term="photography" /><summary type="html">Backing up an Adobe Lightroom catalog is a bit of a pain. If you have local storage large enough to hold all your photos then you can tell Lightroom to save a copy of every photo there, and then you can backup that location. That’s better than nothing. But when you don’t have enough space (largely thanks to Apple’s exorbinate storage upgrade prices), backing up everything, including what gets offloaded to Adobe’s cloud is difficult. As far as I can tell, there’s no straightforward way to do this, especially not repeatedly.</summary></entry><entry><title type="html">Expose self-hosted Home Assistant to the internet (and companion app) with Cloudflare Tunnels and Docker Compose</title><link href="https://www.wassupy.com/2024/12/self-hosting-home-assistant-with-cloudflare-tunnels.html" rel="alternate" type="text/html" title="Expose self-hosted Home Assistant to the internet (and companion app) with Cloudflare Tunnels and Docker Compose" /><published>2024-12-29T00:00:00+00:00</published><updated>2024-12-29T00:00:00+00:00</updated><id>https://www.wassupy.com/2024/12/self-hosting-home-assistant-with-cloudflare-tunnels</id><content type="html" xml:base="https://www.wassupy.com/2024/12/self-hosting-home-assistant-with-cloudflare-tunnels.html">&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;

&lt;p&gt;This guide assumes you have the following:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;A Cloudflare account (free)&lt;/li&gt;
  &lt;li&gt;A domain name (bought from Cloudflare or elsewhere; $10/yr), set up in Cloudflare&lt;/li&gt;
  &lt;li&gt;A linux server with docker, which can be the same one where you run Home Assistant, but doesn’t need to be&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;the-general-idea&quot;&gt;The general idea&lt;/h2&gt;

&lt;p&gt;Web users connect to Cloudflare, and instead of Cloudflare reaching into our network, we run a service inside the
network that &lt;em&gt;reaches out to Cloudflare&lt;/em&gt;:&lt;/p&gt;

&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 874 240&quot; font-size=&quot;20&quot; stroke-width=&quot;4&quot; stroke=&quot;black&quot; fill=&quot;none&quot; role=&quot;img&quot;&gt;
    &lt;title&gt;CF Tunnels&lt;/title&gt;
    &lt;desc&gt;Diagram of a client connecting to a Home Assistant instance inside a homelab via Cloudflare Tunnels. The diagram has three entities: client,
    Cloudflare, home lab. The client is connected to Cloudflare. The home lab has two docker containers. The first is
    labled &quot;CF Tunnel&quot; and it is connected to the Cloudflare entity, and the other container: Home Assistant&lt;/desc&gt;
    &lt;defs&gt;
        &lt;marker id=&quot;head&quot; orient=&quot;auto&quot; markerWidth=&quot;4&quot; markerHeight=&quot;4&quot; refX=&quot;0.1&quot; refY=&quot;2&quot;&gt;
            &lt;path d=&quot;M0,0 V4 L2,2 Z&quot; stroke-width=&quot;1&quot; fill=&quot;black&quot; /&gt;
        &lt;/marker&gt;
    &lt;/defs&gt;

    &lt;g id=&quot;actor&quot; fill=&quot;none&quot;&gt;
        &lt;circle cx=&quot;50&quot; cy=&quot;50&quot; r=&quot;40&quot; /&gt;
        &lt;line x1=&quot;50&quot; y1=&quot;90&quot; x2=&quot;50&quot; y2=&quot;150&quot; /&gt;
        &lt;line x1=&quot;50&quot; y1=&quot;150&quot; x2=&quot;25&quot; y2=&quot;190&quot; /&gt;
        &lt;line x1=&quot;50&quot; y1=&quot;150&quot; x2=&quot;75&quot; y2=&quot;190&quot; /&gt;
        &lt;line x1=&quot;25&quot; y1=&quot;100&quot; x2=&quot;75&quot; y2=&quot;100&quot; /&gt;
    &lt;/g&gt;
    &lt;path id=&quot;cloudflare&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; d=&quot;M 172.498 118.768 C 172.498 175.125 220.749 189.214 244.868 189.214 L 376.449 189.214 C 396.186 189.214 435.661 177.687 435.661 131.576 C 435.661 85.467 396.186 73.94 376.449 73.94 C 376.449 54.727 356.712 9.898 310.658 9.898 C 273.816 9.898 251.447 35.515 244.868 48.323 C 220.749 48.323 172.498 62.413 172.498 118.768 Z&quot; style=&quot;fill: none;&quot; /&gt;

    &lt;g id=&quot;homelab&quot;&gt;
        &lt;rect x=&quot;500&quot; y=&quot;10&quot; width=&quot;360&quot; height=&quot;180&quot; /&gt;
        &lt;rect x=&quot;520&quot; y=&quot;80&quot; width=&quot;110&quot; height=&quot;40&quot; /&gt;
        &lt;rect x=&quot;685&quot; y=&quot;80&quot; width=&quot;155&quot; height=&quot;40&quot; /&gt;
    &lt;/g&gt;

    &lt;g id=&quot;connections&quot;&gt;
        &lt;path marker-end=&quot;url(#head)&quot; d=&quot;M90,100 155,100&quot; /&gt;
        &lt;path marker-end=&quot;url(#head)&quot; d=&quot;M515,100 450,100&quot; /&gt;
        &lt;path marker-end=&quot;url(#head)&quot; d=&quot;M635,100 670,100 &quot; /&gt;
    &lt;/g&gt;

    &lt;g id=&quot;labels&quot; stroke-width=&quot;0&quot; fill=&quot;black&quot;&gt;
        &lt;text x=&quot;50&quot; y=&quot;220&quot; dominant-baseline=&quot;middle&quot; text-anchor=&quot;middle&quot;&gt;Client&lt;/text&gt;
        &lt;text x=&quot;304&quot; y=&quot;220&quot; dominant-baseline=&quot;middle&quot; text-anchor=&quot;middle&quot;&gt;Cloudflare&lt;/text&gt;
        &lt;text x=&quot;681.5&quot; y=&quot;220&quot; dominant-baseline=&quot;middle&quot; text-anchor=&quot;middle&quot;&gt;Homelab Containers&lt;/text&gt;
        &lt;text x=&quot;530&quot; y=&quot;108&quot;&gt;CF Tunnel&lt;/text&gt;
        &lt;text x=&quot;695&quot; y=&quot;108&quot;&gt;Home Assistant&lt;/text&gt;
    &lt;/g&gt;

&lt;/svg&gt;

&lt;p&gt;We are &lt;strong&gt;not&lt;/strong&gt; opening any ports, e.g. http/https from our server to the local network or internet. We are &lt;strong&gt;not&lt;/strong&gt;
messing with dynamic dns stuff. We don’t need to set up TLS.&lt;/p&gt;

&lt;h2 id=&quot;an-example-with-docker-compose&quot;&gt;An example with Docker Compose&lt;/h2&gt;

&lt;h3 id=&quot;create-the-tunnel-config-at-cloudflare&quot;&gt;Create the tunnel config at Cloudflare&lt;/h3&gt;

&lt;p&gt;First create your tunnel in the Cloudflare dashboard &amp;gt; Zero Trust &amp;gt; Networks &amp;gt; Tunnels:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Choose &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cloudflared&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Name it whatever you want, e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;homelab&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Click the “docker” installation instructions just to &lt;em&gt;grab the token&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Don’t actually install anything&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Copy that token for the next section.&lt;/p&gt;

&lt;h3 id=&quot;on-your-homelab-server&quot;&gt;On your homelab server&lt;/h3&gt;

&lt;p&gt;Create &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose.yaml&lt;/code&gt; like this, with the token you copied above:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;3.3&quot;&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;tunnel&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cloudflare/cloudflared&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;restart&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;unless-stopped&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;tunnel run&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;TUNNEL_TOKEN=***insert CF Tunnel token here***&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Start up the containers:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker compose up &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Tail the logs:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker compose logs &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;back-in-the-cloudflare-dashboard&quot;&gt;Back in the Cloudflare dashboard&lt;/h3&gt;

&lt;p&gt;At this point, the Cloudflare dashboard should show your tunnel as &lt;em&gt;healthy&lt;/em&gt;. If not, read those logs carefully for
errors and fix it before continuing.&lt;/p&gt;

&lt;p&gt;Add a public hostname to your tunnel:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;subdomain:&lt;/strong&gt; anything you want (this DNS record will be added for you)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;domain:&lt;/strong&gt; pick the domain you already set up in Cloudflare&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;service:&lt;/strong&gt; choose &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http&lt;/code&gt;, and set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;url&lt;/code&gt; to the internal IP and port of your Home Assistant inac name of your docker service. E.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://192.168.1.100:8123&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;additional application settings:&lt;/strong&gt; none&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With that done, you should be able to browse to the subdomain you set up, and the response will be tunneled over from
your homelab server. You’ll probably get a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;400&lt;/code&gt; error. Don’t fret—you’re almost done.&lt;/p&gt;

&lt;p&gt;Now you need to tell Home Assistant that the Cloudflare tunnel is allowed to proxy it. You can see the exact IP you need
in the Home Assistant error log, e.g.:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“A request from a reverse proxy was received from 172.21.0.8, but your HTTP integration is not set-up for reverse proxies”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That exact IP could change when your cftunnel container restarts, but the first two parts of the network probably won’t,
so you can just trust the whole thing like this, in HA &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;configuration.yaml&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;http&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;use_x_forwarded_for&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;trusted_proxies&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;172.21.0.0/16&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# add the actual IP of your cftunnels container, or the network it&apos;s on&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(And restart your HA instance.)&lt;/p&gt;

&lt;p&gt;You can also find the anticipated proxy IP by checking the network details, too. First, list your docker networks:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker network ls

NETWORK ID     NAME                     DRIVER    SCOPE
37c19c0df841   bridge                   bridge    local
da9f58733dc9   home-assistant_default   bridge    local
587ff440738c   host                     host      local
30a44b8290f3   none                     null      local
0e1df72bc30c   sites_default            bridge    local
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then pick the one &lt;em&gt;where you’re running your cftunnels container&lt;/em&gt; (&lt;strong&gt;not&lt;/strong&gt; the one running your HA instance!). In my case, it’s in a folder called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sites&lt;/code&gt;, so I need the last one. Inspect it:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;docker&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;network&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;inspect&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;sites_default&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; 

&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sites_default&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;IPAM&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Config&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Subnet&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;172.21.0.0/16&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;//&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;&amp;lt;--&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;is&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;what&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;you&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;want&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Gateway&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;172.21.0.1&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Containers&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;6626c238...&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sites-tunnel-1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
                &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;IPv4Address&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;172.21.0.8/16&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
            &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;do-i-need-to-do-tls-http-compression-etc&quot;&gt;Do I need to do TLS, HTTP compression, etc.?&lt;/h2&gt;

&lt;p&gt;No. Cloudflare handles TLS and HTTP compression for you. The tunnel is encrypted so no additional TLS is necessary for
that leg.&lt;/p&gt;

&lt;h2 id=&quot;im-still-getting-a-400-error-or-something-else-isnt-working&quot;&gt;I’m still getting a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;400&lt;/code&gt; error, or something else isn’t working&lt;/h2&gt;

&lt;p&gt;Check the logs!&lt;/p&gt;</content><author><name></name></author><category term="technology" /><category term="code" /><category term="homelab" /><summary type="html">Prerequisites</summary></entry><entry><title type="html">A dash of whimsy with a tiny loading animation</title><link href="https://www.wassupy.com/2024/12/a-dash-of-whimsy.html" rel="alternate" type="text/html" title="A dash of whimsy with a tiny loading animation" /><published>2024-12-19T00:00:00+00:00</published><updated>2024-12-19T00:00:00+00:00</updated><id>https://www.wassupy.com/2024/12/a-dash-of-whimsy</id><content type="html" xml:base="https://www.wassupy.com/2024/12/a-dash-of-whimsy.html">&lt;p&gt;It’s neat how a teensy little animation can add a little whimsy to an otherwise &lt;a href=&quot;https://vcfprinter.wassupy.com/&quot;&gt;boring thing&lt;/a&gt; like loading a bunch of contact cards:&lt;/p&gt;

&lt;video autoplay=&quot;&quot; loop=&quot;&quot; muted=&quot;&quot; playsinline=&quot;&quot; width=&quot;1866&quot; height=&quot;1510&quot; aria-label=&quot;A looping animation that shows a web page loading a grid of 24 contact cards. The contact cards quickly fade into view at slightly different times, over a period of about 1 second.&quot;&gt;
  &lt;source src=&quot;/assets/2024/vcf-whimsy.mp4&quot; type=&quot;video/mp4&quot; /&gt;
  &lt;source src=&quot;/assets/2024/vcf-whimsy.webm&quot; type=&quot;video/webm&quot; /&gt;
  &lt;source src=&quot;/assets/2024/vcf-whimsy.ogg&quot; type=&quot;video/ogg&quot; /&gt;
  &lt;img src=&quot;/assets/2024/vcf-whimsy.gif&quot; loading=&quot;lazy&quot; /&gt;
&lt;/video&gt;

&lt;p&gt;This is achieved with pretty much the most naive code you can imagine. I’m using JS to add the elements with  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;opacity: 0&lt;/code&gt;, and then schedule their reveal with a random delay:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getElementById&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;appendChild&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nf&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;el&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;classList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// sets `opacity: 1;`&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;Math&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This would work just as well for elements already in the DOM, too.&lt;/p&gt;</content><author><name></name></author><category term="code" /><summary type="html">It’s neat how a teensy little animation can add a little whimsy to an otherwise boring thing like loading a bunch of contact cards:</summary></entry><entry><title type="html">Easy self-hosting websites with Cloudflare and Docker Compose</title><link href="https://www.wassupy.com/2024/12/easy-self-hosting-cloudflare.html" rel="alternate" type="text/html" title="Easy self-hosting websites with Cloudflare and Docker Compose" /><published>2024-12-19T00:00:00+00:00</published><updated>2024-12-19T00:00:00+00:00</updated><id>https://www.wassupy.com/2024/12/easy-self-hosting-cloudflare</id><content type="html" xml:base="https://www.wassupy.com/2024/12/easy-self-hosting-cloudflare.html">&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h2&gt;

&lt;p&gt;This guide assumes you have the following:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;A Cloudflare account (free)&lt;/li&gt;
  &lt;li&gt;A domain name (bought from Cloudflare or elsewhere; $10/yr), set up in Cloudflare&lt;/li&gt;
  &lt;li&gt;A linux server with docker&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;the-general-idea&quot;&gt;The general idea&lt;/h2&gt;

&lt;p&gt;Web users connect to Cloudflare, and instead of Cloudflare reaching into our network, we run a service inside the
network that &lt;em&gt;reaches out to Cloudflare&lt;/em&gt;:&lt;/p&gt;

&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 879 240&quot; font-size=&quot;20&quot; stroke-width=&quot;4&quot; stroke=&quot;black&quot; fill=&quot;none&quot; role=&quot;img&quot;&gt;
    &lt;title&gt;CF Tunnels&lt;/title&gt;
    &lt;desc&gt;Diagram of a user connecting to a homelab via Cloudflare Tunnels. The diagram has three entities: user,
    Cloudflare, home lab. The user is connected to Cloudflare. The home lab has three docker containers. The first is
    labled &quot;CF Tunnel&quot; and it is connected to the Cloudflare entity, and to each of the other two containers: Webapp1,
    and Webapp2&lt;/desc&gt;
    &lt;defs&gt;
        &lt;marker id=&quot;head&quot; orient=&quot;auto&quot; markerWidth=&quot;4&quot; markerHeight=&quot;4&quot; refX=&quot;0.1&quot; refY=&quot;2&quot;&gt;
            &lt;path d=&quot;M0,0 V4 L2,2 Z&quot; stroke-width=&quot;1&quot; fill=&quot;black&quot; /&gt;
        &lt;/marker&gt;
    &lt;/defs&gt;

    &lt;g id=&quot;actor&quot; fill=&quot;none&quot;&gt;
        &lt;circle cx=&quot;50&quot; cy=&quot;50&quot; r=&quot;40&quot; /&gt;
        &lt;line x1=&quot;50&quot; y1=&quot;90&quot; x2=&quot;50&quot; y2=&quot;150&quot; /&gt;
        &lt;line x1=&quot;50&quot; y1=&quot;150&quot; x2=&quot;25&quot; y2=&quot;190&quot; /&gt;
        &lt;line x1=&quot;50&quot; y1=&quot;150&quot; x2=&quot;75&quot; y2=&quot;190&quot; /&gt;
        &lt;line x1=&quot;25&quot; y1=&quot;100&quot; x2=&quot;75&quot; y2=&quot;100&quot; /&gt;
    &lt;/g&gt;
    &lt;path id=&quot;cloudflare&quot; stroke-linecap=&quot;round&quot; stroke-linejoin=&quot;round&quot; d=&quot;M 172.498 118.768 C 172.498 175.125 220.749 189.214 244.868 189.214 L 376.449 189.214 C 396.186 189.214 435.661 177.687 435.661 131.576 C 435.661 85.467 396.186 73.94 376.449 73.94 C 376.449 54.727 356.712 9.898 310.658 9.898 C 273.816 9.898 251.447 35.515 244.868 48.323 C 220.749 48.323 172.498 62.413 172.498 118.768 Z&quot; style=&quot;fill: none;&quot; /&gt;

    &lt;g id=&quot;homelab&quot;&gt;
        &lt;rect x=&quot;500&quot; y=&quot;10&quot; width=&quot;365&quot; height=&quot;180&quot; /&gt;
        &lt;rect id=&quot;cftunnel&quot; x=&quot;520&quot; y=&quot;80&quot; width=&quot;135&quot; height=&quot;40&quot; /&gt;
        &lt;rect id=&quot;webapp1&quot; x=&quot;710&quot; y=&quot;30&quot; width=&quot;135&quot; height=&quot;40&quot; /&gt;
        &lt;rect id=&quot;webapp2&quot; x=&quot;710&quot; y=&quot;130&quot; width=&quot;135&quot; height=&quot;40&quot; /&gt;
    &lt;/g&gt;

    &lt;g id=&quot;connections&quot;&gt;
        &lt;path marker-end=&quot;url(#head)&quot; d=&quot;M90,100 155,100&quot; /&gt;
        &lt;path marker-end=&quot;url(#head)&quot; d=&quot;M515,100 450,100&quot; /&gt;
        &lt;path marker-end=&quot;url(#head)&quot; d=&quot;M660,90 695,75 &quot; /&gt;
        &lt;path marker-end=&quot;url(#head)&quot; d=&quot;M660,110 695,125 &quot; /&gt;
    &lt;/g&gt;

    &lt;g id=&quot;labels&quot; stroke-width=&quot;0&quot; fill=&quot;black&quot;&gt;
        &lt;text x=&quot;50&quot; y=&quot;220&quot; dominant-baseline=&quot;middle&quot; text-anchor=&quot;middle&quot;&gt;User&lt;/text&gt;
        &lt;text x=&quot;304&quot; y=&quot;220&quot; dominant-baseline=&quot;middle&quot; text-anchor=&quot;middle&quot;&gt;Cloudflare&lt;/text&gt;
        &lt;text x=&quot;681.5&quot; y=&quot;220&quot; dominant-baseline=&quot;middle&quot; text-anchor=&quot;middle&quot;&gt;Homelab Containers&lt;/text&gt;
        &lt;text id=&quot;cftunnel-label&quot; x=&quot;530&quot; y=&quot;108&quot;&gt;CF Tunnel&lt;/text&gt;
        &lt;text id=&quot;webapp1-label&quot; x=&quot;720&quot; y=&quot;58&quot;&gt;Webapp1&lt;/text&gt;
        &lt;text id=&quot;webapp2-label&quot; x=&quot;720&quot; y=&quot;158&quot;&gt;Webapp2&lt;/text&gt;
    &lt;/g&gt;

&lt;/svg&gt;

&lt;p&gt;We are &lt;strong&gt;not&lt;/strong&gt; opening any ports, e.g. http/https from our server to the local network or internet. We are &lt;strong&gt;not&lt;/strong&gt;
messing with dynamic dns stuff. We don’t need to set up TLS.&lt;/p&gt;

&lt;h2 id=&quot;an-example-with-docker-compose&quot;&gt;An example with Docker Compose&lt;/h2&gt;

&lt;h3 id=&quot;create-the-tunnel-config-at-cloudflare&quot;&gt;Create the tunnel config at Cloudflare&lt;/h3&gt;

&lt;p&gt;First create your tunnel in the Cloudflare dashboard &amp;gt; Zero Trust &amp;gt; Networks &amp;gt; Tunnels:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Choose &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cloudflared&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Name it whatever you want, e.g. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;homelab&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;Click the “docker” installation instructions just to &lt;em&gt;grab the token&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Don’t actually install anything&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Copy that token for the next section.&lt;/p&gt;

&lt;h3 id=&quot;on-your-homelab-server&quot;&gt;On your homelab server&lt;/h3&gt;

&lt;p&gt;Create &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose.yaml&lt;/code&gt; like this, with the token you copied above:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;3.3&quot;&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;tunnel&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cloudflare/cloudflared&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;restart&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;unless-stopped&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;tunnel run&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;TUNNEL_TOKEN=***insert CF Tunnel token here***&lt;/span&gt;

    &lt;span class=&quot;na&quot;&gt;web&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nginx:latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Start up the containers:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker compose up &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Tail the logs:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker compose logs &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;back-in-the-cloudflare-dashboard&quot;&gt;Back in the Cloudflare dashboard&lt;/h3&gt;

&lt;p&gt;At this point, the Cloudflare dashboard should show your tunnel as &lt;em&gt;healthy&lt;/em&gt;. If not, read those logs carefully for
errors and fix it before continuing.&lt;/p&gt;

&lt;p&gt;Add a public hostname to your tunnel:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;subdomain:&lt;/strong&gt; anything you want (this DNS record will be added for you)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;domain:&lt;/strong&gt; pick the domain you already set up in Cloudflare&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;service:&lt;/strong&gt; choose &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http&lt;/code&gt;, and set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;url&lt;/code&gt; to the name of your docker service. In the above example it’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;additional application settings:&lt;/strong&gt; none&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With that done, you should be able to browse to the subdomain you set up, and the response will be tunneled over from
your homelab server. Good job ✨&lt;/p&gt;

&lt;h2 id=&quot;adding-another-website&quot;&gt;Adding another website&lt;/h2&gt;

&lt;p&gt;You can add another container to the docker compose file like this:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;version&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;3.3&quot;&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;tunnel&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cloudflare/cloudflared&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;restart&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;unless-stopped&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;command&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;tunnel run&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;environment&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;TUNNEL_TOKEN=&amp;lt;insert sensitive token here&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;web&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;nginx:latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;restart&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;unless-stopped&lt;/span&gt;

  &lt;span class=&quot;na&quot;&gt;whoami&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;traefik/whoami&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;restart&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;unless-stopped&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And add a public hostname mapping in your tunnel:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;subdomain:&lt;/strong&gt; anything you want (this DNS record will be added for you)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;domain:&lt;/strong&gt; pick the domain you already set up in Cloudflare&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;service:&lt;/strong&gt; choose &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http&lt;/code&gt;, and set &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;url&lt;/code&gt; to the name of your docker service. &lt;strong&gt;This time it’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;whoami&lt;/code&gt;&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;additional application settings:&lt;/strong&gt; none&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;do-i-need-to-do-tls-http-compression-etc&quot;&gt;Do I need to do TLS, HTTP compression, etc.?&lt;/h2&gt;

&lt;p&gt;No. Cloudflare handles TLS and HTTP compression for you. The tunnel is encrypted so no additional TLS is necessary for
that leg.&lt;/p&gt;

&lt;p&gt;I suggest setting up a global redirect rule in Cloudflare to handle http-&amp;gt;https.&lt;/p&gt;

&lt;h2 id=&quot;can-i-build-my-own-containers&quot;&gt;Can I build my own containers?&lt;/h2&gt;

&lt;p&gt;Sure, that’s pretty easy. Your docker compose service can just point to a folder that itself contains a Dockerfile like
this:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;web&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;./web&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;restart&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;unless-stopped&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When you do &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker compose up -d&lt;/code&gt; it will build the image for you and then start it.&lt;/p&gt;

&lt;p&gt;…and run this to &lt;em&gt;rebuild&lt;/em&gt; the image as needed: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker compose up -d --build web&lt;/code&gt;&lt;/p&gt;

&lt;h2 id=&quot;why-dont-i-need-a-load-balancer-like-traefik&quot;&gt;Why don’t I need a load balancer like Traefik?&lt;/h2&gt;

&lt;p&gt;Often we’d carefully expose containers within docker to something &lt;em&gt;outside&lt;/em&gt; of docker by configuring ports and proxying
traffic through a load balancer like nginx or traefik. We’re not actually accepting connections to our
containers from outside of docker so we don’t need to do that.&lt;/p&gt;

&lt;p&gt;By default, docker compose containers can see each other and connect by service name (i.e. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;web&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;whoami&lt;/code&gt;, etc.). When
you configure the tunnel to resolve a public hostname to the internal docker service name, the tunnel container just
uses the docker network to talk to it. And the tunnel container can reach &lt;em&gt;out&lt;/em&gt; of the docker network to connect to
Cloudflare. If your container is listening on port 80, you’re done—you don’t need to expose it to the host network.&lt;/p&gt;

&lt;h2 id=&quot;troubleshooting&quot;&gt;Troubleshooting&lt;/h2&gt;

&lt;p&gt;See logs from your containers, including the cloudflare tunnel (read those error messages carefully!):&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker compose logs &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;See if the tunnel state is healthy in the Cloudflare dashboard&lt;/p&gt;

&lt;p&gt;See if your containers are running:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker container &lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name></name></author><category term="technology" /><category term="code" /><category term="homelab" /><summary type="html">Prerequisites</summary></entry></feed>