<?xml version="1.0" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Ben&apos;s Blog</title>
    <link>https://benkenawell.com</link>
    <atom:link href="https://benkenawell.com/feed.xml" rel="self" type="application/rss+xml"/>
    <item>
      <pubDate>Wed, 01 Jul 2026 00:00:00 GMT</pubDate>
      <title>Passing Behavior Through A Network</title>
      <link>https://benkenawell.com/p/passing-behavior-through-a-network.html</link>
      <guid>https://benkenawell.com/p/passing-behavior-through-a-network.html</guid>
      <description>&lt;p&gt;You can pass a description through a network. You can&amp;#39;t pass behavior or code through a network.&lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Passing Behavior Through A Network</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Passing Behavior Through A Network</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2026-07-01T00:00:00+00:00">Jul 1st, 2026</time>
    <span class="p-author">Ben</span>
  </div>
</header>

<p>You can pass a description through a network. You can&#39;t pass behavior or code through a network.<!--more--> With a strong declarative and semantic language, building the code on both ends of your network to allow a description of the behavior (your declarative language) to inform both sides to make it feel like you&#39;re passing the behavior itself through.</p>
<p>HTML is a great example of this. Add the <code>readonly</code> attribute to an input element in your HTML and now the input behaves differently (you can&#39;t edit the field). Your readonly attribute doesn&#39;t actually make it not-editable but tells the browser engine to render it that way and disallow typing.</p>
<p>On the web, Custom Elements are a great way to extend the html engine. Adding a <code>star-rating</code> component or <code>segmented-control</code> element via JS and <code>customElements.define</code> can then be declaratively invoked by your HTML. It&#39;s never been easier! This is a key conceptual point behind <a href="https://htmx.org/#:~:text=The%20hx%2Dpost,the%20HTML%20response%E2%80%9D">HTMX</a> too, with custom attributes instead of custom elements.</p>
<p>JSON Schema is less semantically rich out of the gate, but has support for attributes on fields that can be used in a very similar way. You need to create the engine that understands those attribute semantics. This gives you more flexibility than HTML provides at the expense of building it yourself.</p>
<p>React Server Components also fit this model. React is the engine deployed via javascript bundle, your components provide the semantics, and some form of json is the component description over the network.</p>
<p>Related Reading: <a href="https://unplannedobsolescence.com/blog/the-server-doesnt-render/">The Server Doesn&#39;t Render Anything</a>, <a href="https://htmx.org/essays/two-approaches-to-decoupling/">Two Approaches to Decoupling</a>, <a href="https://htmx.org/essays/webcomponents-work-great/">Web Components Work Great with htmx</a>, <a href="https://unplannedobsolescence.com/blog/what-dynamic-typing-is-for/#web-development-has-lots-of-dsls">Web Development Has Lots of DSLs</a></p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Fri, 12 Jun 2026 00:00:00 GMT</pubDate>
      <title>Handoff Between Release and Production</title>
      <link>https://benkenawell.com/p/2026-06-12-handoff-release.html</link>
      <guid>https://benkenawell.com/p/2026-06-12-handoff-release.html</guid>
      <description>&lt;p&gt;Most days as software engineers we don&amp;#39;t think of how our release actually makes it to production. &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Handoff Between Release and Production</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Handoff Between Release and Production</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2026-06-12T00:00:00+00:00">Jun 12th, 2026</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>Most days as software engineers we don&#39;t think of how our release actually makes it to production. <!--more-->  If we have one of numerous GitOps integrations, we just push to Github. Maybe someone wrote a script that runs or an action is manually fired. Occasionally you might find a slower release cadence with some more ceremony around it.  Only once have I ever &quot;thrown it over the fence&quot; to another engineer dedicated to the job (it was a very old product by that point).</p>
<p>Lots of companies have made it their business to help you deploy because you want to develop an app, not develop a deployment pipeline, right?  At least, that&#39;s the general salse pitch I hear. Using their process means releasing on their infrastructure, paying their prices. You may or may not be able to develop well with their system. It might be somewhat of a black box. I don&#39;t want to attribute anything malicious to them, their goal is to make it seem seamless and magical to you. But as engineers, we know that it isn&#39;t magical. And we pride ourselves in being able to take apart the internals when we need (sometimes when we want!).</p>
<h2 id="what-ive-tried">What I&#39;ve Tried</h2>
<p>If you aren&#39;t using a provider, how might you set up a deployment pipeline?  For years now with this personal site I had a Digital Ocean Droplet I had set up manually with Caddy, then I just built a Jekyll site on my computer and rsync&#39;d the files up.  As rudimentary as it might sound, it worked great for me. I never had any downtime (I would&#39;ve noticed, I think, because it wouldn&#39;t have come back up on its own) and I could focus on writing these posts.</p>
<p>That doesn&#39;t &quot;scale&quot; past static sites, and it doesn&#39;t really tell you what was released when.  For some personal projects, I started using Kamal. That worked really great for Rails applications once I got a registry set up, but it didn&#39;t work very well for my static sites.  Working with containers was very nice.</p>
<p>Looking at something <a href="https://ergaster.org/posts/2025/07/09-kubernetes-black-friday/">like Kubernetes</a>, I ended up going with <a href="https://docs.podman.io/en/latest/markdown/podman-quadlet.1.html">podman quadlets</a> instead. I thought it would be just a bit easier, and I still think it is. And it introduced me to the power of systemd.  I had all the power of container deployments and all the power of systemd, so I wrapped all my static sites in a caddy container and shipped it up to my Droplet.  This worked great, until I realized I had to deploy <em>all</em> my sites in order to deploy <em>any</em> of them.  I couldn&#39;t have multiple containers all sharing ports 80 and 443, so back to the drawing board I went.</p>
<p>Using caddy as a reverse proxy, I can now deploy all my sites individually. Combining the powers of systemd, podman, and caddy, I can deploy static sites and containers with equal ease, and manage them with the same commands.  I don&#39;t think I would want to deploy a rails app directly on to the machine, but I shouldn&#39;t ever need to. Containers cover that case amazingly well.  Caddy also has some fastcgi directies I might try eventually, to add interactivity to my static sites without a whole web app.</p>
<h2 id="moving-deployment-control-to-the-app-repo">Moving Deployment Control to the App Repo</h2>
<p>With those in place, I didn&#39;t want to go back to just rsync&#39;ing the static files back to the Droplet. I wanted something a little more controlled than that. The droplet runs Ubuntu and <a href="https://nfpm.goreleaser.com/">nfpm</a> makes it trivial to build debain packages.  Using a debian package, I can decide where all my files should be placed and installation will run a postinstall script. I can put my caddyfile in place, and systemd unit files, podman quadlets, static files, etc into the debian package. I can rsync that to the server then run <code>dpkg -i</code> to install.</p>
<p>How&#39;s that different that rsync&#39;ing the files up directly? It moves where the control of the release is.  My &quot;deploy&quot; scripts now live in an infrastructure repo. But my apps need to be released more often than that. I want to be able to change what the app needs with impunity, without having to modify a different repo.  I&#39;ve taken a hard dependency on the expectation of caddy and podman being present, but that doesn&#39;t seem different than other deployment processes. There are usually <em>some</em> release assumptions baked into the app repo.  I&#39;ll only need to change the deploy scripts if I change something about the computer it&#39;s running on.  Worst case, I change distros, but nfpm makes it trivial to build a rpm or other distro packages.</p>
<p>With this setup, I can even add container images to the debian package and load them with the postinstall script. Or pull them from a registry.</p>
<p>Using systemd and caddy, I can install a sensitive file in /etc then decrypt it at runtime into /run.</p>
<p>I can set up systemd timers or path files to perform backups on a schedule or reload services when files change.</p>
<p>From what I&#39;ve read about Kubernetes, I have 90% of what I would use on my personal sites.  And even if I&#39;m not in K8s, I think I&#39;m getting a taste for what they provide and why, if not necessasrily how.  But I also don&#39;t need to run the whole service K8s, or even minikube, require.</p>
<h2 id="versioning">Versioning</h2>
<p>With containers under your control, you might pin to a latest tag so an auto-update picks it up. Debian packages are the first deployment strategy I&#39;ve used that feels like it strongly benefits from version numbers.  Maybe I feel I need versioning now because debian pacakge don&#39;t have sha&#39;s of any kind like containers do (checksums, I suppose). Mostly I feel like it&#39;s an important way to talk about releases, so I wanted to have something similar.</p>
<p>I&#39;m using CalVer because nobody is consuming these sites based on SemVer rules.  I&#39;m using git tags to keep track of it.  5 lines of bash is all I need to query and calculate the next version.</p>
<pre><code class="hljs language-bash"><span class="hljs-comment"># find the current/latest tag</span>
git tag -l <span class="hljs-string">&#x27;v*&#x27;</span> | sed <span class="hljs-string">&#x27;s/^v//&#x27;</span> | <span class="hljs-built_in">sort</span> -V | <span class="hljs-built_in">tail</span> -1

<span class="hljs-comment"># create next tag</span>
prefix=$(<span class="hljs-built_in">date</span> +%Y.%m)
last=$(git tag -l <span class="hljs-string">&quot;v<span class="hljs-variable">${prefix}</span>.*&quot;</span> | sed <span class="hljs-string">&quot;s/^v<span class="hljs-variable">${prefix}</span>\.//&quot;</span> | <span class="hljs-built_in">sort</span> -n | <span class="hljs-built_in">tail</span> -1)
next=$((<span class="hljs-variable">${last:-0}</span> + <span class="hljs-number">1</span>))
version=<span class="hljs-string">&quot;<span class="hljs-variable">${prefix}</span>.<span class="hljs-variable">${next}</span>&quot;</span>
</code></pre><h2 id="release">Release</h2>
<p>Creating a release is a <a href="https://mise.jdx.dev/tasks/file-tasks.html">mise task</a> that checks for a clean git worktree, calculates the new version number, runs my build task with the version number, then creates the git tag.  The <a href="https://git.sr.ht/~benjk/benjk.srht.site/tree/stable/item/mise/tasks/release/_default">file</a> is not very long, even with checks and confirmation.  It builds a file to my local <code>~/.cache</code> directory, which I think it appropriate since the release could easily be recreated by running the build pipeline again.  In a larger system, or if you ran this on your git forge, this might become an artifact you download during deployment. Since my deployment is from my machine, I use <code>~/.cache</code> as my place to share the file.</p>
<h2 id="deploy">Deploy</h2>
<p>After I have a file with the version number as the name, my deployment script can simply look in the shared folder, grab the lastest version number, then deploy it.  It&#39;s as simple as collecting the necessary information out of my OpenTofu state, resync&#39;ing the package up, and running <code>dpkg -i</code>.  If you had a CI pipeline, the rsync might be a wget command. If you wanted more control, you could set up a apt repository. Then your actual deployement would just be an <code>apt update; apt upgrade &lt;pkg&gt;</code>.  I use rsync because I&#39;m keeping everything local for now.</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Wed, 10 Jun 2026 00:00:00 GMT</pubDate>
      <title>Watch out for name collisions!</title>
      <link>https://benkenawell.com/p/package-name-collision.html</link>
      <guid>https://benkenawell.com/p/package-name-collision.html</guid>
      <description>&lt;p&gt;While upgrading my websites, I noticed my caddyfile was gone. What happened?? &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Watch out for name collisions!</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Watch out for name collisions!</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2026-06-10T00:00:00+00:00">Jun 10th, 2026</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>While upgrading my websites, I noticed my caddyfile was gone. What happened?? <!--more-->  It looks like dpkg decided to upgrade my package without my knowledge and there was something else named &quot;staticsite&quot; in apt already. Scary!  I think this was mostly harmless to my site, but it could have been bad. I&#39;ll have to double check for name collisions before creating anymore deb packages.</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Wed, 10 Jun 2026 00:00:00 GMT</pubDate>
      <title>June 2026 Tech Stack</title>
      <link>https://benkenawell.com/p/june-2026-tech-stack.html</link>
      <guid>https://benkenawell.com/p/june-2026-tech-stack.html</guid>
      <description>&lt;p&gt;I like writing these out to see how they change over time. &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - June 2026 Tech Stack</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">June 2026 Tech Stack</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2026-06-10T00:00:00+00:00">Jun 10th, 2026</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>I like writing these out to see how they change over time. <!--more--> I&#39;ve added a lot of components to them over the years as I learn more about tools. I think I might finally be in a place where I could take something from nothing to deployed without too much click ops, in a way that can grow. And fast, cheaply.  I know I better understand each part of the &quot;stack&quot; and tradeoffs made along the way.</p>
<h2 id="the-site">The site</h2>
<p>I&#39;m mostly building websites, so this section is geared towards that. It&#39;s also the place with the most latitude here... Rails, Phoenix, and Laravel are big players and pretty full featured. I like the idea of Golang for some of my smaller stuff.</p>
<h3 id="well-known-route-server-rendered">Well known route, server rendered</h3>
<ul>
<li>ruby</li>
<li>rails (active record, active job, solid queue, etc.)</li>
<li>hotwire</li>
<li>omniauth (? for oauth)</li>
</ul>
<h3 id="client-side-rendered">Client side rendered</h3>
<p>I&#39;d reach for preact or solid if I needed to move fast, but maybe Lit or Svelte if I had a chance to try some new things. I&#39;d only reach for this if I was planning offline functionality a la PWA.</p>
<h3 id="the-new-hotness----a-golang-stack">The new hotness -- A Golang stack</h3>
<p>I think this would be lightweight and fast.  It has lots built into the standard lib, pretty well.</p>
<ul>
<li>golang - language</li>
<li>cobra - command line tool, so it could do more than <code>serve</code></li>
<li>viper - command line helper</li>
<li>sqlc/sqlx - start with sqlite</li>
<li>goose/dbmate - language agnostic migrations, with Go API so the app could actually migrate when starting up</li>
<li>SQLite via modernc.org/sqlite, unless I go postgres</li>
<li>backlite - background jobs</li>
<li>htmx? alpine? - some frontend interactivity</li>
<li>chi/stdlib - do I need a http framework? Or can I use the standard library?</li>
<li>templ/stdlib - do I need a templating framework? Or can I use the standard library?</li>
<li>oauth via coreos/go-oidc/v3 + x/oauth2 - so I can put this behind Authelia and not write auth myself.</li>
</ul>
<h3 id="well-trodden-and-quick">Well trodden and quick</h3>
<p>I could throw this together in an afternoon and have a demo for you. It wouldn&#39;t scale as well as the Rails app though and probably wouldn&#39;t be as fast the the Golang app</p>
<ul>
<li>node</li>
<li>express</li>
<li>nunjucks</li>
<li>sqlite</li>
</ul>
<h2 id="packaging--deployment">Packaging / deployment</h2>
<p>A brand new section for me, I thought it deserved it&#39;s own section because it sits somewhere between building the app and the infrastructure it runs on. They have different lifecycles, they deserve different sections but bridging the gap in a way that deployment doesn&#39;t have to rely on the app can be a tough problem.  It boils down to creating an artifact that your deployment knows how to use.  Containers are an easy answer, but for my static sites were actually way overkill.  If I were using Kubernetes, that would affect this choice too. But deploying on my own VPS&#39;s had me &quot;go native&quot;</p>
<p>Packaging everything up in a native format for my VPS makes it easier to just copy over and run install from the infra side.  Out of band secret management is a different story, it has its own section below.</p>
<ul>
<li>buildah - building containers, part of podman the way I use it.</li>
<li>nfpm - building debian packages</li>
<li>tar - if all I need is a tarball</li>
</ul>
<p>(what, I said native but I&#39;m building containers??? See the podman item under infra)</p>
<h2 id="the-infra">The infra</h2>
<p>A completely revamped section. I&#39;ve felt mostly comfortable deploying to my own machine for a while now. Either with Kamal or some bash scripts. But Kamal was a little too constraining and my bash scripts never felt very polished. Recently, I&#39;ve dropped Kamal but it could come back.</p>
<ul>
<li>OpenTofu - provisioning infrastructure</li>
<li>cloud-init - getting my basic packages in place</li>
<li>mise tasks - uploading/deploying my packages, bash scripts with ways to use flags and envvars effectively.</li>
<li>systemd - process supervisor, much lighter weight than kubernetes or other options, often built in. Capable of running timers to process backups as well.</li>
<li>caddy - used as a reverse proxy for my apps, or serving them directly with a caddy file. can be run directly on the VPS as a systemd service. Allows me to reverse proxy or serve static files as I&#39;d like.</li>
<li>podman - provide a container runtime, running containers as systemd services via quadlets (and their systemd generator).</li>
</ul>
<h2 id="secret-management-and-sensitive-values">Secret Management and Sensitive Values</h2>
<p>I&#39;m not sure how best I like doing this quite yet.  sops is an easy answer for deploying sensitive values. Then age (with ssh) since I&#39;m keeping it low cost for myself. I should probably re-encrypt the sensitive values as part of their deployment. In a case where I was using a KMS it would be a non issue. And a KMS would be an easy sell for business</p>
<ul>
<li>sops</li>
<li>age</li>
<li>ssh</li>
</ul>
<h2 id="what-did-i-forget">What Did I Forget?</h2>
<p>What about all the things apps need, like a database? high availability? failover? telemetry? auth? uptime monitoring?</p>
<p>Those are great questions. I didn&#39;t include them above, the above allows me to more flexibly deploy any and all of these things. Caddy could give me some HA/Failover I think, but doesn&#39;t the way I&#39;m using it.</p>
<ul>
<li>Authelia - oauth provider, I want my users to be able to access all my apps with one log in.</li>
<li>Victoria Metrics - going to try instead of Prometheus</li>
<li>Jaeger</li>
<li>Grafana Loki</li>
<li>Uptime Kuma</li>
<li>Tailscale/Wireguard for secure comms</li>
<li>Zot - container registry, if I need one.</li>
<li>Seaweed FS - if I need an S3 bucket, or I could use Backblaze</li>
</ul>
<p>There&#39;s a lot of Golang services in that list, which is a big part of what has me looking at Go. I&#39;m not sure if it&#39;s fortunate or unfortunate that there isn&#39;t a big framework in Go, but a lot of the CNCF projects seem to do great in Go without it.</p>
<h2 id="dev-tools">Dev Tools</h2>
<p>Very often these posts list dev tools for me.  This section hasn&#39;t changed very much, but let&#39;s list things out again. Commands I run in the course of a day. Apps I use for development.</p>
<ul>
<li>ghostty</li>
<li>tmux</li>
<li>neovim</li>
<li>batcat</li>
<li>eza</li>
<li>fzf</li>
<li>git</li>
<li>mise</li>
<li>usage</li>
<li>bash</li>
<li>curl</li>
<li>jq</li>
</ul>
<p>mise has been a huge productivity boost. Along with <a href="https://usage.jdx.dev/cli/scripts">usage</a> to define flags and envvars and running by default in a known directory are both huge quality of life improvements. I guess I don&#39;t <em>need</em> mise for usage, but the combination has been powerful.</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Mon, 08 Jun 2026 00:00:00 GMT</pubDate>
      <title>From Containers And Back</title>
      <link>https://benkenawell.com/p/from-containers-and-back.html</link>
      <guid>https://benkenawell.com/p/from-containers-and-back.html</guid>
      <description>&lt;p&gt;A month or two ago I revamped my deployment process. I was manually scp&amp;#39;ing files on to a server &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - From Containers And Back</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">From Containers And Back</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2026-06-08T00:00:00+00:00">Jun 8th, 2026</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>A month or two ago I revamped my deployment process. I was manually scp&#39;ing files on to a server <!--more--> and I wanted something that felt less fragile than that. I&#39;m familiar with containers so I figured I would build a container! I wrapped up my caddy server and decided to use podman because I thought I was familiar with their quadlets and that would be an easier than something like Kubernetes.  That sent me down a whole rabbit hole learning about <a href="https://systemd.io/">systemd</a> and socket activation then <a href="https://opentofu.org/">OpenTofu</a> and <a href="https://getsops.io/">sops</a> and <a href="https://github.com/FiloSottile/age">age</a>. It turns out I didn&#39;t know quadlets very well, but I know them much better now!</p>
<blockquote>
<p>TL;DR: infra and apps/sites have different lifecycles; treat them differently. Make sure you can deploy all your apps/sites separately.</p>
</blockquote>
<p>I didn&#39;t fully appreciate how scp&#39;ing static files to the server gave me separate deployments for free.  I didn&#39;t have my Caddyfile separated out, so they were still interconnected. But my Caddyfile changed so seldom that it wasn&#39;t a big deal.</p>
<p>Deploying a container was a pretty nice artifact to deploy, but I realized then I had to deploy <em>all</em> my sites and services together, the way I had set it up. That was a bad idea, I couldn&#39;t leave anything half finished! I needed to be able to deploy them separately again. Four separate deployments meant I couldn&#39;t have a single artifact to push up. I needed something in front to coordinate my sites. Something that didn&#39;t actually bundle them together so tightly, but each deployment could affect. Enter back in caddy and systemd.</p>
<p>I&#39;m using caddy to front all my other sites/services.  This is basically the same way I was doing it before, but this time around I broke the Caddyfile apart. I can have caddy include all the Caddyfiles in a particular directory.  Leveraging this, each site can &quot;install&quot; its own caddyfile. If it&#39;s a service and shipping a container, maybe that holds a reverse_proxy directive. Maybe it&#39;s just some static files with file_server or even uses the <a href="https://caddyserver.com/docs/caddyfile/directives/templates#examples">templates directive</a>. Caddy can terminate tls and then the sky is the limit! [^1]</p>
<p>I also learned a good deal about packaging. Debian packages are basically a couple of tarballs archived together.  They hold a tarball that maps on to your system directory and will place files where they appear. It has a postinstall script (among others) that can do anything, including loading container images or reloading systemd&#39;s daemon.  A tool like <a href="https://nfpm.goreleaser.com/">nfpm</a> makes it easy to define how your repo directories map to the tarball.  Having a package makes for an easy &quot;handoff&quot; between an site repo and my infra repo.  This artifact can affect more of the system it runs on than a container can.</p>
<p>Containers can&#39;t describe how they&#39;re deployed, so you always need something like this. Podman and systemd via quadlets is a great method for deployment. I can wrap those unit files up in a deb package and podman will pull them! The postinstall script can reload the systemd daemon to make sure they&#39;re picked up.  Security is only as good as my unit files I guess, but at least I didn&#39;t have to hand write the caddy one.</p>
<p>I now have an infrastruture repo with OpenTofu that sets up the caddy server which will front the rest of my sites and podman because I&#39;m sure I&#39;ll use a container runtime. Debian packages make it easy to install my sites and other softwares as I need them.  Having a separate repo is important because I will very seldomly need to change much about the server, but I want to deploy my sites very often!</p>
<p>I can put all the deployment scripts in the infra repo. That includes scripts for all my sites. The sites build their artifact, the deb package, then I can deploy from the infra repo using a <a href="https://mise.jdx.dev/tasks/">mise task</a>.  I looked into Ansible and others but didn&#39;t find anythign I liked enough for my very simple scripts, so these are just bash with a slightly nicer handling.  This splut should make it easy for me to turn this into GitOps if I ever wanted to. Build the package on a push, then pull from the remote and install. But I don&#39;t have any plans to do that just yet.</p>
<p>Lastly, secrets can go right in deployment artifacts. Using sops and age, I can write unit files that decode them on the server in a very ephermeral way. I&#39;m not sure that it&#39;s as good as podman secrets, but I&#39;m sure I&#39;ll learn how to make it even better in time.</p>
<p>My old stack: caddy, systemd (unwittingly), bash</p>
<p>My new stack: caddy, systemd (knowledgeably), bash, opentofu, mise, podman, nfpm, sops, age, cloud-init</p>
<p>What I lost: nothing, in the end. This was a big knowledge gain and massive deployment improvement</p>
<p>What I gained: the ability to deploy as many sites as I want easily. Knowledge about the Linux Filesystem Hierarchy (FHS). A container runtime on my server. Better secret management</p>
<p>For the time being, this system is flexible enough that I can continue to use my container setup as I migrate my other repos back and break apart that container.</p>
<p>[^1]: The caddy file is really simple: <code>import /etc/caddy/othercaddyfiles/*.caddyfile</code> I can have cloud-init drop it on the new machine</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Mon, 11 May 2026 00:00:00 GMT</pubDate>
      <title>A Weekend With sops and age</title>
      <link>https://benkenawell.com/p/first-use-sops-age.html</link>
      <guid>https://benkenawell.com/p/first-use-sops-age.html</guid>
      <description>&lt;p&gt;They seem almost too easy. It&amp;#39;s safe to git commit my secrets now? &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - A Weekend With sops and age</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">A Weekend With sops and age</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2026-05-11T00:00:00+00:00">May 11th, 2026</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>They seem almost too easy. It&#39;s safe to git commit my secrets now? <!--more--> A keyfile can really be that easy to read?  I looked into <a href="https://getsops.io/">sops</a> and <a href="https://github.com/FiloSottile/age">age</a> this weekend and like what I found.  I host a little wedding countdown timer for my family weddings. The data isn&#39;t super secret, but I also don&#39;t need to make it super public. So I&#39;ve kept it out of version control. I deploy from my own machine, so it just lived there for a while. Then I wrote a script to load it from my password manager.</p>
<p>But sops takes a different approach. As a CNCF Sandbox project donated by Mozilla, it encrypts values in the same structured format you use it in. It keeps the keys in the file so it&#39;s easy to see what has changed in a diff. Then, because the values are all encrypted, you can just commit the file to your git repo!</p>
<p>Editing happens with the <code>sops</code> command, which decrypts and opens the file in your editor, then re-encrypts it after closing, pretty snazzy!  sops does the structured part, but what keys do you encrypt with? It seems sops integrates with a lot of cloud options, but age caught my eye.  It&#39;s a single Go binary with short keys and is capable of using ssh keys as well.  Public keys can be put in .sops.yaml for sops to load, specifying age with the age key.  Then when decrypting, age private keys live in <code>~/.config/sops/age/keys</code>.txt. All your keys in a simple text file with comments!</p>
<p>The combo really shines when you realize you can encrypt with multiple public keys.  I followed parts of <a href="https://www.dchost.com/blog/en/the-calm-way-to-secrets-on-a-vps-gitops-with-sops-age-systemd-magic-and-rotation-you-can-sleep-on/">this dchost tutorial</a> and want to continue with the systemd stuff too.</p>
<h2 id="send-your-coworkers-encrypted-messages">Send your coworkers encrypted messages</h2>
<p>Github lets you easily download public ssh keys for anyone using <code>https://github.com/&lt;username&gt;.keys</code>.  You can download the keys for all of your coworkers then encrypt a message that any of them can descrypt with the ssh key they already use for github!  Not a long term solution, maybe, but pretty cool that it&#39;s so simple.</p>
<pre><code class="hljs language-bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;secret message&quot;</span> &gt; msg <span class="hljs-comment"># put secret message in here</span>
<span class="hljs-built_in">mkdir</span> -p keylists/
<span class="hljs-comment"># can repeat with multiple coworkers</span>
curl -L https://github.com/&lt;username&gt;.keys &gt; keylists/&lt;username&gt;
<span class="hljs-built_in">cat</span> keylists/* | age --encrypt --armor -R - msg &gt; msg.age
</code></pre><p>Send it in any text channel (Slack? WhatsApp? IRC?) and let them decrypt!</p>
<pre><code class="hljs language-bash">age --decrypt -i ~/.ssh/gh msg.age <span class="hljs-comment"># will print to terminal</span>
</code></pre><p>I would print you an example, but I don&#39;t know you yet!  It&#39;s so much simpler than gpg or any other provider I&#39;ve used.  Maybe I&#39;ll use it in more places. sops has some other cool features like <code>exec-env</code> and <code>exec-file</code>, which seems like really cool ways to never need to decrypt the file on your machine at all. One less operational worry!</p>
<p>Enjoy explaining both of these names to your coworkers, hopefully they&#39;ll be happy using it no matter how they decide to pronounce them.</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Sun, 03 May 2026 00:00:00 GMT</pubDate>
      <title>Running an App Server</title>
      <link>https://benkenawell.com/p/running-an-app-server.html</link>
      <guid>https://benkenawell.com/p/running-an-app-server.html</guid>
      <description>&lt;p&gt;Running an app server isn&amp;#39;t enough.&lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Running an App Server</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Running an App Server</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2026-05-03T00:00:00+00:00">May 3rd, 2026</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>Running an app server isn&#39;t enough.<!--more--> Eventually you&#39;ll want to automatically restart your app if it crashes. Eventually you&#39;ll want to be notified if it crashes. Eventually you&#39;ll want to observe how your app is working. Eventually you&#39;ll want to tweak settings without restarting your app. Eventually you&#39;ll want to gracefully degrade your app when it&#39;s stressed, so it crashes less often. Eventually you&#39;ll want to backup data that your app is creating. Eventually you&#39;ll want two copies of your app to handle more requests (horizontal scaling). Eventually you&#39;ll want to expand/contract the number of running app copies. Eventually you&#39;ll want better insight on how people are using your app. Eventually one database won&#39;t scale vertically fast enough. Eventually you&#39;ll want to authenticate a user to your app. Eventually you&#39;ll want to tell another service you&#39;ve done something. Eventually you&#39;ll want to know when an outside service has done something. Eventually you&#39;ll want your websockets to stay connected through redeploying your app. Eventually you&#39;ll want healthchecks because monitoring the process isn&#39;t enough.</p>
<p>None of these things <em>are your app</em> but they all work together to make sure your app works. They&#39;re all capabilities around the core business logic of your app. I wasn&#39;t an engineer during the microservice craze. I&#39;ve been learning a lot about the graduated and incubating CNCF projects though. There&#39;s a lot of work with initiatives like OpenTelemetry and OpenFeature that I wouldn&#39;t want to replicate in my app. Authentication is really tough to set up all the features people may want (password, passwordless, MFA, passkey, etc). What if you had a central server you could trust (a la forward auth or OIDC)? Whole companies are built around adding these capabilities to your app. Clerk and Firbase sell auth, Datadog/Grafana/Betterstack/Sentry all sell telemetry. Vercel/Posthog sell feature flagging. If you use any of these services, are you running microservices now? Or is it still a monolith? If you run all those same things yourself -- Jaeger, Prometheus, Keycloak/Authelia, flagd -- are you still building a monolith or microservices?</p>
<p>We all agree a reverse proxy is a good idea for a web server, so you&#39;re always going to have at least two services running. Or do you want to build TLS support directly into your app? Then how would you handle load balancing?</p>
<p>I&#39;ve read DHH&#39;s majestic monolith, but I&#39;ve also seen them release Upright. I&#39;ve seen them try different auth methods (all standards, but built into their app) and I&#39;ve seen people always ask for <em>their preferred, portable auth method</em>. You can&#39;t make everyone happy, but maybe you don&#39;t need to reinvent the wheel either.</p>
<p>I&#39;m not arguing or advocating for a monolith or microservices. I&#39;m not arguing for hosting it all or paying someone else.</p>
<p>If you do decide to run microservices, don&#39;t separate your business logic too much. You&#39;ll already run a lot more services than you think.</p>
<p>If you decide on a monolith, don&#39;t try to do too much. There are many standards out there and many smart people have thought through these problems. There are many self hosted projects or people to pay to take care of something you don&#39;t care to implement. Let your app stay focused on your core business.</p>
<p>Whatever you call it, you&#39;ll want many of the same parts other people use. Hopefully you&#39;ll share something useful or fix something broken, and we can all continue to learn together. Learn the names, they help communicate the concepts. Learn the concepts and their nuances, names can&#39;t capture every detail. If they did, we&#39;d all have names as long as Tolkien&#39;s Ents. Eventually someone will pay you for your discernment. You can take great pride in it and they don&#39;t have to learn about it.</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Wed, 29 Apr 2026 00:00:00 GMT</pubDate>
      <title>Systemd Socket Activation Tutorial with Caddy and Quadlets</title>
      <link>https://benkenawell.com/p/socket-activation-caddy-quadlets-tutorial.html</link>
      <guid>https://benkenawell.com/p/socket-activation-caddy-quadlets-tutorial.html</guid>
      <description>&lt;p&gt;I&amp;#39;ve spent the past couple of days learning about how systemd.socket files work and how they work with a little caddy server. &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Systemd Socket Activation Tutorial with Caddy and Quadlets</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Systemd Socket Activation Tutorial with Caddy and Quadlets</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2026-04-29T00:00:00+00:00">Apr 29th, 2026</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>I&#39;ve spent the past couple of days learning about how systemd.socket files work and how they work with a little caddy server. <!--more-->  It feels like a bit of a super power, so I wanted to share.  I referenced <a href="https://ilmanzo.github.io/post/systemd-socket-activated-services/">this blog post</a> a lot while writing my own test sockets, it&#39;s another great resource.</p>
<p>I&#39;ll be using <a href="https://www.redhat.com/en/blog/quadlet-podman">podman quadlets</a> in this tutorial to run the caddy server, but you could run the <a href="https://caddyserver.com/docs/running#linux-service">caddy.service</a> directly if you&#39;d like. So many of these commands need to run as root, I may assume you&#39;re the root user, or prepend sudo.  Our service should do something you can see, so it will returns good tidings.</p>
<h2 id="why-socket-activation">Why Socket Activation</h2>
<ul>
<li>More secure, Can run container as unpriviledged user connected to a priviledged port</li>
<li>Faster boot, only the socket starts</li>
<li>Save resources, the server can be shutdown when it isn&#39;t in use (called exit-on-idle)</li>
</ul>
<h2 id="get-the-computer-ready">Get the computer ready</h2>
<p><a href="/p/new-version-ubuntu.html">Ubuntu 26.04</a> came out a few days ago and has podman 5.7 in its package repos, so that&#39;s a good place to start.  With a new machine, run these commands to get podman.</p>
<pre><code class="hljs language-bash">apt update
apt install podman
</code></pre><h2 id="write-our-files">Write our files</h2>
<h3 id="socket-unit">Socket Unit</h3>
<p>I&#39;m going to write my files in the home directory of my user, cadusr.  First up is a <a href="https://www.freedesktop.org/software/systemd/man/latest/systemd.socket.html">socket file</a>.  Names are important, the socket and container file names must match. I&#39;ll call this <code>tidings.socket</code></p>
<pre><code class="hljs language-ini"><span class="hljs-section">[Unit]</span>
<span class="hljs-attr">Description</span>=socket for good tidings
<span class="hljs-attr">Before</span>=tidings.service

<span class="hljs-section">[Socket]</span>
<span class="hljs-attr">ListenStream</span>=localhost:<span class="hljs-number">80</span>
<span class="hljs-attr">FileDescriptorName</span>=http

<span class="hljs-section">[Install]</span>
<span class="hljs-attr">WantedBy</span>=sockets.target
</code></pre><p>This socket file describes to systemd what socket it should listen on.  The &quot;Before&quot; key tells systemd that tidings.service relies on tidings.socket. Socket units will actually get this by default, but I&#39;ve put it here to illustrate.  The Socket table has the port to listen on and gives it a name.  The name sets LISTEN_FDNAMES in <a href="https://www.freedesktop.org/software/systemd/man/latest/sd_listen_fds_with_names.html#">the environment</a> with a colon separated list of names given to the sockets.  systemd passes these sockets as file descriptors (think stdin, stdout) starting at 3. LISTEN_FDS gives you the number of sockets passed through.  The &quot;ListenStream&quot; keyword specifies the type of socket, in our case this will work for http/s communication. The &quot;WantedBy&quot; </p>
<h3 id="caddyfile">Caddyfile</h3>
<p>Before we write our container file, let&#39;s write the Caddyfile that we will configure caddy with.  It likes to be <a href="https://caddyserver.com/docs/running#docker-compose">mounted in a directory</a>, not the file directly, so we&#39;ll create a <code>caddy</code> directory for it.</p>
<pre><code class="hljs language-bash"><span class="hljs-built_in">mkdir</span> caddy
<span class="hljs-built_in">touch</span> caddy/Caddyfile
</code></pre><pre><code class="hljs language-caddy">http:// {
  bind fd/3
  respond &quot;Good tidings&quot;
}
</code></pre><p>That&#39;s it for the Caddyfile.  This tells caddy to listen on http protocol but doesn&#39;t give it a port or hostname. We bind &quot;fd/3&quot;, or file descriptor 3, as the interface, which we know systemd will provide for us.</p>
<h3 id="container-file">Container file</h3>
<p>Last, let&#39;s set up our caddy container.  In a file named <code>tidings.container</code>, write the following:</p>
<pre><code class="hljs language-ini"><span class="hljs-section">[Unit]</span>
<span class="hljs-attr">Description</span>=send good tidings
<span class="hljs-attr">Requires</span>=tidings.socket
<span class="hljs-attr">After</span>=network.target

<span class="hljs-section">[Container]</span>
<span class="hljs-attr">Image</span>=docker.io/library/caddy
<span class="hljs-attr">Volume</span>=/home/cadusr/caddy:/etc/caddy <span class="hljs-comment"># replace with your own Caddyfile directory</span>
<span class="hljs-attr">ReloadCmd</span>=/usr/bin/caddy reload --config /etc/caddy/Caddyfile --force

<span class="hljs-section">[Service]</span>
<span class="hljs-attr">User</span>=<span class="hljs-number">1000</span> <span class="hljs-comment"># replace with output from `id -u` when you aren&#x27;t root</span>
<span class="hljs-attr">Group</span>=<span class="hljs-number">1000</span> <span class="hljs-comment"># replace with output from `id -g` when you aren&#x27;t root</span>
</code></pre><p>A <a href="https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html">Container Unit</a> can be used by systemd because podman has a generator for it to turn it into a systemd service, this is why we can&#39;t use docker or another container runtime.
Table by table, the Unit table just sets up a description and some dependencies for us. systemd is great at dependency management. The Container table is where we put all our podman related keys.  These correspond very closely with the command line values (or docker compose values).  Notice I&#39;ve put a ReloadCmd so we can call <code>systemctl reload tidings.service</code> to reload our caddy file; systemd knows how to do it! We also don&#39;t publish any ports, which is unusual for a web service.  The socket unit is what listens on the port in this case, caddy just binds to a file descriptor. So the container doesn&#39;t need any ports open, or really any network priviledges! Lastly, the Service table. This is forwarded through the podman generator verbatim. In our case, we&#39;re going to run our service as the user id 1000 and group id 1000, so our container doesn&#39;t run as root.</p>
<h2 id="installing-our-unit-files">Installing our unit files</h2>
<p>&quot;Installing&quot; the unit files is as simple as copying to the right place and reloading systemd.  podman has a fancy <code>quadlet install</code> command that helps us, but I&#39;m not sure it does anything but copy (and maybe verify?) to the right place.  Note, both these commands must be run as root. If you run podman quadlet install as your user, it will become a user unit file instead of a system unit file. Then your socket won&#39;t be able to find your service and neither of them will work.  Even though our container will run as an unpriviledged user, it&#39;s still installed as a system unit.</p>
<pre><code class="hljs language-bash"><span class="hljs-built_in">cp</span> tidings.socket /etc/systemd/system
podman quadlet install tidings.container
systemctl daemon-reload
</code></pre><p>Once we install the file, you can run <code>systemctl cat tidings.service</code> to see the generated service unit.</p>
<p>If you accidently install a bad container unit, sometimes podman will refuse to install a new one or remove the old one.  It doesn&#39;t give you great feedback unless you run <code>podman quadlet list</code>.  In that case, you can just remove it manually from where it was &quot;installed&quot; (copeid) to. <code>rm /etc/conatiners/systemd/tidings.container</code></p>
<h2 id="enable-our-units">Enable our units</h2>
<p>Either quadlets or systemd generators don&#39;t really need to be enabled, but we definitely want to enable and start the socket. This next command enables the socket (so it starts on boot) and starts it now for us.</p>
<pre><code class="hljs language-bash">systemctl <span class="hljs-built_in">enable</span> --now tidings.socket
</code></pre><p>Now systemd is listening to port 80 for us!  We can hit port 80 on our machine with curl. The first time it will take a moment as the caddy server starts. Subsequent invocations of curl will work much faster!</p>
<pre><code class="hljs language-bash">curl localhost
<span class="hljs-comment"># should return &quot;Good tidings&quot;</span>
</code></pre><p>Now we have a socket activated service that will be supervised by systemd! Good work!</p>
<h2 id="further-usage">Further Usage</h2>
<h3 id="more-than-one-port">More than one port</h3>
<p>Most web servers will listen to at least two ports, 80 and 443, so this socket activation wouldn&#39;t be very useful if we couldn&#39;t bind to more than one port.  Fortunately, you can add <code>ListenStream</code> as many times as you want in the socket file.  systemd guarantees file descriptor ordering of sockets listed within the same .socket file (it does not guarantee them across socket files, if you had more than one socket file for the same service).  If we wrote this socket file:</p>
<pre><code class="hljs language-ini"><span class="hljs-section">[Socket]</span>
<span class="hljs-attr">ListenStream</span>=<span class="hljs-number">80</span>
<span class="hljs-attr">ListenStream</span>=<span class="hljs-number">443</span>
</code></pre><p>then fd/3 would always be port 80 and fd/4 would always be port 443.  This is nice because caddy doesn&#39;t yet support the names passed through.</p>
<p>You can also listen on specific interfaces. When we set our ListenStream to localhost:80 before, systemd opened a socket that only listened to the localhost (loopback) interface. With just setting the value to 80 like I did above, systemd will listen on all interfaces and your caddy server will be reachable externally to your computer.  This is nothing special for caddy, but just know that you aren&#39;t losing that here; it&#39;s really only that the port is passed to you instead of your service needing to bind it itself.</p>
<h3 id="running-it-yourself">Running it yourself</h3>
<p>We set up unit files and installed them so systemd would handle the lifecycle of our app and make it easy for us to control with typical systemd commands.  But systemd also provides <a href="https://www.freedesktop.org/software/systemd/man/251/systemd-socket-activate.html">a shell command</a> you can run to set up a socket activation.</p>
<pre><code class="hljs language-bash">systemd-socket-activate -l <span class="hljs-string">&quot;localhost:80&quot;</span> podman run --volume /home/cadusr/caddy:/etc/caddy docker.io/library/caddy
</code></pre><p>That command is the equivalent of our unit files from above. This is just for a one off command and a great way to play with socke activation. While the process is running it behaves the exact same way as our unit files.  A few options I find very interesting are &quot;accept&quot; and &quot;inetd&quot; which basically make systemd into a web server for cgi-like scripts. You programming language would still need to set up the environment for you, but it&#39;s a very simple stdin to stdout interface to talk with the world.</p>
<h3 id="writing-a-web-service-for-socket-activation">Writing a web service for socket activation</h3>
<p>What if you are writing your web app and have it containerized and want it to support socket activation? I don&#39;t think I saved the fabulous tutorial I found online about eating lunch. But the basic premise is to check for the LISTEN_FDS environment variable. If it&#39;s set, you know you can connect to fd/3, however your programming language expresses that. For node I think this might be as simple as</p>
<pre><code class="hljs language-javascript">net.<span class="hljs-title function_">createServer</span>().<span class="hljs-title function_">listen</span>({<span class="hljs-attr">fd</span>: <span class="hljs-number">3</span>})
</code></pre><p>although I haven&#39;t made a good test for it.</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Tue, 28 Apr 2026 00:00:00 GMT</pubDate>
      <title>Ubuntu 26.04</title>
      <link>https://benkenawell.com/p/new-version-ubuntu.html</link>
      <guid>https://benkenawell.com/p/new-version-ubuntu.html</guid>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Ubuntu 26.04</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Ubuntu 26.04</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2026-04-28T00:00:00+00:00">Apr 28th, 2026</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>Just wanted to celebrate the release of a <a href="https://ubuntu.com/server">Ubuntu version</a> that ships with a new enough version of podman to have the quadlets subcommand and the replace flag! <a href="https://docs.podman.io/en/v5.7.0/">podman version 5.7.0</a></p>
<p>I&#39;m familiar with different types of Linux in general, but I&#39;m most familiar with Debian and Ubuntu. I didn&#39;t have trouble getting Fedora set up with podman once I figured out it had what I needed, but it&#39;s nice that there are more options now!</p>
<p>Hopefully this makes it easier in general to use quadlets for more people so they will continue to see more adoption.</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Wed, 22 Apr 2026 00:00:00 GMT</pubDate>
      <title>Moving To Quadlets</title>
      <link>https://benkenawell.com/p/moving-to-quadlets.html</link>
      <guid>https://benkenawell.com/p/moving-to-quadlets.html</guid>
      <description>&lt;p&gt;I moved my website from running &lt;a href=&quot;https://caddyserver.com/&quot;&gt;caddy&lt;/a&gt; as a systemd service to running a container as a systemd service! &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Moving To Quadlets</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Moving To Quadlets</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2026-04-22T00:00:00+00:00">Apr 22nd, 2026</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>I moved my website from running <a href="https://caddyserver.com/">caddy</a> as a systemd service to running a container as a systemd service! <!--more--> Otherwise known as Podman Quadlets, podman has a systemd generator that will let you run containers as a systemd service using Unit files that resemble podman run commands (or docker compose yaml) in systemd&#39;s ini file syntax.</p>
<p>The result is a container with systemd running as its supervisor.  systemd will start the container when the system starts. It can manage the lifecycle and more. You can interact with your container with typical systemd commands.</p>
<h2 id="motivation">Motivation</h2>
<p>My deployment method was simple, but very bespoke. I had written my original Caddyfile <em>on the server</em> and that was the only place it lived.  My deployment system was a bespoke set of scripts for every site/repo I hosted there (four and counting) and it was all tied together by scp and rsync&#39;ing into the right directories.  I would have never been able to recreate it. Maybe, given enough time, but the configurations were so scattered and that Caddyfile still only lived on the single server.</p>
<p>That worked great for me. I was barely comfortable with bash at the time, but caddy made it so simple that I was able to get a website up and keep it running for 3+ years this way. I don&#39;t want to understate this, it was my beginning on a journey running my own operations.</p>
<p>But it&#39;s been three years and that box is out of date.  I&#39;ve become very comfortable with bash and containers.  I think containers are really great; their runtimes, like Docker and Podman, offer a standard interface to start them, stop them, and more.  A Containerfile (or Dockerfile) is an easy way to write down the configuration in a way that makes it trivially repeatable to build.</p>
<p>I want to have a repeatable build that coordinates across my repos and gives me a single command (or small set) to deploy my website.  I still want to deploy it from my local machine. But I want to limit my bespoke bash when I can.</p>
<h2 id="my-decisions">My Decisions</h2>
<p>I decided to go super simple with the image and bake everything, including the Caddyfile, into my image.  I&#39;m coordinating all the different repos I deploy from by making them submodules of my single deployment repo, this lets me deploy easily from my local machine still and has git handle the coordination between repos.</p>
<p>I now have a bespoke script for setting up a brand new Fedora machine to put the newest version of podman on it.  It&#39;s still bespoke, but I didn&#39;t have a script at all for that before.  My deploy script basically just copies files to the remote machine then runs a couple of systemctl and podman commands. Bespoke, technically, but also basically four lines of bash.</p>
<p>With an image ready to be deployed, how do I deploy it?  Docker is fine, but podman seems lighter weight.  I&#39;ve been looking at kubernetes for work, and I hear <a href="https://ergaster.org/posts/2025/07/09-kubernetes-black-friday/">K3s aren&#39;t just for Black Friday</a> but I just don&#39;t feel comfortable with that yet.  I know I need a supervisor to make sure my container keeps running.  I used systemd with my old configuration (basically without knowing it).</p>
<p>Podman has done some really cool stuff to bridge a container runtime with kubernetes.  They&#39;re where I learned what a Pod was and why use one. They can run kube yaml, although I&#39;ve never tried. And they have this thing called Quadlets that lets you run a declarative configuration under systemd.  Their unit files are declarative configuration, like a docker compose file or kube yaml, but in systemd&#39;s ini file syntax. Honestly, ini is so super simple to read and write that wasn&#39;t a problem. Podman should give me the easiest way to deploy!</p>
<h2 id="quadlets">Quadlets</h2>
<p>Like most of what I learn from Podman, I really misunderstand the concepts involved at first.  When I first learned about Pods, I expected them to behave like a docker compose file. I didn&#39;t realize all those containers shared a network namespace (plus more, of course).  When I first tried to use Quadlets, I didn&#39;t realize how much control podman ceded to systemd.  Once I run <code>podman quadlet install</code>, I&#39;m basically done with podman.  I think that command might even just copy the files to the right place and run <code>systemd daemon-reload</code>.  Now, I think podman is providing the generator to turn those Units into Service Units that systemd understands. But having podman is enough, so the <code>quadlet</code> subcommand feels like icing on the cake.</p>
<h2 id="the-trouble">The Trouble</h2>
<p>It&#39;s all rosy now that you&#39;re reading this from a quadlet I have deployed, but getting a version of podman with the right support is tough.  You need 5.6 to get the <code>quadlet</code> subcommand at all. And 5.7 to get the <code>--replace</code> flag on the <code>install</code> command, useful for making my deploy scripts simpler. Plus, I couldn&#39;t seem to run a container with 5.6 on CentOS Stream. And my local machine is Ubuntu and only has 4.9.3 which doesn&#39;t support quadlets at all!  Your options seem to be Arch or Fedora.  And Digital Ocean only supports Fedora officially.  Then you need to install the specific new version of podman from their updates repo because Fedora only ships with 5.6.2.... Trouble and misunderstandings aside, I learned some stuff and quadlets are a fantastic way to run containers.</p>
<h2 id="future-plans">Future Plans</h2>
<p>Socket Activation for my caddy container.  Caddy is already really lightweight, but it&#39;s a powerful seeming technique I&#39;d like to try.</p>
<p>Rolling deployments. Technically, I&#39;ve introduced downtime by restarting a container. With socket activation, systemd can hold the connections for me. And <code>podman auto-update</code> will manage starting the new container with a health check then rolling back to the old one if the health check fails.  This means I also need to introduce a health check and maybe better tag versions, but neither will be hard if I find the time.</p>
<p>Some dynamic content. Now I can put anything in a container. I could use multiple containers! The sky feels like the limit and I don&#39;t have to write a whole new set of scripts for every one. Just small updates to what I have.  If I add a site visitor counter, you&#39;ll know I&#39;ve done just that.  This was another motivator for me.</p>
<h2 id="why-not-kamal">Why Not Kamal</h2>
<p>I use Kamal for deplying things internally, I&#39;ve written a bunch about it here. But it uses docker and it has an extra kamal proxy container for rolling deployments. And it&#39;s a big local dependency. And it&#39;s defaults are very much geared towards a whole web application. It requires a lot more ssh&#39;ing into specific containers to debug.  It doesn&#39;t have anything like journald.</p>
<p>My biggest issue: Kamal requires a container registry. I&#39;m just not interested in setting one up or paying for one.  With podman, I can <code>podman save | ssh podman load</code> to copy my 80 MB image over.  It&#39;s not terribly efficient, but it&#39;s direct and doesn&#39;t require any additional infrastructure.  Otherwise, I&#39;ve taken a lot of inspiration from their interface in writing my little bash scripts. I think they&#39;re easy to read.</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Tue, 21 Apr 2026 00:00:00 GMT</pubDate>
      <title>Declarative Configuration, Imperative Interface</title>
      <link>https://benkenawell.com/p/declarative-configuration-imperative-interface.html</link>
      <guid>https://benkenawell.com/p/declarative-configuration-imperative-interface.html</guid>
      <description>&lt;p&gt;Programs that have a declarative interface for their configuration like &lt;a href=&quot;https://kubernetes.io/&quot;&gt;Kubernetes&lt;/a&gt;, Docker Compose, &lt;a href=&quot;https://systemd.io/&quot;&gt;systemd&lt;/a&gt;, and OpenTofu are all easier to read through and reason about than their counterparts. &lt;a href=&quot;https://kamal-deploy.org/&quot;&gt;Kamal&lt;/a&gt; takes a different approach &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Declarative Configuration, Imperative Interface</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Declarative Configuration, Imperative Interface</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2026-04-21T00:00:00+00:00">Apr 21st, 2026</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>Programs that have a declarative interface for their configuration like <a href="https://kubernetes.io/">Kubernetes</a>, Docker Compose, <a href="https://systemd.io/">systemd</a>, and OpenTofu are all easier to read through and reason about than their counterparts. <a href="https://kamal-deploy.org/">Kamal</a> takes a different approach <!--more--> and says commands should be obvious and imperative. Where is that line?</p>
<p>Kamal still has a config file where you declare how to build your container image, where to send it, where your hosts live, and any additional sidecar containers you want.  Then it gives you imperative commands to get them there. Once you have Kamal configured, you just call <code>kamal deploy</code> to get a new version of your app deployed.</p>
<p>I can&#39;t claim to be a Kubernetes expert (barely a novice), but once you have the configuration done, it seems to me that you just call <code>kubectl -f config.yaml apply</code> which is not as easy English as Kamal&#39;s deploy but doesn&#39;t it do the same thing?  Both get your production service to a new state don&#39;t they?  Kamal had to roll their own proxy to get rolling, zero-downtime releases, so now they&#39;re doing multi-container management. Kubernetes does that too, but has a lot of options and contributors for your use.</p>
<p>A big difference I see is that Kubernetes seems to guarantee an eventual consistency with the applied state through their operator pattern whereas Kamal has the new state up and running by the time the command finishes (or errors, and it rolls back).  And, of course, K8s are a beast to run. But <a href="https://k3s.io/">K3s</a> seem easier to run yourself.</p>
<p>Both of these systems are way better than a bunch of shell scripts. Maybe better than Ansible too, I don&#39;t have any experience there but that yaml scares me insofar as it reminds me of GitHub Actions.  In both, you can declare things about the state of your system in a configuration file then both give you a standard set of commands for interacting with what&#39;s been created with that configuration.  I&#39;m currently in the process of moving from shell script deployments for this static site to containerizing it and running it with <a href="https://docs.podman.io/en/latest/markdown/podman-quadlet.1.html">Podman Quadlets</a>.</p>
<p>Systemd gets a special mention here because I&#39;ve been playing with Podman Quadlets. I&#39;ve found their unit files and <code>systemctl</code> to work a very similar way. Declarative configuration with imperative commands. There are differences: INI syntax instead of yaml, containers running as processes, and it&#39;s much closer to the UNIX system it comes from overall. But you can declare dependencies and start/reload commands that systemd then knows how to run with its standard interface. The same way Kubernetes and Kamal seem to work.  It can do rolling releases and health checks. Has a decent structured logging interface out of the gate, and doesn&#39;t require the overhead of a Kubernetes cluster.</p>
<p>I&#39;m writing a follow up post about my experience setting up Quadlets. It&#39;s been a little bumpy, but I really like the place they fit in. Systemd can be controversial, but I think it&#39;s here to stay.  It&#39;s more standard than Kamal and less heavy than Kubernetes. The declarative nature of those <a href="https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html">Unit files</a> reminds me so much of Docker Compose (which I have plenty of experience in, especially comapred to kube yaml). And that declarative nature gives me something much easier to read and is more repeatable to deploy than the shell scripts I had been using. I could set up the system from scratch by uploading a Container Unit and image to any server running systemd and podman (5.8+). That setup still has some shell scripts for now, but it&#39;s a step in a direction I&#39;m very happy with (and I can still build it on my own computer!).</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate>
      <title>Migrating from Jekyll to WebOrigami</title>
      <link>https://benkenawell.com/p/migrating-from-jekyll-to-weborigami.html</link>
      <guid>https://benkenawell.com/p/migrating-from-jekyll-to-weborigami.html</guid>
      <description>&lt;p&gt;They have slightly different philosophies. &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Migrating from Jekyll to WebOrigami</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Migrating from Jekyll to WebOrigami</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2026-04-09T00:00:00+00:00">Apr 9th, 2026</time>
    <span class="p-author">Ben</span>
  </div>
</header>

<p>They have slightly different philosophies. <!--more--> <a href="https://weborigami.org/">WebOrgami</a> is much more direct, while <a href="https://jekyllrb.com/">Jekyll</a> has layers of configuration. WebOrigami is very attached to file extensions while Jekyll&#39;s are all wrong (markdown and liquid in html or md??)</p>
<p>What I&#39;ve ended with is spiritually something in between Jekyll and WebOrigami. But because WebOrigami is so flexible, it&#39;s all written in it!  I wasn&#39;t rely much on Jekyll, really, so this process took me a few hours over the course of a week or two.  These steps are ordered in the way I wish I would have done it.</p>
<p>First step, change all the filenames. I haven&#39;t found a better way than to just rename then on the system. Everything became .liquid so I could use a <a href="https://weborigami.org/language/filetypes#defining-a-handler-for-a-template-language">file loader</a> for it. </p>
<p>Second, adapt <a href="https://github.com/WebOrigami/extensions/blob/main/liquid/src/liquid_handler.js">Jan&#39;s Liquid handler</a>. I&#39;ve made it load much closer to what Jekyll expects, and return an object with the front matter too. My loader creates all context for the Liquid rendering, renders that, then gives me back the document with its front matter.  The document&#39;s body might be markdown or html, but the handler doesn&#39;t care.  For the markdown files, I manually call <code>Origami.mdHtml</code> later on.</p>
<p>Writing the Liquid Handler was probably the most difficult part for me.  I think the intention might be that a loader is able to turn an arbitrary file into a function that returns something you can post on the web.  I&#39;m not sure, but all the loaders I&#39;ve seen do that. I think mine still works well within WebOrigami&#39;s system because it returns a tree like many of the examples on their site. Jekyll builds a lot of context for you, I had to move that to the handler.  I definitely took some shortcuts. My blog was 3 layers deep at its thickest and I think I relied on a small subset of what Jekyll really provides.  All the special Jekyll tags I use needed to be added to the Liquid rendering engine, but I only used <code>post_url</code> .  Since I&#39;ve created my own post hierarchy now, I just interpolated it directly there, half a step from hard coded.  Returning a tree from the handler instead of text only was my biggest enabler.  Once I had the posts&#39; front matter available to me in my site.ori file, I could do many more interesting transformations on the data.</p>
<p>Including, moving metadata into front matter. This was part of making the blogging system my own. I don&#39;t like the naming convention required by Jekyll. Instead of recreating it in WebOrigami, I added title and date properties into the front matter. I didn&#39;t taken the time to make the URLs match, sorry about that lapse of judgement.  Since I made the file handler return the front matter to me, I have all the metadata I want, and WebOrigami makes the transformations easy.  I&#39;m not sure if I&#39;ll use <a href="https://obsidian.md/">Obsidian</a> or <a href="https://ergaster.org/til/sveltia-cms-astro/">Sveltia</a> to write these eventually, but the front matter should be easy to add in both.</p>
<p>Lastly, porting all the other Jekyll extensions and add ons.  I really only relied on the feed_xml for an RSS feed.  WebOrigami uses a JSON Feed to RSS converter and has a very nice tutorial on how to create a JSON Feed. Now I have both!</p>
<h2 id="future-plans">Future Plans</h2>
<p>I can do so much more with this now. I never learned Jekyll&#39;s tagging system, I didn&#39;t take the time when I had so few blog posts and committed to only blogging on tech subjects. But now I can build that myself, and it can be as lightweight as I want. With WebOrigami easily traversing the file system and yaml and any sort of tree, I don&#39;t expect it will be that hard.</p>
<p>I&#39;ve changed the file names on my posts, which is a decision I made thinking not too many people actually come to my site.  Eventually, they will probably change again. I&#39;d like to allow them to be organized by year and maybe month.</p>
<p><a href="https://git.sr.ht/~benjk/benjk.srht.site">This</a> is what it looks like, with a <a href="https://git.sr.ht/~benjk/benjk.srht.site/tree/stable/item/src/site.ori">single .ori file</a>. Two if you count the <a href="https://git.sr.ht/~benjk/benjk.srht.site/tree/stable/item/config.ori">configuration</a>.</p>
<p>Overall, I&#39;ve had a fun experience with WebOrigami. It&#39;s being actively worked on and you can read all the comics to learn more about it.  They have a great community to chat with. The Tree mechanism is really an incredible one!</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Wed, 04 Mar 2026 00:00:00 GMT</pubDate>
      <title>Tailnet Zero-Downtime Deployments</title>
      <link>https://benkenawell.com/p/2026-03-04-kamal-and-tailscale.html</link>
      <guid>https://benkenawell.com/p/2026-03-04-kamal-and-tailscale.html</guid>
      <description>&lt;p&gt;&lt;a href=&quot;https://tailscale.com/&quot;&gt;Tailscale&lt;/a&gt; is amazing for enabling private mesh network overlays. &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Tailnet Zero-Downtime Deployments</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Tailnet Zero-Downtime Deployments</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2026-03-04T00:00:00+00:00">Mar 4th, 2026</time>
    <span class="p-author">ben</span>
  </div>
</header>


<p><a href="https://tailscale.com/">Tailscale</a> is amazing for enabling private mesh network overlays. <!--more--> For connecting your phone to a server you having running at home, easily and securely.  If you&#39;re building things you want to deploy on your tailnet, what&#39;s the best way to do that?</p>
<p>I want everything to run on my own hardware, minimizing my costs and letting me use what I already have.  Tailscale lets me talk with my computers easily. <a href="https://kamal-deploy.org/">Kamal</a> lets me deploy from my local machine with ease and zero downtime (my wife will never know!).  Tailscale&#39;s <a href="https://tailscale.com/docs/features/tailscale-serve">Serve</a> feature work great with Kamal Proxy&#39;s accessory setup.</p>
<p>Kamal has one onerous requirement, it requires a container registry.  This registry has to be available from your laptop and server.  If you use an external service, like GitHub&#39;s Artifact Repository or Digital Ocean&#39;s container registry, then you should be good to go.  If you, like me, want to also run your own container registry on your tailnet, I wrote <a href="/p/2026-03-02-kamal-and-zot.html">another article</a> about that.</p>
<h2 id="how-it-works">How it works</h2>
<p>Using a ephermeral tailscale auth key, Tailscale will route traffic on our network to the accessory tailscale container that Kamal deploys for us.  We use docker&#39;s dns routing to point Tailscale&#39;s Serve feature to the kamal-proxy container that Kamal deploys.  Kamal&#39;s proxy will check the hostname for us then route to the application container for our app.  Because we&#39;re deploying behind kamal-proxy, Kamal will handle zero downtime deploys for us, no configuration needed!</p>
<h2 id="limitations">Limitations</h2>
<p>Kamal proxy routes via the hostname for you, so you can&#39;t access these services via the IP address.</p>
<h2 id="deploy-more-services">Deploy more services</h2>
<p>Kamal Proxy can handle multiple apps deployed to the same server. We register a different tailscale sidecar for each one to make our deployments simpler and each service can have it&#39;s own identifier in our Tailscale Admin Dashboard. Requests to both services will run through the same kamal proxy container, who will route it properly via hostname.</p>
<h2 id="appendix-a-configuration">Appendix A: Configuration</h2>
<pre><code class="hljs language-yaml"><span class="hljs-comment"># excerpts from my deploy.yml for Kamal</span>

<span class="hljs-comment"># my server is on tailscale as well</span>
<span class="hljs-attr">servers:</span>
  <span class="hljs-attr">web:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">&lt;server</span> <span class="hljs-string">name&gt;.&lt;tailnet</span> <span class="hljs-string">name&gt;.ts.net</span>

<span class="hljs-comment"># To connect with Zot on my tailnet</span>
<span class="hljs-attr">build:</span>
  <span class="hljs-attr">driver:</span> <span class="hljs-string">docker</span>

<span class="hljs-comment"># tell the proxy my tailscale hostname</span>
<span class="hljs-attr">proxy:</span>
  <span class="hljs-comment"># tailscale terminates the ssl connection</span>
  <span class="hljs-attr">ssl:</span> <span class="hljs-literal">false</span>
  <span class="hljs-comment"># same hostname as the tailscale sidecar</span>
  <span class="hljs-attr">host:</span> <span class="hljs-string">&lt;service</span> <span class="hljs-string">name&gt;.&lt;tailnet</span> <span class="hljs-string">name&gt;.ts.net</span>
  <span class="hljs-comment"># rails port number</span>
  <span class="hljs-attr">app_port:</span> <span class="hljs-number">3000</span>
  <span class="hljs-comment"># rails takes a long time to boot on my little server...</span>
  <span class="hljs-attr">healthcheck:</span>
    <span class="hljs-attr">path:</span> <span class="hljs-string">/up</span>
    <span class="hljs-attr">interval:</span> <span class="hljs-number">5</span>
    <span class="hljs-attr">timeout:</span> <span class="hljs-number">600</span>
  <span class="hljs-attr">run:</span>
    <span class="hljs-comment"># don&#x27;t publish ports to the host machine,</span>
    <span class="hljs-comment"># all the traffic will come through the docker network</span>
    <span class="hljs-comment"># via the tailscale accessory</span>
    <span class="hljs-attr">publish:</span> <span class="hljs-literal">false</span>

<span class="hljs-attr">accessories:</span>
  <span class="hljs-attr">sidecar:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">ghcr.io/tailscale/tailscale:stable</span>
    <span class="hljs-comment"># for kamal, the host to run this sidecar on, same as servers.web configuration</span>
    <span class="hljs-attr">host:</span> <span class="hljs-string">&lt;server</span> <span class="hljs-string">name&gt;.&lt;tailnet</span> <span class="hljs-string">name&gt;.ts.net</span>
    <span class="hljs-attr">files:</span>
      <span class="hljs-comment"># I don&#x27;t think directories works the way I way, so</span>
      <span class="hljs-comment"># copy and mount the file directly.</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">config/tailscale/ts.json:/config/ts.json</span>
    <span class="hljs-attr">env:</span>
      <span class="hljs-attr">clear:</span>
        <span class="hljs-attr">TS_USERSPACE:</span> <span class="hljs-literal">false</span>
        <span class="hljs-attr">TS_STATE_DIR:</span> <span class="hljs-string">/var/lib/tailscale</span>
        <span class="hljs-attr">TS_SERVE_CONFIG:</span> <span class="hljs-string">/config/ts.json</span>
        <span class="hljs-attr">TS_EXTRA_ARGS:</span> <span class="hljs-string">--advertise-tags=tag:container</span>
      <span class="hljs-attr">secret:</span>
        <span class="hljs-comment"># loaded with .kamal/secrets</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">TS_AUTHKEY</span>
    <span class="hljs-attr">options:</span>
      <span class="hljs-comment"># must be the same as the proxy&#x27;s service name so tailscale and kamal proxy agree</span>
      <span class="hljs-attr">hostname:</span> <span class="hljs-string">&lt;service</span> <span class="hljs-string">name&gt;</span>
      <span class="hljs-attr">cap-add:</span> <span class="hljs-string">NET_ADMIN</span>
      <span class="hljs-attr">device:</span> <span class="hljs-string">/dev/net/tun:/dev/net/tun</span>
      <span class="hljs-comment"># derive the volume from the service name so we can have multiple tailscale sidecars on the same server</span>
      <span class="hljs-attr">volume:</span> <span class="hljs-string">&lt;service</span> <span class="hljs-string">name&gt;-tailscale-state:/var/lib/tailscale</span>
</code></pre></main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Mon, 02 Mar 2026 00:00:00 GMT</pubDate>
      <title>Container Registry for Kamal, Secured with Tailscale</title>
      <link>https://benkenawell.com/p/2026-03-02-kamal-and-zot.html</link>
      <guid>https://benkenawell.com/p/2026-03-02-kamal-and-zot.html</guid>
      <description>&lt;p&gt;I think this is a niche confluence of technologies.  &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Container Registry for Kamal, Secured with Tailscale</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Container Registry for Kamal, Secured with Tailscale</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2026-03-02T00:00:00+00:00">Mar 2nd, 2026</time>
    <span class="p-author">ben</span>
  </div>
</header>


<p>I think this is a niche confluence of technologies.  <!--more-->  Hopefully my experience can help someone else who&#39;s trying to deploy services with <a href="https://kamal-deploy.org/">Kamal</a>, all while behind a <a href="https://tailscale.com/">Tailnet</a>.  This enables you to use your own hardware, secure in your Tailnet, to deploy easily and zero downtime with Kamal.</p>
<p>Since I&#39;m only deploying personal web apps/containers with this setup, I tend to think of it as pretty ephemeral, but if you take the time with this (and maybe have a mirror), Zot is capable of being a complete container registry for docker images and any <a href="https://oras.land/">OCI Artifacts</a>.</p>
<h2 id="setting-up-zot">Setting up Zot</h2>
<p><a href="https://zotregistry.dev/v2.1.14/">Zot</a> has pretty good docs, and it&#39;s pretty simple to setup too.  We&#39;re going to put it behind Tailscale, so if we&#39;re a little lenient with some of the security options we should still be safe.  Some of this is <em>not best practices</em>.  At best, these are okay for a homelab setup when <em>nothing is exposed to the internet</em>.</p>
<p>In <a href="#appendix-a-zot-configuration-files">Appendix A</a>, I have a bunch of configuration files to help get you started.  Run the compose on any docker instance you want to use as your registry server. The server doesn&#39;t need to be on your tailnet because we&#39;ll put the registry on it directly.</p>
<p>Kamal requires a password be set for a registry it accesses through HTTPS, so you will need to <a href="https://zotregistry.dev/v2.1.14/articles/authn-authz/#htpasswd">setup a password</a> to use in Zot.</p>
<h2 id="configure-kamal">Configure Kamal</h2>
<p>You only need to set up Zot once, but Kamal you&#39;ll configure for every project you want to deploy.  I wrote <a href="/p/2026-03-04-kamal-and-tailscale.html">another article</a> that goes into more detail about the Kamal configuration for deploying on a Tailnet.</p>
<p>In Kamal&#39;s deploy.yml file, <a href="https://kamal-deploy.org/docs/configuration/builders/">the <code>builder</code> section</a> <strong>must</strong> contain &quot;driver: docker&quot;.  The <a href="https://docs.docker.com/build/builders/drivers/">docker driver</a> has access to your machine&#39;s DNS configuration, but Kamal&#39;s default &quot;docker-container&quot; does not.  I think this might change eventually, but I&#39;m not sure. If you&#39;re using a registry that doesn&#39;t need Tailscale&#39;s DNS to work, don&#39;t worry about this setting.</p>
<p>I haven&#39;t used a remote builder. I imagine it will need access to your Tailnet as well, to upload the built image.  I think the docker driver limits the architectures we can build an image for, so remote might be important if your laptop and server have different architectures.</p>
<h2 id="appendix-a-zot-configuration-files">Appendix A: Zot Configuration Files</h2>
<pre><code class="hljs language-yaml"><span class="hljs-comment"># example compose.yml with tailscale</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">registry</span>
<span class="hljs-attr">volumes:</span>
  <span class="hljs-attr">registry-tailscale-state:</span>
  <span class="hljs-attr">zot-data:</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">sidecar:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">ghcr.io/tailscale/tailscale:stable</span>
    <span class="hljs-attr">hostname:</span> <span class="hljs-string">registry</span>
    <span class="hljs-attr">cap_add:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">NET_ADMIN</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">registry-tailscale-state:/var/lib/tailscale</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/path/to/configuration/directory:/config</span> <span class="hljs-comment"># holds the ts.json, below</span>
    <span class="hljs-attr">devices:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/dev/net/tun:/dev/net/tun</span>
    <span class="hljs-attr">env_file:</span> <span class="hljs-string">stack.env</span> <span class="hljs-comment"># put all the tailscale environment variables here</span>
  <span class="hljs-attr">app:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">ghcr.io/project-zot/zot:latest</span>
    <span class="hljs-attr">depends_on:</span> 
      <span class="hljs-bullet">-</span> <span class="hljs-string">sidecar</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-comment"># zot configuration</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/share/Docker/stack-configs/ocireg/config.yaml:/etc/zot/config.yaml</span>
      <span class="hljs-comment"># users and passwords</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/share/Docker/stack-configs/ocireg/htpasswd:/etc/zot/htpasswd</span>
      <span class="hljs-comment"># where our images will be stored</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">zot-data:/data/zot</span>
    <span class="hljs-attr">command:</span> <span class="hljs-string">serve</span> <span class="hljs-string">/etc/zot/config.yaml</span>
</code></pre><p>Example ts.json, <a href="https://tailscale.com/docs/features/tailscale-serve">Serve Functionality</a></p>
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
  <span class="hljs-attr">&quot;TCP&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
    <span class="hljs-attr">&quot;443&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
      <span class="hljs-attr">&quot;HTTPS&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span>
    <span class="hljs-punctuation">}</span>
  <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">&quot;Web&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
    <span class="hljs-attr">&quot;${TS_CERT_DOMAIN}:443&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
      <span class="hljs-attr">&quot;Handlers&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
        <span class="hljs-attr">&quot;/&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
          <span class="hljs-attr">&quot;Proxy&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;http://app:8080&quot;</span>
        <span class="hljs-punctuation">}</span>
      <span class="hljs-punctuation">}</span>
    <span class="hljs-punctuation">}</span>
  <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">&quot;AllowFunnel&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
    <span class="hljs-attr">&quot;${TS_CERT_DOMAIN}:443&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">false</span></span>
  <span class="hljs-punctuation">}</span>
<span class="hljs-punctuation">}</span> 
</code></pre><pre><code class="hljs language-yaml"><span class="hljs-comment"># example Zot configuration, config.yaml</span>
<span class="hljs-attr">distSpecVersion:</span> <span class="hljs-number">1.0</span><span class="hljs-number">.1</span>
<span class="hljs-attr">storage:</span>
  <span class="hljs-attr">rootDirectory:</span> <span class="hljs-string">/data/zot</span>
<span class="hljs-attr">http:</span>
  <span class="hljs-attr">address:</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span>
  <span class="hljs-attr">port:</span> <span class="hljs-number">8080</span>
  <span class="hljs-attr">auth:</span>
    <span class="hljs-attr">htpasswd:</span>
      <span class="hljs-attr">path:</span> <span class="hljs-string">&quot;/etc/zot/htpasswd&quot;</span>
  <span class="hljs-comment"># accessControl:</span>
  <span class="hljs-comment">#   repositories:</span>
  <span class="hljs-comment">#     **:</span>
  <span class="hljs-comment">#       defaultPolicy:</span>
  <span class="hljs-comment">#         - read</span>
  <span class="hljs-comment">#         - create</span>
  <span class="hljs-comment">#         - update</span>
  <span class="hljs-comment">#         - delete</span>
<span class="hljs-attr">extensions:</span>
  <span class="hljs-attr">ui:</span>
    <span class="hljs-attr">enable:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">search:</span>
    <span class="hljs-attr">enable:</span> <span class="hljs-literal">true</span>
</code></pre></main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Thu, 19 Feb 2026 00:00:00 GMT</pubDate>
      <title>What Bash Does Better</title>
      <link>https://benkenawell.com/p/2026-02-19-what-bash-does-better.html</link>
      <guid>https://benkenawell.com/p/2026-02-19-what-bash-does-better.html</guid>
      <description>&lt;p&gt;Bash is eschewed online for being hard to read and write. &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - What Bash Does Better</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">What Bash Does Better</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2026-02-19T00:00:00+00:00">Feb 19th, 2026</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>Bash is eschewed online for being hard to read and write. <!--more--> Depending on who you listen to, you should reach for something else anytime you need something longer than what fits in your terminal.  But I&#39;ve been having a great time writing bash scripts.  They are at least twice concise as my boss&#39;s equivalent javascript scripts, measuring objectively on lines of code and subjectively on how many lines I have to read to understand what a piece does.</p>
<p>There are some niceties that I like to set up for myself.  For example, the <code>die</code> function gives me an easy way to write checks</p>
<pre><code class="hljs language-bash"><span class="hljs-function"><span class="hljs-title">die</span></span>() {
  <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;<span class="hljs-variable">$@</span>&quot;</span> &gt;&amp;2
  <span class="hljs-built_in">exit</span> 1
}

[[ <span class="hljs-string">&quot;<span class="hljs-subst">$(git rev-parse --is-in-work-tree 2&gt;/dev/null)</span>&quot;</span> == <span class="hljs-string">&quot;true&quot;</span> ]] || die <span class="hljs-string">&quot;Not in git repo&quot;</span>
</code></pre><p>I often need to <a href="https://devhints.io/bash">look up</a> the string manipulations, but it&#39;s <em>so simple</em> to read them.</p>
<pre><code class="hljs language-bash">VERBOSE=<span class="hljs-string">&quot;<span class="hljs-variable">${VERBOSE:-false}</span>&quot;</span> <span class="hljs-comment"># set VERBOSE to false if it is unset or null.</span>
</code></pre><p>Bash&#39;s real super power over a traditional language is the <em>input and output flexibility</em>.  Its control flow is second-to-none.  The mechanism is super, super simple, strings all around really. But streams are the default, and they are <strong>powerful</strong>.</p>
<pre><code class="hljs language-bash"><span class="hljs-keyword">while</span> <span class="hljs-built_in">read</span> -r line; <span class="hljs-keyword">do</span>
 <span class="hljs-comment"># run a command every time a notification is sent.</span>
<span class="hljs-keyword">done</span> &lt; &lt;(curl -sN https://ntfy.sh/test/json)
</code></pre><p>The thing on the last line there looks a little weird, doesn&#39;t it? <code>&lt; &lt;(command)</code> is another super power of bash. How commands can be called.  You can treat a file as a string, a string as a file, open a named pipe on your system for inter process communication, inline a script in another language, and more! Bash takes it all in stride.  The only thing I&#39;ve seen recently that comes close is <a href="https://weborigami.org/">WebOrigami</a>.</p>
<pre><code class="hljs language-bash">A=3 B=3 node -e <span class="hljs-string">&quot;console.log(Number(process.env.A) + Number(process.env.B))&quot;</span> <span class="hljs-comment"># 6</span>
<span class="hljs-comment"># heredoc</span>
<span class="hljs-built_in">cat</span> &lt;&lt;<span class="hljs-string">EOF
This is a long
 inline string with
 enters, spaces and everything!
EOF</span>

<span class="hljs-comment"># herestring</span>
json=<span class="hljs-string">&#x27;{&quot;this&quot;: &quot;Test&quot;, &quot;hello&quot;: [&quot;sun&quot;, &quot;moon&quot;, &quot;world&quot;]}&#x27;</span>
hello=<span class="hljs-string">&quot;<span class="hljs-subst">$(jq -n &#x27;.hello[2]&#x27; &lt;&lt;&lt;<span class="hljs-string">&quot;<span class="hljs-variable">$line</span>&quot;</span>)</span>&quot;</span>
</code></pre><p>I can even control the process forking of my command with ease. <code>()</code> for a subshell, <code>$()</code> for a subshell, piping stdout bach to me. <code>&lt;()</code> for a subshell, giving me back a file descriptor for it. Functions run in my process automatically, but I can run them in a subshell if I want. Commands may run in a subshell automatically, but I other scripts I can <code>source</code> and run in my process instead.</p>
<p>Lastly, its simple I/O mechanisms are available in <em>every language</em> and it can call <em>any executable on your system with ease, by name</em>.  I consider <a href="https://jqlang.org/">jq</a> part of my basic toolset nowadays.  And <a href="https://github.com/charmbracelet/gum">gum</a> a close second.  They&#39;re installed on my system, I don&#39;t need to import them every time. I wrote a <a href="https://www.npmjs.com/package/@benkenawell/parseargs">parseargs package</a> because I didn&#39;t like the current options and know Node well, now I can call that easily as well! Eventually I&#39;ll rewrite it in Zig to be faster, but my scripts won&#39;t need to change at all.</p>
<p>The importing is a double edged sword, I know. It&#39;s a reason to limit the amount of dependencies I use, luckily bash has a lot of good already in it!  But some dependencies are worth convincing other people to use.  I trust my taste here and so do my colleagues, but sometimes I will write helpers that don&#39;t require others to have packages installed that I do.</p>
<pre><code class="hljs language-bash"><span class="hljs-function"><span class="hljs-title">log</span></span>() {
  <span class="hljs-keyword">if</span> <span class="hljs-built_in">command</span> -v gum &amp;&gt;/dev/null; <span class="hljs-keyword">then</span>
    gum <span class="hljs-built_in">log</span> --structured --level info <span class="hljs-string">&quot;<span class="hljs-variable">$@</span>&quot;</span>
  <span class="hljs-keyword">else</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">&quot;<span class="hljs-variable">$@</span>&quot;</span>
  <span class="hljs-keyword">fi</span>
}
</code></pre><p>There&#39;s some syntax to learn with bash. It&#39;s probably somewhere between Ruby and Perl in that.  There&#39;s no one place to go and learn about it.  You probably only use it for quick, one off scripts and don&#39;t fire your whole engineering brain making it robust.  But read enough tips and tricks, try new things each time, and I think you&#39;ll find yourself reaching for it more and more often given that you can find it almost everywhere and it is amazingly expressive.</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Thu, 12 Feb 2026 00:00:00 GMT</pubDate>
      <title>Don&apos;t Underestimate a CTE</title>
      <link>https://benkenawell.com/p/2026-02-12-dont-underestimate-a-cte.html</link>
      <guid>https://benkenawell.com/p/2026-02-12-dont-underestimate-a-cte.html</guid>
      <description>&lt;p&gt;CTEs are a powerful tool for making your SQL more legible. &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Don't Underestimate a CTE</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Don't Underestimate a CTE</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2026-02-12T00:00:00+00:00">Feb 12th, 2026</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>CTEs are a powerful tool for making your SQL more legible. <!--more--> They also help me model my data and iterate quickly before materializing the final shape of a table, without mucking about modifying a table shape. </p>
<h2 id="the-problem">The Problem</h2>
<p>My wife wants to know when she&#39;ll work a certain number of hours so she can take an exam. There&#39;s a projection forward, but I also want to take into account days/hours she&#39;s already worked and days that are out of the norm.</p>
<h2 id="the-start">The Start</h2>
<p>I could make one table with entries for all her hours worked, but then I need to track in that table which are a projection and which are &quot;real&quot;. Thinking myself clever, I decided the distinction warranted two tables. One for simple entries of hours worked and one to hold all the projections. </p>
<p>How do I fill in all those projection dates? I came up with a simple algorithm that basically takes all the week days and gives her 8 hours a day. I used a CTE to come up with this, then I was going to insert them into projections and go from there.</p>
<h2 id="the-iteration">The Iteration</h2>
<p>I made a <em>table expression</em> to insert data into a real table. But why not just use that expression in my calculation directly?  If I have a algorithmic projection, what parameters could I change that would cause the calculation to update? Those parameters are what I want to store in a table!</p>
<p>If she&#39;s works part time, I can tune that parameter directly instead of trying to reconcile what&#39;s in the table with the new data I want. The SQL CTE will just recalculate it for me.</p>
<p>CTEs are super nice to iterate quickly, since there&#39;s nothing stored to clean up. DBeaver has a nice concept of variables I used to help me decide what parameters should be part of my configuration.  What I put in the DBeaver variables become columns in the table directly.</p>
<h2 id="the-solution-and-next-steps">The Solution and Next Steps</h2>
<p>Now that I have a more stable idea of what I want, the next step is materializing it in the database. A View for my projection and a Table for my configuration. Both make the query a little more complicated, since now I have to get the configuration from a new table. But both will help make my final calculation, when will my wife hit her testing hours, easier. And we can tweak parameters until we&#39;ve run through all the scenarios we want. Even generating a little rails app where she could tweak the parameters herself without wading through SQL code shouldn&#39;t be too hard. If I need to change the projection, I can change the view. If we need more parameters, I can tweak the table.</p>
<p>It wasn&#39;t my initial design, but CTEs helped me find my way to a more powerful solution.</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Fri, 07 Nov 2025 00:00:00 GMT</pubDate>
      <title>A List of DSLs for a Website</title>
      <link>https://benkenawell.com/p/2025-11-07-a-list-of-dsls-for-a-website.html</link>
      <guid>https://benkenawell.com/p/2025-11-07-a-list-of-dsls-for-a-website.html</guid>
      <description>&lt;p&gt;The most basic form of a website needs ~4 or 5 languages. &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - A List of DSLs for a Website</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">A List of DSLs for a Website</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2025-11-07T00:00:00+00:00">Nov 7th, 2025</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>The most basic form of a website needs ~4 or 5 languages. <!--more--></p>
<blockquote>
<p>written after reading <a href="https://unplannedobsolescence.com/blog/what-dynamic-typing-is-for/">this article</a></p>
</blockquote>
<ol>
<li>HTML</li>
<li>CSS</li>
<li>Javascript</li>
<li>SQL</li>
<li>backend language</li>
</ol>
<p>3 are required to use the browser most effectively. 1 is required for solid data management. The last one is the only one we <em>really</em> get to choose.  Even if we chose Javascript, I could agree it&#39;s 5 languages: Node and other runtimes are <em>very close</em> but ultimately different environments for the same language and they should be treated differently.</p>
<p>In a production Node application, with a React frontend, we could easily double that count.</p>
<ol>
<li>HTML</li>
<li>JSX</li>
<li>CSS</li>
<li>Panda CSS</li>
<li>React</li>
<li>Javascript/Typescript/Node</li>
<li>SQL</li>
<li>Prisma</li>
<li>Lua</li>
</ol>
<p>9 languages! 6 or 7 DSLs! Some are over top of others, but you write better JSX if you understand HTML better.  Same for Panda and CSS, Prisma and SQL.  For every DSL layer on top of a DSL doesn&#39;t abstract it away as much as you&#39;d think.</p>
<h2 id="what-makes-a-dsl">What makes a DSL?</h2>
<p>Would <em>any</em> templating language be considered a DSL?  Something like Liquid or Nunjucks?  They&#39;re such simpler abstractions than JSX/React, you really get the semantics of the underlying DSL much, much more clearly.  There&#39;s hardly even another layer to think about.  Ultimately they do require a little more knowledge, but they also don&#39;t really know anything about the HTML layer beneath them.  They could be used to template Javascript or SQL the exact same way.</p>
<p>More advanced templaters like ERB (with Rails) or Laravel&#39;s Blade definitely feel like they spill over into DSL land.  They start to feel more aware of the langauge they&#39;re abstracting.  Since they ultimately render down to HTML though, they&#39;re still better at separating what they do vs the templated code than a React/JSX that tries to mix the two and bring a runtime into the mix.</p>
<h2 id="do-we-gain-enough-through-the-abstraction-the-additional-dsl-to-justify-it">Do we gain enough through the abstraction, the additional DSL, to justify it?</h2>
<p>Each person and group can decide for themselves and has a different tolerance for then.  10-15 years ago, React, SASS, etc. smoothed a lot of rough edges on the DSLs that make up web pages.  But the platform has come a long way since then.  For me, I prefer working with the DSLs directly and dropping into the full Javascript language when needed over just smoothing edges that aren&#39;t rough or pointy anymore.</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Sun, 20 Jul 2025 00:00:00 GMT</pubDate>
      <title>A Weekend With Caddy</title>
      <link>https://benkenawell.com/p/2025-07-20-a-weekend-with-caddy.html</link>
      <guid>https://benkenawell.com/p/2025-07-20-a-weekend-with-caddy.html</guid>
      <description>&lt;p&gt;I&amp;#39;ve been dabbling with &lt;a href=&quot;https://caddyserver.com&quot;&gt;Caddy&lt;/a&gt; web server.&lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - A Weekend With Caddy</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">A Weekend With Caddy</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2025-07-20T00:00:00+00:00">Jul 20th, 2025</time>
    <span class="p-author">Ben</span>
  </div>
</header>

<p>I&#39;ve been dabbling with <a href="https://caddyserver.com">Caddy</a> web server.<!--more--> It&#39;s the easiest reverse proxy I&#39;ve found for self signed development certs.  I set up a Caddy server to run my personal blog, a static website.  This weekend, I took it a step further and used some caddy templating to make a mostly static site dynamic.  The outcome?  Every single one of these projects has felt like a magical experience.  In this post, I focus on this past weekend when I used some caddy templating to make a mostly static site dynamic.</p>
<p>I host a countdown timer for various friends and family&#39;s weddings.  Previously, it was an entirely static site I made for my own wedding, hosted on Github Pages.  Then I changed it for the next wedding.  But then I couldn&#39;t see how long I had been married for!</p>
<p>I needed the barest amount of dynamic content on my page.  I didn&#39;t want to ship everybody&#39;s wedding dates to the client, so it needed to happen server side.  I make web apps (React, et al) for a living but that was just way too much (effort, code, time, etc) for what should be a simple problem, one interpolated date!</p>
<p>I had been testing Caddy to host my personal blog, a static website.  It&#39;s been performing phenomenally, I&#39;m able to deploy faster to it (via rsync) than to SourceHut Pages most of the time.  I had recently come across the <a href="https://caddyserver.com/docs/modules/http.handlers.templates#docs">Caddy templates module</a>.  For my blog, I was resistant to becoming too dependent on a feature like that, in case I needed to switch servers or something.  But the concept was the exact right amount of complexity for my wedding countdown timer project.</p>
<p>It took me a few hours to adapt my static site: loading a json file server side then interpolating the date into my timer-element web component and deploying the changes on my public facing web server (adapting the Caddyfile I had there to host two sites at the same time!).  But the end result?  I&#39;m very impressed!  I was able to add a bunch more weddings to my site by adding lines to the json file.  Everyone I texted about it was happy to see the counters to their weddings!  It will last as long as Caddy does, I didn&#39;t need to introduce or maintain any other moving parts.</p>
<p>For the future, I&#39;m exploring templating via a <a href="https://en.wikipedia.org/wiki/Common_Gateway_Interface">CGI script</a>.  It&#39;s slightly more work, but much more server agnostic.  Luckily, caddy has a third party module to support CGI and an amazing build tool, xcaddy, to produce supporting binaries.  We&#39;ll see where my ideas take me, but I know I&#39;ll be using Caddy for a lot in the future! Thank you <a href="https://github.com/sponsors/mholt">Matt Holt</a> for the amazing project!</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Thu, 10 Jul 2025 00:00:00 GMT</pubDate>
      <title>Tailscale Sidecar Run Deep Dive</title>
      <link>https://benkenawell.com/p/2025-07-10-tailscale-sidecar-run-deep-dive.html</link>
      <guid>https://benkenawell.com/p/2025-07-10-tailscale-sidecar-run-deep-dive.html</guid>
      <description>&lt;p&gt;From my other post &lt;a href=&quot;&quot;&gt;&lt;/a&gt;, we have a tailscale container running alongside another container.  It&amp;#39;s not a long file, but there is a lot of configuration going on. In this post, I&amp;#39;m going to step through the &lt;code&gt;run&lt;/code&gt; script line by line. &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Tailscale Sidecar Run Deep Dive</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Tailscale Sidecar Run Deep Dive</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2025-07-10T00:00:00+00:00">Jul 10th, 2025</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>From my other post <a href=""></a>, we have a tailscale container running alongside another container.  It&#39;s not a long file, but there is a lot of configuration going on. In this post, I&#39;m going to step through the <code>run</code> script line by line. <!--more--></p>
<h2 id="full-file">Full File</h2>
<p>First, the full file for reference</p>
<pre><code class="hljs language-bash"><span class="hljs-meta">#!/usr/bin/env bash</span>

<span class="hljs-comment"># load our environment variables</span>
<span class="hljs-built_in">source</span> .<span class="hljs-built_in">env</span>

<span class="hljs-comment"># run our sidecar container</span>
podman run --detach \
  --hostname <span class="hljs-string">&quot;<span class="hljs-variable">$HOSTNAME</span>&quot;</span> \
  --name <span class="hljs-string">&quot;<span class="hljs-variable">$SIDECAR_NAME</span>&quot;</span>\
  --cap-add net_admin \
  --device /dev/net/tun:/dev/net/tun \
  --volume freshrss_tailscale_state:/var/lib/tailscale \
  --env-file .ts.env \
  ghcr.io/tailscale/tailscale:latest

<span class="hljs-comment"># run our FreshRSS service</span>
podman run --detach \
  --restart unless-stopped \
  --log-opt max-size=10m \
  --network container:<span class="hljs-string">&quot;<span class="hljs-variable">$SIDECAR_NAME</span>&quot;</span> \
  -e TZ=America/New_York \
  -e <span class="hljs-string">&#x27;CRON_MIN=1,31&#x27;</span> \
  -v freshrss_data:/var/www/FreshRSS/data \
  -v freshrss_extensions:/var/www/FreshRSS/extensions \
  --name <span class="hljs-string">&quot;<span class="hljs-variable">$CONTAINER_NAME</span>&quot;</span> \
  docker.io/freshrss/freshrss
</code></pre><h2 id="line-by-line">Line by Line</h2>
<h3 id="bash-setup">Bash setup</h3>
<p>The first few lines are just bash setup.</p>
<p><code>#!/usr/bin/env bash</code>
: run this script through the bash interpreter.  After making the script executable[^chmod], this tells our computer how to run it.</p>
<p><code>source .env</code>
: We set up a .env file with bash variables[^variables] inside. The <code>source</code> command loads those variables into our current process.  We could put any bash in there, but by convention we&#39;re only setting variables.</p>
<h3 id="tailscale-sidecar-container">Tailscale Sidecar Container</h3>
<pre><code class="hljs language-bash">podman run --detach \
  --hostname <span class="hljs-string">&quot;<span class="hljs-variable">$HOSTNAME</span>&quot;</span> \
  --name <span class="hljs-string">&quot;<span class="hljs-variable">$SIDECAR_NAME</span>&quot;</span>\
  --cap-add net_admin \
  --device /dev/net/tun:/dev/net/tun \
  --volume freshrss_tailscale_state:/var/lib/tailscale \
  --env-file .ts.env \
  ghcr.io/tailscale/tailscale:latest
</code></pre><p><code>podman run --detach</code>
: tell our container engine, podman, to run a container detached so we aren&#39;t connected to it&#39;s stdout</p>
<p><code>--hostname &quot;$HOSTNAME&quot;</code>
: set the <a href="https://docs.podman.io/en/latest/markdown/podman-run.1.html#hostname-h-name">hostname</a> for the container. Tailscale uses this as the machine name. We&#39;re using the HOSTNAME variable we set in our .env, which makes it easy to share between scripts</p>
<p><code>--name &quot;$SIDECAR_NAME&quot;</code>
: give the <a href="https://docs.podman.io/en/latest/markdown/podman-run.1.html#name-name">container a name</a>. We reference this in our service container, so we&#39;ve made it a variable to ensure it has the same value both places.</p>
<p><code>--cap-add net_admin</code>
: <a href="https://docs.podman.io/en/latest/markdown/podman-run.1.html#cap-add-capability">add the net_admin</a> capability to the linux container. This gives the container some priviledges. Tailscale needs it to do some of its networking.</p>
<p><code>--device /dev/net/tun:/dev/net/tun</code>
: <a href="https://docs.podman.io/en/latest/markdown/podman-run.1.html#device-host-device-container-device-permissions">give Tailscale access</a> to the tun device so it can network effectively.</p>
<p><code>--volume freshrss_tailscale_state:/var/lib/tailscale</code>
: save the Tailscale state into a <a href="https://docs.podman.io/en/latest/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options">volume</a> named &quot;freshrss_tailscale_state&quot;.  /var/lib/tailscale is where the information needed for Tailscale to remember this device, so we can teardown and recreate this container at will.  This state directory is set with TS_STATE_DIR, which we set in the .ts.env and is loaded by the next line.</p>
<p><code>--env-file .ts.env</code>
: this is the file the holds the environment variables this Tailscale container needs to authenticate and store state in the correct place.  <a href="https://docs.podman.io/en/latest/markdown/podman-run.1.html#environment">env-file</a> is a nice alternative to setting envvars individually, like we do with FreshRSS.  It can keep secrets, like our oauth token, out of git (don&#39;t commit your .ts.env file!) and collects all the envvars we need into one place.  In fact, we could do this same thing with FreshRSS if we wanted or <a href="https://github.com/FreshRSS/FreshRSS/tree/edge/Docker#environment-variables">our configuration</a> got more complex!</p>
<p><code>ghcr.io/tailscale/tailscale:latest</code>
: tells podman which <a href="https://docs.podman.io/en/latest/markdown/podman-run.1.html#image">container image</a> to run, &quot;ghcr.io/tailscale/tailscale,&quot; and which version, the &quot;latest&quot; tag.  We rely on the command from the Dockerfile this container image was built with to automatically start Tailscale, so we don&#39;t need to specify anything else! ghcr.io is GitHub&#39;s container registry, so we know that&#39;s where tailscale hosts it!</p>
<h3 id="freshrss-container">FreshRSS Container</h3>
<p>FreshRSS is our main service in the example, but it could be any container you want to run on your tailnet.</p>
<pre><code class="hljs language-bash"><span class="hljs-comment"># run our FreshRSS service</span>
podman run --detach \
  --restart unless-stopped \
  --log-opt max-size=10m \
  --network container:<span class="hljs-string">&quot;<span class="hljs-variable">$SIDECAR_NAME</span>&quot;</span> \
  -e TZ=America/New_York \
  -e <span class="hljs-string">&#x27;CRON_MIN=1,31&#x27;</span> \
  -v freshrss_data:/var/www/FreshRSS/data \
  -v freshrss_extensions:/var/www/FreshRSS/extensions \
  --name <span class="hljs-string">&quot;<span class="hljs-variable">$CONTAINER_NAME</span>&quot;</span> \
  docker.io/freshrss/freshrss
</code></pre><p><code>podman run --detach</code>
: <a href="https://docs.podman.io/en/latest/markdown/podman-run.1.html">run</a> this container in detached mode too</p>
<p><code>--restart unless-stopped</code>
: set the <a href="https://docs.podman.io/en/latest/markdown/podman-run.1.html#restart-policy">restart policy</a> for the container.  <code>unless-stopped</code> will always try to restart the container unless we explicitly run stop to stop the container. Not really necessary here, since the tailscale sidecar won&#39;t restart this way. But we could have that too if we wanted.</p>
<p><code>--log-opt max-size=10m</code>
: freshrss suggests this setting. It sets the max size of the log file. <a href="https://docs.podman.io/en/latest/markdown/podman-run.1.html#log-opt-name-value">More info</a></p>
<p><code>--network continaer:&quot;SIDECAR_NAME&quot;</code>
: this line does the tailscale magic.  It makes the tailscale conatiner (with name $SIDECAR_NAME) <a href="https://docs.podman.io/en/latest/markdown/podman-run.1.html#network-mode-net">the networking stack</a> for this container, so our tailscale sidecar can connect to tailscale <em>and talk to the service within this container</em>.  We&#39;ve parameterized the name of the tailscale sidecar container so we know we&#39;re using the right one!</p>
<p><code>-e TZ=America/New_York</code>
: sets an <a href="https://docs.podman.io/en/latest/markdown/podman-run.1.html#env-e-env">environment variable inside the container</a>. In this case, FreshRSS uses the TZ variable to set <a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones">timezone information</a>, so I&#39;ve set it to the US East Coast. </p>
<p><code>-e &#39;CRON_MIN=1,31</code>
: another environment variable. This one FreshRSS uses to set when to run a cronjob and update feeds. It&#39;s wrapped in single quotes so we can be sure our shell sends those exact characters through, instead of expanding it somehow.</p>
<p><code>-v freshrss_data:/var/www/FreshRSS/data</code>
: mount a <a href="https://docs.podman.io/en/v4.4/markdown/options/volume.html">volume</a> in the container.  A volume is a place we can store things to persist even when the container has been destroyed.  In this case, we&#39;re binding to the directory FreshRSS uses to store its data.</p>
<p><code>-v freshrss_extensions:/var/www/FreshRSS/extensions</code>
: mount another volume into the container.  This is the directory FreshRSS uses to save extensions if we load any.</p>
<p><code>--name &quot;$CONTAINER_NAME&quot;</code>
: set the name of the container to the value we stored in $CONTAINER_NAME in our .env file.  This makes it much easier to find this container when we run <a href="https://docs.podman.io/en/latest/markdown/podman-ps.1.html">podman ps</a></p>
<p><code>docker.io/freshrss/freshrss</code>
: the name of the container to run.  Note the <code>docker.io</code> on the front here; docker sets this by default if you don&#39;t use it.  Other container engines require the full uri (but have settings where you can set defaults).</p>
<h2 id="wrap-up">Wrap Up</h2>
<p>That&#39;s the entire file, line by line!  Of course you can read all about all these options and more in the <a href="https://docs.podman.io/en/latest/">docs</a> but hopefully this was a nice little tutorial of all of them in plain English for you. Container engines have a lot of options and the best way to learn is by practice.</p>
<p>Like a pod of seals, float on!</p>
<p>[^chmod]: <code>chmod +x run</code>
[^variables]: key=&quot;value&quot; pairs, where the quotes help in case there&#39;s space!                                                   </p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Mon, 07 Jul 2025 00:00:00 GMT</pubDate>
      <title>Tailscale Sidecar Tutorial</title>
      <link>https://benkenawell.com/p/2025-07-07-tailscale-sidecar-tutorial.html</link>
      <guid>https://benkenawell.com/p/2025-07-07-tailscale-sidecar-tutorial.html</guid>
      <description>&lt;p&gt;Adding &lt;a href=&quot;https://tailscale.com/&quot;&gt;Tailscale&lt;/a&gt; to your self hosted containers lets you easily set up those services with a secure, fully qualified domain name. &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Tailscale Sidecar Tutorial</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Tailscale Sidecar Tutorial</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2025-07-07T00:00:00+00:00">Jul 7th, 2025</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>Adding <a href="https://tailscale.com/">Tailscale</a> to your self hosted containers lets you easily set up those services with a secure, fully qualified domain name. <!--more-->  It is easy to remember and easy to set up.  Tailscale has their own <a href="https://tailscale.com/kb/1282/docker">Docker docs</a>, but I want to expand on that starting point and use a different container engine.</p>
<p>There&#39;s a lot to Tailscale that facilitates this magic.  First, the Tailnet name means you can give each container a name.  It&#39;s easy to identify on the Tailscale Dashboard, it&#39;s easy to type into a URL bar, it&#39;s as easy to use as any URL! Second, OAuth tokens make it simple to connect containers (not just machines!) into your Tailnet.  Third, HTTPS certificates mean web services behave like you would expect. They look secure in the browser and have access to <a href="https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts">secure context APIs</a>.  Lastly, Tailscale Serve proxies you to the correct port in your container.</p>
<p>Those services together make the experience pretty seamless to connect, once you know how all those pieces fit together.  This post will walk you through all the steps you need to set this up today.  We&#39;ll be using <a href="https://www.freshrss.org/">FreshRSS</a> as the service we&#39;re proxying.  I&#39;m <em>not</em> going to use docker compose. Instead, I&#39;ll be using <a href="https://podman.io/">podman</a> and a handful of bash scripts to connect our containers together.  This way, the tutorial will be a little more generic and should work for any oci compliant engine (docker, podman, nerdctl, etc).</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>Have a Tailscale account.</li>
<li>Have a computer on your Tailnet.</li>
<li>Have a container engine installed. Podman, docker, and nerdctl should all work interchangably, but I&#39;ll use podman for this tutorial</li>
<li>A project directory on your computer. Something like <code>~/services/freshrss-sidecar</code> would be great.  All files and folders will be created in this directory.</li>
<li>Some kind of text editor.</li>
<li>A Linux/Mac computer -- not a requirement, but all my commands will be for Unix-y operating systems.</li>
</ul>
<h2 id="tutorial">Tutorial</h2>
<h3 id="pull-the-container-images">Pull the container images</h3>
<p>If you skip this step, podman will pull these the first time we run the container.  Either way is fine!</p>
<pre><code class="hljs language-bash"><span class="hljs-comment"># pull tailscale</span>
podman pull ghcr.io/tailscale/tailscale:latest
<span class="hljs-comment"># pull freshrss</span>
podman pull docker.io/freshrss/freshrss:latest
</code></pre><h3 id="set-up-some-environment-variables">Set up some environment variables</h3>
<p>This <code>.env</code> file will hold a few configuration variables we can share across our bash scripts.  You can parameterize more or less, but this is a good start. <code>HOSTNAME</code> is how we&#39;ll reference it on our tailnet at the end.</p>
<pre><code class="hljs language-bash"><span class="hljs-comment"># file: .env</span>
SIDECAR_NAME=<span class="hljs-string">&quot;freshrss_tailscale&quot;</span>
CONTAINER_NAME=<span class="hljs-string">&quot;freshrss&quot;</span>
HOSTNAME=<span class="hljs-string">&quot;rss&quot;</span>
</code></pre><p>Next, create a <code>.ts.env</code> file.  This one will hold all the environment variables our tailscale sidecar container needs to connect to our tailnet.  We can fill in everything in this file but our TS_AUTHKEY argument. We&#39;ll generate that shortly.</p>
<pre><code class="hljs language-bash"><span class="hljs-comment"># file: .ts.env</span>
TS_AUTHKEY=tskey-client-notareal-tailscaleoauthtoken
TS_EXTRA_ARGS=--advertise-tags=tag:container
TS_STATE_DIR=/var/lib/tailscale
TS_USERSPACE=<span class="hljs-literal">false</span>
TS_SERVE_CONFIG=/config/ts.json
</code></pre><h3 id="pick-a-tailscale-tailnet-name">Pick a Tailscale Tailnet Name</h3>
<p>In your Tailscale dashboard, go to DNS and find your <a href="https://tailscale.com/kb/1217/tailnet-name">Tailnet name</a>.  Tailscale uses <code>yak-bebop</code> as their example. You can&#39;t pick the one you want (only generate it), and you also can&#39;t change it once you turn on HTTPS.  So take some time to generate one that you like.  I like a fun name, but you can pick the default too!</p>
<p>We can make most of this tutorial work without HTTPS, so we won&#39;t turn it on until the end.</p>
<h3 id="generate-an-oauth-client">Generate an OAuth client</h3>
<p>An [OAuth client] gives you a more secure way to connect to your tailnet versus and Auth Key.  Before we can generate the token, we need a tag to give our container.  The official docs give a good reference for <a href="https://tailscale.com/kb/1068/tags#define-a-tag">defining a tag</a>.  My short version: go to Tailscale Access Controls tab and add an entry under the <code>tagOwner</code> key named <code>tag:container</code>:</p>
<pre><code class="hljs language-json"><span class="hljs-attr">&quot;tagOwners&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
  <span class="hljs-attr">&quot;tag:container&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">&quot;autogroup:admin&quot;</span><span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span>
<span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
</code></pre><p>Save that with the button beneath and now we have a group we can assign the <a href="https://tailscale.com/kb/1215/oauth-clients">OAuth token</a> to. So head over to your Tailscale Settings page, the &quot;OAuth clients&quot; option along the sidebar and click the &quot;Generate OAuth Client&quot; button.  Type a description like &quot;FreshRSS Test Token&quot; and make sure you give it the &quot;Devices:Core&quot; and &quot;Keys:Auth Keys&quot; write permissions.  Make sure you have the tag:container that we just created chosen under the &quot;Add tag&quot; dropdown.  It should automatically assign to the second permission if you&#39;ve set it for the first one.</p>
<p>Remember, &quot;Devices:Core&quot; and &quot;Keys:Auth Keys&quot; write permissions, assigned to your new tag, tag:container!</p>
<p>Click &quot;Generate Client&quot; and copy the key it gives you into your <code>.ts.env</code> file.  You&#39;ll never see this key again, but if you lose it just delete this one and regenerate a new one.  The TS_AUTHKEY line should now have your auth token in place of our previous placeholder, giving you a file that looks like:</p>
<pre><code class="hljs language-bash">TS_AUTHKEY=tskey-client-yournew-tailscaleoauthtoken
TS_EXTRA_ARGS=--advertise-tags=tag:container
TS_STATE_DIR=/var/lib/tailscale
TS_USERSPACE=<span class="hljs-literal">false</span>
TS_SERVE_CONFIG=/config/ts.json
</code></pre><h3 id="running-our-container">Running our Container</h3>
<p>Now we have everything we need to run our container and access it over http.  We need two containers, so we&#39;ll write a short script to help coordinate it.  I call it <code>run</code>, to mimic the <code>podman run</code> command. It will create 3 volumes, use the images we pulled in the first step, and our .env and .ts.env files to load everything up.</p>
<pre><code class="hljs language-bash"><span class="hljs-meta">#!/usr/bin/env bash</span>

<span class="hljs-comment"># load our environment variables</span>
<span class="hljs-built_in">source</span> .<span class="hljs-built_in">env</span>

<span class="hljs-comment"># run our sidecar container</span>
podman run --detach \
  --hostname <span class="hljs-string">&quot;<span class="hljs-variable">$HOSTNAME</span>&quot;</span> \
  --name <span class="hljs-string">&quot;<span class="hljs-variable">$SIDECAR_NAME</span>&quot;</span>\
  --cap-add net_admin \
  --device /dev/net/tun:/dev/net/tun \
  --volume freshrss_tailscale_state:/var/lib/tailscale \
  --env-file .ts.env \
  ghcr.io/tailscale/tailscale:latest

<span class="hljs-comment"># run our FreshRSS service</span>
podman run --detach \
  --restart unless-stopped \
  --log-opt max-size=10m \
  --network container:<span class="hljs-string">&quot;<span class="hljs-variable">$SIDECAR_NAME</span>&quot;</span> \
  -e TZ=America/New_York \
  -e <span class="hljs-string">&#x27;CRON_MIN=1,31&#x27;</span> \
  -v freshrss_data:/var/www/FreshRSS/data \
  -v freshrss_extensions:/var/www/FreshRSS/extensions \
  --name <span class="hljs-string">&quot;<span class="hljs-variable">$CONTAINER_NAME</span>&quot;</span> \
  docker.io/freshrss/freshrss
</code></pre><p>Make the file executable by running <code>chmod +x run</code>, the run the script with <code>./run</code>.  Or you can call <code>bash run</code> and not need to run the chmod command.</p>
<p>Shortly, we should see a &quot;machine&quot; with the tag:container under our Tailscale Dashboard&#39;s machine tab called &quot;rss&quot;.</p>
<p>Navigate a web browser to <code>http://rss.${tailnet-name}.ts.net</code> (eg, <code>http://rss.yak-bebop.ts.net</code>) from your tailnet connected computer and you should see the FreshRSS welcome page!  Because we don&#39;t have https certificates working anyway, you could use <code>http://rss</code> to go to the same site, but the short names don&#39;t work with HTTPS certs so stick with the longer version.</p>
<p>You could be done at this point, but Tailscale Serve ensures you can work with many different container ports and HTTPS Certs will make your experience much better in a modern browser.</p>
<h3 id="tailscale-serve-config">Tailscale Serve Config</h3>
<p>This is the last configuration we need, <a href="https://tailscale.com/blog/reintroducing-serve-funnel">Tailscale Serve</a>. Tailscale won&#39;t provision a TLS cert for us without this.  And the config will tell tailscale what port to proxy to, so we don&#39;t need to type that into our browser either.  We didn&#39;t need a port number with FreshRSS because it exposes itself on the correct port, port 80.  But for most services we&#39;ll want this set up.  And 80 is the wrong port for https (that&#39;s port 443).  Make a <code>config</code> directory with <code>ts.json</code> in it, to mount to our tailscale sidecar container.  Under the Proxy key is where you would set the port for your service; FreshRSS is on port 80.</p>
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
  <span class="hljs-attr">&quot;TCP&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
    <span class="hljs-attr">&quot;443&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
      <span class="hljs-attr">&quot;HTTPS&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span>
    <span class="hljs-punctuation">}</span>
  <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">&quot;Web&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
    <span class="hljs-attr">&quot;${TS_CERT_DOMAIN}:443&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
      <span class="hljs-attr">&quot;Handlers&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
        <span class="hljs-attr">&quot;/&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
          <span class="hljs-attr">&quot;Proxy&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;http://127.0.0.1:80&quot;</span>
        <span class="hljs-punctuation">}</span>
      <span class="hljs-punctuation">}</span>
    <span class="hljs-punctuation">}</span>
  <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
  <span class="hljs-attr">&quot;AllowFunnel&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
    <span class="hljs-attr">&quot;${TS_CERT_DOMAIN}:443&quot;</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">false</span></span>
  <span class="hljs-punctuation">}</span>
<span class="hljs-punctuation">}</span>
</code></pre><p>Since this serve config tells Tailscale to generate a https certificate for us, let&#39;s turn that on in Tailscale before we use it.</p>
<h3 id="tailscale-https-certificates">Tailscale HTTPS Certificates</h3>
<p>In your Tailscale Dashboard, go to the DNS tab and scroll to the bottom.  Click Enable HTTPS and go through any of its prompts.  Done!  Now let&#39;s return to the terminal and restart these containers. The <a href="https://tailscale.com/kb/1153/enabling-https">official docs</a> are easy to follow for this step.</p>
<h3 id="restart-with-https">Restart with https</h3>
<p>Our service container, freshrss, depends on the tailscale sidecar to function.  That means we can&#39;t stop tailscale until our  freshrss container has stopped.  Let&#39;s write a small script to help us always stop the containers in the correct order.  Call it <code>stop</code> to again mimic the podman cli.</p>
<pre><code class="hljs language-bash"><span class="hljs-meta">#!/usr/bin/env bash</span>

<span class="hljs-comment"># reuse our envvars to get the container names</span>
<span class="hljs-built_in">source</span> .<span class="hljs-built_in">env</span>

podman stop <span class="hljs-string">&quot;<span class="hljs-variable">$CONTAINER_NAME</span>&quot;</span>
podman stop <span class="hljs-string">&quot;<span class="hljs-variable">$SIDECAR_NAME</span>&quot;</span>
</code></pre><p>Then, we can also make a <code>rm</code> script to fully remove the containers.  This could be one script, but I like breaking it into two for a few reasons. One, I might not always want to fully remove the containers. Two, it mimics the podman cli which has pretty good ergonomics for all its power.</p>
<pre><code class="hljs language-bash"><span class="hljs-meta">#!/usr/bin/env bash</span>

<span class="hljs-built_in">source</span> .<span class="hljs-built_in">env</span>

podman <span class="hljs-built_in">rm</span> <span class="hljs-string">&quot;<span class="hljs-variable">$CONTAINER_NAME</span>&quot;</span>
podman <span class="hljs-built_in">rm</span> <span class="hljs-string">&quot;<span class="hljs-variable">$SIDECAR_NAME</span>&quot;</span>
</code></pre><p>We need to add a line to our run script to mount the tailscale serve config, making the full file:</p>
<pre><code class="hljs language-bash"><span class="hljs-meta">#!/usr/bin/env bash</span>

<span class="hljs-built_in">source</span> .<span class="hljs-built_in">env</span>

podman run --detach \
  --hostname <span class="hljs-string">&quot;<span class="hljs-variable">$HOSTNAME</span>&quot;</span> \
  --name <span class="hljs-string">&quot;<span class="hljs-variable">$SIDECAR_NAME</span>&quot;</span>\
  --cap-add net_admin \
  --device /dev/net/tun:/dev/net/tun \
  --volume freshrss_tailscale_state:/var/lib/tailscale \
  --volume <span class="hljs-string">&quot;<span class="hljs-subst">$(pwd)</span>/config&quot;</span>:/config \
  --env-file .ts.env \
  ghcr.io/tailscale/tailscale:latest

podman run --detach \
  --restart unless-stopped \
  --log-opt max-size=10m \
  --network container:<span class="hljs-string">&quot;<span class="hljs-variable">$SIDECAR_NAME</span>&quot;</span> \
  -e TZ=America/New_York \
  -e <span class="hljs-string">&#x27;CRON_MIN=1,31&#x27;</span> \
  -v freshrss_data:/var/www/FreshRSS/data \
  -v freshrss_extensions:/var/www/FreshRSS/extensions \
  --name <span class="hljs-string">&quot;<span class="hljs-variable">$CONTAINER_NAME</span>&quot;</span> \
  docker.io/freshrss/freshrss
</code></pre><p>After all that, run the <code>stop</code> script, then the <code>rm</code> script, then the <code>run</code> script again.  You should see everything come back up and work, with any state/login/rss feeds you already setup in FreshRSS!</p>
<h2 id="troubleshooting">Troubleshooting</h2>
<h3 id="deleting-the-tailscale-state-volume">Deleting the Tailscale state volume</h3>
<p>If we delete the &quot;freshrss_tailscale_state&quot; volume, tailscale won&#39;t be able to reuse the same hostname.  Instead you&#39;ll end up seeing &quot;rss-1&quot;, &quot;rss-2&quot;, etc. in your list of tailscale machines.  To rememdy this, just use the tailscale dashboard to remove those machines, delete the &quot;freshrss_tailscale_state&quot; volume again and rerun the <code>run</code> script to reset the tailscale hostname</p>
<h3 id="tailscale-isnt-showing-my-machine-or-it-isnt-connected">Tailscale isn&#39;t showing my machine or it isn&#39;t connected</h3>
<p>If you can&#39;t see the rss machine, or it isn&#39;t connected, run <code>podman ps -a</code> to get a list of all the containers, including stopped ones.  If you can see it has exited, you can run <code>podman logs freshrss_tailscale</code> to see what error messages it might have thrown.</p>
<h2 id="taking-it-further">Taking it further</h2>
<p>Now that we have the container up and running, there&#39;s more we can do to automate!  Podman has a great Quadlets feature that will run this all via systemd for us, adding a lot of resiliency. We can add more envvars to our .env file to make our scripts more configurable.  We could write an <code>up</code> script to mimic a <code>docker compose up</code> style command.  We could automate backing up FreshRSS by copying out the volume data somewhere.  We could automate upgrades using <code>podman rename</code> before spinning up a new instance and inserting it where the old one was.</p>
<p>We could enjoy our FreshRSS instance and download a reader like <a href="https://f-droid.org/en/packages/com.capyreader.app/">Capy Reader</a> to use it on our phone. </p>
<p>There are so many possibilities!  I hope you enjoyed this tutorial.</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Wed, 28 May 2025 00:00:00 GMT</pubDate>
      <title>Note Taking: Obsidian and Fastmail</title>
      <link>https://benkenawell.com/p/2025-05-28-obsidian-and-fastmail.html</link>
      <guid>https://benkenawell.com/p/2025-05-28-obsidian-and-fastmail.html</guid>
      <description>&lt;p&gt;Everyone has a note taking app they prefer.  For almost 5 years now I&amp;#39;ve paid for &lt;a href=&quot;https://standardnotes.com/&quot;&gt;Standard Notes&lt;/a&gt; &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Note Taking: Obsidian and Fastmail</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Note Taking: Obsidian and Fastmail</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2025-05-28T00:00:00+00:00">May 28th, 2025</time>
    <span class="p-author">Ben</span>
  </div>
</header>

<p>Everyone has a note taking app they prefer.  For almost 5 years now I&#39;ve paid for <a href="https://standardnotes.com/">Standard Notes</a> <!--more--> to give me plain text notes synced across my devices.  Back then, I was much more bullish on encryption and privacy and this worked great cross platform.  It gave me basic searching and sorting functions.</p>
<h2 id="old-editor">Old Editor</h2>
<p>I never really liked their editors, even their Markdown or Spreadsheet ones.  They didn&#39;t feel good, especially on mobile.  Like they were making as many as possible, but not refining any of them.  So I always used the plaintext editor, even when typing in Markdown.</p>
<p>I didn&#39;t want to fall into the hamster wheel trap of the next new note taking app.  Standard Notes stored and synced notes like I needed. Migrating notes to a new experience every couple of months would be such a pain, especially into a new format. And just to try some other editor that will probably charge me money to sync.</p>
<p>A lot has changed in the time since I started using Standard Notes. I&#39;ve gotten less strict about a E2E solution for everything.  I favor open standards over the encrypted stuff now, in many cases.  I switched email providers from <a href="https://proton.me/mail">Proton Mail</a> to <a href="https://www.fastmail.com/">Fastmail</a> (again, a big UX choice).  I&#39;ve learned much more about self hosting and have gotten more comfortable with how to put things on the web (including this blog).  And it has been 4 or 5 years since I&#39;ve changed notes apps, Standard Notes hadn&#39;t changed much in that time.</p>
<h2 id="new-editor">New Editor</h2>
<p>I had heard about  <a href="https://obsidian.md/">Obsidian</a> on a couple podcasts, but mostly ignored it.  I was setting up a new laptop at work, where I used the free version of Standard Notes, and decided this would be a good time to give it a try.  The promise of all my notes being Markdown files on my computer was super cool.  I figured I could script against them myself even!  I don&#39;t need any syncing at work, it&#39;s just the one computer.</p>
<p>And wow, I was blown away!  A few weeks in and I&#39;m still amazed at how open Obsidian is, how responsive Obsidian is, and how many great features they&#39;ve packed in.  I&#39;ve even taken a look at <a href="https://codemirror.net/">CodeMirror</a>, and might use it in future projects.</p>
<p>I liked it so much at work I decided to download it on my Android phone and see if it could live up to my hype there.  And it nailed it!  Really just wonderful.  The <a href="https://help.obsidian.md/Extending+Obsidian/Obsidian+URI">obsidian links</a> make it easy to extends via <a href="https://www.macrodroid.com/">macros</a> and the markdown looks great.</p>
<h2 id="syncing-solution">Syncing Solution</h2>
<p>So I also downloaded it on <a href="/p/2025-04-19-new-laptop-framework.html">my laptop</a> too.  But now I needed a way to sync between my phone and computer.  Since I&#39;ve begun to self host other things, I started to look into that.  But they all seemed like they might be a lot of work, I didn&#39;t want something that would be more maintenance than useful.</p>
<p>I looked into the official <a href="https://obsidian.md/sync">Obsidian Sync</a>, which costs about the same amount as my Standard Notes subscription at $4/mo.  But by now my imagination was going wild with embeddable photos and who knows what else!  The official plan only gave me 1GB and sharing with my wife would be another $4/mo for her.</p>
<p>As I was looking at the possibilities, I stumbled into WebDAV as an option via the <a href="https://remotelysave.com/">RemotelySave plugin</a> .  My experience with the DAV protocols are somewhat mixed.  They&#39;re powerful but tough to use.  Fortunately, my Fastmail email has <a href="https://www.fastmail.help/hc/en-us/articles/1500000277882-Remote-file-access">a full WebDAV server</a>.  You can even view the files in their app!  And my account comes with 10GB of storage, plus I could have my wife just log in with a different app password.</p>
<h2 id="migration">Migration</h2>
<p>Coming back to this complaint from above, I haven&#39;t migrated much yet. Standard Notes will give me my notes in a .txt, and Obsidian will automatically recognize those if I change them to a .md extension.  Some of the titles might be mangled and I might lose some metadata.  But I had very little metadata, and titles can be repaired.</p>
<p>I&#39;ve realized that maybe most of my notes are fairly <a href="https://notes.andymatuschak.org/Evergreen_notes?stackedNotes=zKGjQtsTKgscAoq271ZzKqw">transient</a> anyway. The posts on this blog have a little more staying power, I think.  So this is a work in progress, who knows how much I&#39;ll end up moving over.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Time will tell how long this version of note taking lasts for me.  But Obsidian is an amazing product and RemotelySave with Fastmail makes it so easy to sync that for now, this is definitely a winner.</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Sat, 10 May 2025 00:00:00 GMT</pubDate>
      <title>Defining Part Stack Developer</title>
      <link>https://benkenawell.com/p/2025-05-10-defining-part-stack-developer.html</link>
      <guid>https://benkenawell.com/p/2025-05-10-defining-part-stack-developer.html</guid>
      <description>&lt;p&gt;On &lt;a href=&quot;https://www.linkedin.com/in/benjamin-kenawell/&quot;&gt;my LinkedIn page&lt;/a&gt;, my title is &amp;quot;Part Stack Web Dev.&amp;quot; It came from an article I read or podcast I listened to that I haven&amp;#39;t been able to find again, so I wanted to write a bit about it. &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Defining Part Stack Developer</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Defining Part Stack Developer</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2025-05-10T00:00:00+00:00">May 10th, 2025</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>On <a href="https://www.linkedin.com/in/benjamin-kenawell/">my LinkedIn page</a>, my title is &quot;Part Stack Web Dev.&quot; It came from an article I read or podcast I listened to that I haven&#39;t been able to find again, so I wanted to write a bit about it. <!--more--></p>
<p>I work at a start up small enough that there are no defined levels or strict job titles, so when I heard about the tongue in cheek &quot;part stack&quot; I decided it would be a fun title to give myself.  It&#39;s supposed to be a joke on &quot;Full stack Developer&quot; job titles where they mean &quot;front end web pages/SPA and backend application servers,&quot; which feels like a majority of job postings.</p>
<h2 id="part-stack">Part Stack</h2>
<p>But there&#39;s also a whole stack of computers and services to serve your application, why aren&#39;t <em>those</em> considered part of a full stack engineer&#39;s job?  I think of that as an Operations role, so maybe those pieces fall under a &quot;DevOps Engineer&quot; title, which smashes the &quot;developer&quot; and &quot;operations&quot; roles together.  Maybe all together that&#39;s a &quot;Full Stack DevOps Engineer&quot;?</p>
<p>Of course, all of these vary by company and HR department. And luckily there&#39;s a job description and interview process where companies and prospective employees can get a better understanding of what a role might entail.  We know what all of these titles mean, to a certain degree, so what is a Part Stack Engineer?</p>
<p>I work on the <strong>part of the stack</strong> that drives your business and serves your customers.  I can take a website/web application from Hello World to IPO.  I can launch a site if you&#39;ll give me a bare metal server or VPS to deploy it on.  I can build an application server capable of scaling to meet your demand, making it secure and observable.  I can write HTML/CSS/JS to make your website look and feel like the brand you want to present.</p>
<h2 id="web-dev">Web Dev</h2>
<p>If you noticed that everything on that list is related to the web, you might also remember my job title includes &quot;Web Dev.&quot; I would happily call myself &quot;Web Engineer,&quot; I don&#39;t mean to debate developer vs engineer here.  Web Dev just rolls off the tongue better than Web Eng.  The &quot;web&quot; part is important to me.  We live in a world where you want your data to be accessible on your laptop, and your phone, and maybe a work computer, tablet, etc.  It has to live somewhere, so a server (<a href="https://once.com/">yours</a> or mine) is a good place to live.  HTTP/WWW is definitely the dominant paradigm for accessing those kinds of things, everybody has a web browser.</p>
<p>Having a web server <a href="https://htmx.org/essays/hateoas/">also deliver the markup</a> makes the whole thing so much more straightforward to develop; you&#39;ll save 100s of hours a year of developer time.  There are <a href="https://native.hotwired.dev/">similar</a> <a href="https://hyperview.org/">projects</a> for mobile apps. I can deliver a mobile ready website, but an app is one of my weakest areas.  So I stuck the &quot;Web&quot; part in my self-proclaimed title because my works revolves around the web.</p>
<h2 id="conclusion">Conclusion</h2>
<p>The web encompasses a lot of modern consumer computing. Any part of that stack is something I am interested in. At this point, 6 years into my professional programming career, I think I have at least a passing familiarity with most of it. And plenty of experience to plan, build, deploy, and operate a web application.  So I call myself a &quot;Part Stack Web Dev,&quot; a title I think encompasses a majority of my experience and interests.</p>
<hr>
<p>PS, if you think you know where &quot;Part Stack&quot; came from, I&#39;d <a href="https://mastodon.social/@benkenawell">love to know</a> so I can link to it from here.</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Sat, 19 Apr 2025 00:00:00 GMT</pubDate>
      <title>New Laptop: Framework 13</title>
      <link>https://benkenawell.com/p/2025-04-19-new-laptop-framework.html</link>
      <guid>https://benkenawell.com/p/2025-04-19-new-laptop-framework.html</guid>
      <description>&lt;p&gt;I&amp;#39;m typing this on my new &lt;a href=&quot;https://frame.work/laptop13&quot;&gt;Framework Laptop 13&lt;/a&gt; in the park &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - New Laptop: Framework 13</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">New Laptop: Framework 13</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2025-04-19T00:00:00+00:00">Apr 19th, 2025</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>I&#39;m typing this on my new <a href="https://frame.work/laptop13">Framework Laptop 13</a> in the park <!--more-->because it&#39;s the first 80 degree day we&#39;ve had this year.  The screen is remarkably bright and it&#39;s standing up okay to the wind.</p>
<p>I&#39;ve had a Lenovo X1 Extreme for ~six years now.  It&#39;s been a great machine, but I decided to upgrade.  In the past 6 years, I was able to plan my wedding with LibreOffice.  I&#39;ve become much, much more comfortable with Linux. A 16 inch laptop was getting wieldy, so I&#39;ve been looking for something smaller.  Microsoft is ending support for <a href="https://support.microsoft.com/en-us/windows/windows-10-supports-ends-on-october-14-2025-2ca8b313-1946-43d3-b55c-2b95b107f281">Windows 10 in October</a>.  All of those things were reasons to look at new laptops.  But the only requirement in there is smaller, with a strong preference for Linux support.</p>
<p>I ended with a Framework because of their excellent Linux support, great size screen, and most importantly: their <a href="https://frame.work/blog/how-to-buy-a-laptop-without-breaking-the-bank-or-the-planet">philosophy</a> that you should be able to fix and upgrade your computer without buying an entirely new one.  I&#39;ve read some <a href="https://daringfireball.net/linked/2024/04/28/dhh-framework-13">articles</a> and had heard good things.  They announced new main boards recently, selling their old AMD Ryzen chips at a discount.  The price was much cheaper for a DIY version than many competitors.  And I look forward to changing and upgrading my computer in the future.</p>
<p>I went with a bottom of the line AMD Ryzen chip, but spec&#39;d it out with a 4TB hard drive and 48GB of DDR5 RAM.  I opted for the nicer screen in thought of scaling 200% in Ubuntu and got plenty of expansion cards to take full advantage of them.  A clear keyboard makes it a bit of a head turner, but otherwise nothing about this computer shouts &quot;I can fix this!&quot;, it just blends in with the landscape.  I have hopes of upgrading the main board in a year or so, and even then this computer will be cheaper for the specs than most of the competitors I looked at.</p>
<p>Overall, my first impressions have been amazing.  It took me ~20 minutes to put the hardware together, even after reading all their docs twice.  The installation of Ubuntu was a breeze, and their guide helped me feel even more confident. Everything has just worked and I&#39;ve gotten all my typical tools installed: LibreOffice, Bitwarden, Brave, mise, git, eza, fzf, neovim... You get the idea.  Some from the App Center, some from the terminal and everything has worked flawlessly.  I&#39;m getting to try out ULauncher and Sway (not at the same time) and podman.  Basically, getting the laptop setup was a nonevent.  In less than an hour of time, I was doing everything I wanted on it.</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Fri, 21 Mar 2025 00:00:00 GMT</pubDate>
      <title>Book Review: The Unicorn Project</title>
      <link>https://benkenawell.com/p/2025-03-21-book-review-unicorn-project.html</link>
      <guid>https://benkenawell.com/p/2025-03-21-book-review-unicorn-project.html</guid>
      <description>&lt;p&gt;Having never read another book by Gene Kim, &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Book Review: The Unicorn Project</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Book Review: The Unicorn Project</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2025-03-21T00:00:00+00:00">Mar 21st, 2025</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>Having never read another book by Gene Kim, <!--more--> he has captured everything I fear from the process and roadblocks and &quot;checks&quot; in a large corporation (and even small startups). While I find his choice of technologies a little hype focused (noSQL, functional programming, microservices), I think the business principles may have merit. But where do people randomly find servers from in a company?</p>
<p>Even if the business principles are part of a hype cycle like the technologies, the book was a fun read. It felt like epic fiction meeting corporate America, and the good guys won. The frameworks were well presented within the narrative and the references section has left me with a lot of reading from Erik&#39;s &quot;sensei&#39;s&quot;.</p>
<p>His seamless blending of technology and business, with all the politics and conflicts it can create (and synergies), was well written to be approachable and familiar while providing plenty of context for those outside a corporate behemoth. </p>
<p>The timelines feel too condensed for any corporation I&#39;ve seen or heard about, but they add to the fun narrative of the story. Most of the individuals details felt exaggerated like that: not too much, but enough to add enjoyment and suspense to the narrative. I hope I never work for or run a company asking their IT staff to work so much without properly recognizing or compensating them.</p>
<p>Some links to the book:</p>
<ul>
<li><a href="https://www.amazon.com/Unicorn-Project-Developers-Disruption-Thriving-ebook/dp/B07QT9QR41">Amazon</a></li>
<li><a href="https://itrevolution.com/product/the-unicorn-project/">IT Revolution</a></li>
<li><a href="https://www.goodreads.com/book/show/44333183-the-unicorn-project'">GoodReads</a></li>
</ul>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Sun, 26 Jan 2025 00:00:00 GMT</pubDate>
      <title>Custom Elements and the Light DOM</title>
      <link>https://benkenawell.com/p/2025-01-26-custom-elements-and-the-light-dom.html</link>
      <guid>https://benkenawell.com/p/2025-01-26-custom-elements-and-the-light-dom.html</guid>
      <description>&lt;p&gt;Custom Elements have their own lifecycle. &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Custom Elements and the Light DOM</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Custom Elements and the Light DOM</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2025-01-26T00:00:00+00:00">Jan 26th, 2025</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>Custom Elements have their own lifecycle. <!--more-->  This makes it orders of magnitude easier to render partial page updates.  No matter how the html is added to the page, adding a custom element will kick in it&#39;s own lifecycle.  You&#39;ll never get these bits of html that should do something, but the JavaScript hasn&#39;t run on them because they were added after page load.</p>
<p>The Custom Element lifecycle gives us <em>some</em> of the reactive benefits of something like Alpine or React, but built directly into the browser.  You&#39;ll probably hit the limit of this reactivity very quickly, but both the lifecycle and reactivity are compelling reasons to use Custom Elements. And they <a href="/p/2025-01-20-react-custom-element.html">integrate very well with something like React</a>, for more complicated components.</p>
<p>Custom Elements also give you the ability to use the ShadowDOM.  A form of encapsulation that ensures you&#39;re interior elements are in their own little realm.  But if I want a Custom Element to add behavior to the DOM, I still want to style it with CSS. So I rarely use the ShadowDOM.  Instead, I use the &quot;normal&quot; DOM, or <a href="https://stackoverflow.com/questions/42093610/difference-between-light-dom-and-shadow-dom">LightDOM</a>, to make them easily accessible through CSS.</p>
<p>I haven&#39;t done much more than think about this next part, but I think the ShadowDOM would be the right decision if I were creating a new input or similar control.  Something that is complicated enough where I want to <em>also</em> give some styling control over using <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_shadow_parts">parts</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot">slots</a>.  Since my day to day work is still mostly in React, I don&#39;t do a lot of this.  With React 19 and it&#39;s <a href="https://react.dev/blog/2024/12/05/react-19#support-for-custom-elements">full support for Custom Elements</a>, many complex React controls could be rewritten as Custom Elements.  <a href="/p/2025-01-20-react-custom-element.html">They might even still have React in them</a>, but they could be templated and share the web page much more easily. </p>
<hr>
<small>
Note: I prefer the name "Custom Element" because it reminds me more of html elements.  Conversely, "Web Components" reminds me more of React components.  Whatever you call them, they feel decidedly more like html elements than React components, so I prefer calling them Custom Elements.  I know Custom Elements are really just one part of the [Web Component suite of technologies](https://developer.mozilla.org/en-US/docs/Web/API/Web_components), but I often hear them used interchangably.
</small>

</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Mon, 20 Jan 2025 00:00:00 GMT</pubDate>
      <title>Custom Element With React</title>
      <link>https://benkenawell.com/p/2025-01-20-react-custom-element.html</link>
      <guid>https://benkenawell.com/p/2025-01-20-react-custom-element.html</guid>
      <description>&lt;p&gt;In my [previous post]({% post_url 2025-01-13-breaking-the-react-wall %}), I discussed how we can escape React out the top. &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Custom Element With React</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  <script type="module" src="/scripts/react-custom-element.js"></script>
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Custom Element With React</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2025-01-20T00:00:00+00:00">Jan 20th, 2025</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>In my <a href="/p/2025-01-13-breaking-the-react-wall.html">previous post</a>, I discussed how we can escape React out the top. <!--more-->  The example there is nice, but feels like a lot of free floating code.  We can make better <a href="https://www.patterns.dev/vanilla/islands-architecture">islands of interactivity</a> by using <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements">Custom Elements</a>!  In fact, we can even hide React as an implementation detail entirely this way.  By making our entrypoint a Custom Element, we have something that&#39;s easy to template/render server-side and still have all of the interactivity client side that we use React for.</p>
<p>Our App component is almost the same as before.  Since we know we&#39;ll have a Custom Element wrapping it, we don&#39;t need the div tag.  With that change, our Custom Element, TextSwitcher, looks like this:</p>
<pre><code class="hljs language-javascript"><span class="hljs-keyword">const</span> <span class="hljs-title class_">App</span> = <span class="hljs-title class_">React</span>.<span class="hljs-title function_">forwardRef</span>(<span class="hljs-keyword">function</span> (<span class="hljs-params">props, ref</span>) {
  <span class="hljs-keyword">const</span> [msg, setMsg] = <span class="hljs-title function_">useState</span>(props.<span class="hljs-property">defaultText</span> || <span class="hljs-string">&quot;Hi there!&quot;</span>);
  <span class="hljs-title function_">useImperativeHandle</span>(ref, <span class="hljs-function">() =&gt;</span> ({
    <span class="hljs-title function_">update</span>(<span class="hljs-params">newMsg</span>) { <span class="hljs-title function_">setMsg</span>(newMsg) }
  }), [setMsg]);
  <span class="hljs-keyword">return</span> msg;
});

<span class="hljs-keyword">class</span> <span class="hljs-title class_">TextSwitcher</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_ inherited__">HTMLElement</span> {
  <span class="hljs-title function_">connectedCallback</span>(<span class="hljs-params"></span>) {
    <span class="hljs-keyword">if</span>(!<span class="hljs-variable language_">this</span>.<span class="hljs-property">reactRoot</span>) <span class="hljs-variable language_">this</span>.<span class="hljs-property">reactRoot</span> = <span class="hljs-title class_">ReactDOM</span>.<span class="hljs-title function_">createRoot</span>(<span class="hljs-variable language_">this</span>);
    <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">render</span>();
  }

  <span class="hljs-title function_">render</span>(<span class="hljs-params"></span>) {
    <span class="hljs-variable language_">this</span>.<span class="hljs-property">reactRoot</span>?.<span class="hljs-title function_">render</span>(<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">App</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">{this.copyRef.bind(this)}</span> <span class="hljs-attr">defaultText</span>=<span class="hljs-string">{this.getAttribute(</span>&#x27;<span class="hljs-attr">text</span>&#x27;)} /&gt;</span></span>;
  }

  <span class="hljs-title function_">copyRef</span>(<span class="hljs-params">refAttr</span>) {
    <span class="hljs-keyword">for</span>(<span class="hljs-keyword">const</span> [key, val] <span class="hljs-keyword">of</span> <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">entries</span>(refAttr)) {
        <span class="hljs-variable language_">this</span>[key] = val;
      } <span class="hljs-comment">// could also do this.ref = refAttr if worried about colliding with another property.</span>
  }
}

customElements.<span class="hljs-title function_">define</span>(<span class="hljs-string">&#x27;text-switcher&#x27;</span>, <span class="hljs-title class_">TextSwitcher</span>);
</code></pre><p><text-switcher id="world-switch" class="block box margin-block"></text-switcher></p>
<p>Then we can add a <code>&lt;text-switcher id=&quot;world-switch&quot;&gt;&lt;/text-switcher&gt;</code> element anywhere on our page.  We no longer have placed anything on the window object, so we can place many of these on a single page!  Notice that a <code>text</code> attribute on our component above provides the default text for our App component, so we can set the default when it first loads!  We can update any of them by targeting/finding it, then calling it&#39;s update method. You can change the text-switcher above by typing the following into your browser&#39;s dev console:</p>
<pre><code class="hljs language-javascript"><span class="hljs-keyword">const</span> elem = <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">&#x27;#world-switch&#x27;</span>);
elem.<span class="hljs-title function_">update</span>(<span class="hljs-string">&#x27;Hello World!&quot;);</span>
</code></pre><p>  Maybe this example is still a bit contrived.  It wouldn&#39;t be hard to change the text inside here without React or a Custom Element.  Hopefully you can come up with places where you <em>need</em> the interactivity React provides.  I think this approach would work there too.  On pages where you need interactivity, but a lot of the page is static or basic form submitting, this approach would shine.</p>
<p>  It might be overkill for something like a spreadsheet application, where the whole page is so reactive that you don&#39;t need to add the Custom Element layer.  If you wanted to though, you could serve a page with a single <code>&lt;spread-sheet /&gt;</code> element in the body that handled mounting React and doing all the things you&#39;d normally do <em>before</em> rendering React to the web page. Still, it might be some work to get bundling and tooling working how you want with this approach.</p>
<p>  Can you identify the principle I&#39;m hoping to illustrate here?  We can build web pages <a href="https://htmx.org/essays/hypermedia-on-whatever-youd-like/">however we want</a> and still take advantage of React.  By making React into an implementation detail, instead of what&#39;s driving our design decisions, we&#39;ve opened up a lot more possibility to reuse these components, mix and match how we do reactivity, and generally take more advantage of all the features a browser already gives us.  We can <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals">integrate with forms</a> directly.  We can port complicated controls to simpler websites because we have written them in a way that fits right into the markup.</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Mon, 13 Jan 2025 00:00:00 GMT</pubDate>
      <title>Breaking the React Wall</title>
      <link>https://benkenawell.com/p/2025-01-13-breaking-the-react-wall.html</link>
      <guid>https://benkenawell.com/p/2025-01-13-breaking-the-react-wall.html</guid>
      <description>&lt;p&gt;I&amp;#39;ve [written before]({% post_url 2024-12-14-react-stepper %}) about using &lt;code&gt;useImperativeHandle&lt;/code&gt; to hoist functionality up a layer of React componentry. &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Breaking the React Wall</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  <script type="module" src="/scripts/breaking-react-wall.js"></script>
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Breaking the React Wall</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2025-01-13T00:00:00+00:00">Jan 13th, 2025</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>I&#39;ve <a href="/p/2024-12-14-react-stepper.html">written before</a> about using <code>useImperativeHandle</code> to hoist functionality up a layer of React componentry. <!--more-->  It helps to better encapsulate any state that component defines, without needing to expose it directly to the consumer.</p>
<p>You can use this same feature to &quot;exit&quot; React!  If your top level component exposes a <code>ref</code>, you can access that with a ref callback!  The simplest implementation, where our top level component is named App, looks like:</p>
<pre><code class="hljs language-tsx"><span class="hljs-keyword">const</span> <span class="hljs-title class_">App</span> = <span class="hljs-title class_">React</span>.<span class="hljs-title function_">forwardRef</span>(<span class="hljs-keyword">function</span> (<span class="hljs-params">props, ref</span>) {
  <span class="hljs-keyword">const</span> [msg, setMsg] = <span class="hljs-title function_">useState</span>(props.<span class="hljs-property">defaultText</span> || <span class="hljs-string">&quot;Hi there!&quot;</span>);
    <span class="hljs-title function_">useImperativeHandle</span>(ref, <span class="hljs-function">() =&gt;</span> ({
      <span class="hljs-title function_">update</span>(<span class="hljs-params">newMsg</span>) { <span class="hljs-title function_">setMsg</span>(newMsg) }
    }), [setMsg]);
  <span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>{msg}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>;
});

<span class="hljs-keyword">const</span> renderRoot = <span class="hljs-title class_">ReactDOM</span>.<span class="hljs-title function_">createRoot</span>(<span class="hljs-variable language_">document</span>.<span class="hljs-title function_">querySelector</span>(<span class="hljs-string">&#x27;#react-root&#x27;</span>);
<span class="hljs-keyword">function</span> <span class="hljs-title function_">refFunc</span>(<span class="hljs-params"><span class="hljs-attr">refAttr</span>: <span class="hljs-title class_">RefAttributes</span> | <span class="hljs-literal">null</span></span>) {
    <span class="hljs-comment">// attach it directly to the window for our example</span>
    <span class="hljs-variable language_">window</span>.<span class="hljs-property">refHandle</span> = refAttr;
}
renderRoot.<span class="hljs-title function_">render</span>(<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">App</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">{refFunc}</span> /&gt;</span></span>);
</code></pre><p>Then running <code>window.refHandle.update(&quot;Hello World&quot;);</code> in the console updates the text within App!  If you try it in your console window on this page, the text in the box below will change!</p>
<div id="react-root" class="box"></div>

<p>It&#39;s a very simple example, but it shows that we can get React to update directly, within it&#39;s lifecycle but outside of the React context, while staying within React&#39;s guidelines.  I&#39;ve seen it talked about very little!  If it seems trivial here, it could turn a lot of React paradigms on their heads.  Most React libraries feel like they try to fit the web <em>into React&#39;s paradigm,</em> instead of augmenting their webpages with something as powerful as React.  Even more powerful than how frameworks like Next.JS do it today, we can allow React to do what it&#39;s best at (reacting to changes) and give us an escape hatch to also interact with other elements in the browser.  Just like that, React can easily be made to create <a href="https://www.patterns.dev/vanilla/islands-architecture">&quot;islands of interactivity&quot;</a></p>
<p>In more complicated &quot;App&quot; components, using an imperative handle means we don&#39;t need to worry about calling &quot;render&quot; again. The functions we expose act inside the React lifecycle, even though we&#39;re calling them from outside.  The same thing could be achieved by placing functions on any element with in the ref, but by able to &quot;come out the top&quot; like this, we can place react DOM render roots anywhere we want, or isolate our consumer from React entirely by <a href="/p/2025-01-20-react-custom-element.html">wrapping our component in a Custom Element</a>.</p>
<p>It&#39;s important that we aren&#39;t just copying out a reference to a setState function or something like that.  While that technically works, it breaks a lot of assumptions React and most developers would make about your code.  It&#39;s a good way to cause a mess!  Comparatively, using an imperative handle is something React gives us, and it exposes functionality in a way that we can easily see what&#39;s going on. Data still flows one way, callbacks are the primary method of interacting upstream, and React can dynamically update the handle if we need.  The ref gives us a way to encapsulate some imperative logic as well!</p>
<p><a href="https://react.dev/reference/react/useRef#manipulating-the-dom-with-a-ref">Focusing an element</a> is the classic example of when we might need or want to use some imperative logic, referencing the DOM directly in React.  <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog">Dialogs</a> also use this technique to call show and close.  <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#indeterminate_state_checkboxes">Checkbox</a> even uses imperative logic to set its indeterminate state.</p>
<p>We can create a more seamless experience within React by mimicking these types of interactions, ones already available to us on the DOM.  Developers can utilize their knowledge of the platform their on, or learn about it, while also using and learning our enhancements and new components.  We aren&#39;t limited by what web browsers give us, and we can use React in its full power to write updates to screen, based on one way data bindings.</p>
<hr>
<p>PS: You can see the script used in your dev tools, or at <a href="/scripts/breaking-react-wall.js">/scripts/breaking-react-wall.js</a>.  It looks a little different than the above script because it&#39;s in React 19, strictly javascript, doesn&#39;t use JSX, and I&#39;m vendoring react and react-dom.</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Sat, 14 Dec 2024 00:00:00 GMT</pubDate>
      <title>Stepper: React&apos;s useImperativeHandle</title>
      <link>https://benkenawell.com/p/2024-12-14-react-stepper.html</link>
      <guid>https://benkenawell.com/p/2024-12-14-react-stepper.html</guid>
      <description>&lt;style&gt;
  details {
    background-color: var(--bg);
    border: none;
    padding: 0;
    &gt; summary {
      font-weight: unset;
      &amp;::marker {
        content: &quot;+ &quot;;
        font-weight: bold;
      }
    }
    &amp;[open] {
      border: 1px solid var(--graphical-fg);
      &gt; summary {
        border: 1px solid var(--graphical-fg);
        &amp;::marker {
        content: &quot;- &quot;;
        font-weight: unset;
        }
      }
    }
  }
&lt;/style&gt;

&lt;p&gt;State encapsulation is pretty good in React: one way data binding via props and callbacks for notifying of changing events.  But how do I change the state of a component from outside it&amp;#39;s declaration? &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Stepper: React's useImperativeHandle</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Stepper: React's useImperativeHandle</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2024-12-14T00:00:00+00:00">Dec 14th, 2024</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<style>
  details {
    background-color: var(--bg);
    border: none;
    padding: 0;
    > summary {
      font-weight: unset;
      &::marker {
        content: "+ ";
        font-weight: bold;
      }
    }
    &[open] {
      border: 1px solid var(--graphical-fg);
      > summary {
        border: 1px solid var(--graphical-fg);
        &::marker {
        content: "- ";
        font-weight: unset;
        }
      }
    }
  }
</style>

<p>State encapsulation is pretty good in React: one way data binding via props and callbacks for notifying of changing events.  But how do I change the state of a component from outside it&#39;s declaration? <!--more--></p>
<details open>
<summary>For example, I have a Stepper component that shows one of its children at a time, where the Stepper component manages the state.
</summary>
<div class="grid grid-variable-cols" markdown="1">
<div markdown="1" data-cols="1">

<pre><code class="hljs language-tsx">&lt;<span class="hljs-title class_">Stepper</span>&gt;
  <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>Step 1<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>Step 2<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
&lt;/<span class="hljs-title class_">Stepper</span>&gt;
</code></pre></div><div markdown="1" data-cols="2" style="overflow-x: overlay">

<pre><code class="hljs language-tsx"><span class="hljs-keyword">function</span> <span class="hljs-title function_">Stepper</span>(<span class="hljs-params">{children}</span>) {
    <span class="hljs-keyword">const</span> [step, setStep] = <span class="hljs-title function_">useState</span>(<span class="hljs-number">0</span>);
    <span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>{React.Children.toArray(children)[step]}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>;
}
</code></pre></div>
</div>
</details>

<p class="list-of-links" markdown="1">

<p>That&#39;s great, but how do I get the stepper to move to the next step?  As a junior developer, I would have started with <a href="#step-prop">making <code>step</code> a prop</a>, or <a href="#ui-set">enforced some UI for the buttons</a>.  As a more experience developer, I would have allowed you <a href="#ui-prop">to pass components</a> for the UI, <a href="#step-hook">wrote a hook</a> or made <a href="#context">a Context and Context aware children</a>.</p>
</p>

<p>But with the experience I have now, I&#39;m suggesting you expose state changes on <a href="https://react.dev/reference/react/useRef">the ref</a> of the Stepper component, giving the devs using your stepper component <a href="#refs">an imperative way to effect the Stepper&#39;s internal state</a>.  No component needs to be aware of where they are in the DOM or the React VDOM, and the component calling the Stepper component can coodinate stepping through without needing to know the details.  Below I have examples of nearly all of these options.  Maybe you&#39;ll look at some and see patterns that you&#39;ve introduced.  Or patterns you&#39;ve struggled to work with.  Maybe you won&#39;t agree with my final result, or my reasoning, but I hope I can at least give you some familiarity with a feature in React I don&#39;t see very often: <a href="https://react.dev/reference/react/useImperativeHandle"><code>useImperativeHandle</code></a>.</p>
<h2 id="options">Options</h2>
<h3 id="forced-ui">Forced UI</h3>
<details id="ui-set" markdown="1">
<summary>The Stepper could have UI to move between steps</summary>

<pre><code class="hljs language-tsx"><span class="hljs-keyword">function</span> <span class="hljs-title function_">Stepper</span>(<span class="hljs-params">{children}</span>) {
    <span class="hljs-keyword">const</span> [step, setStep] = <span class="hljs-title function_">useState</span>(<span class="hljs-number">0</span>);
    <span class="hljs-keyword">const</span> nextStep = <span class="hljs-title function_">useCallback</span>(<span class="hljs-function">() =&gt;</span> <span class="hljs-title function_">setStep</span>(<span class="hljs-function"><span class="hljs-params">step</span> =&gt;</span> step + <span class="hljs-number">1</span>), [setStep]);
    <span class="hljs-keyword">const</span> prevStep = <span class="hljs-title function_">useCallback</span>(<span class="hljs-function">() =&gt;</span> <span class="hljs-title function_">setStep</span>(<span class="hljs-function"><span class="hljs-params">step</span> =&gt;</span> step - <span class="hljs-number">1</span>), [setStep]);
    <span class="hljs-keyword">return</span> (<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        {React.Children.toArray(children)[step]}
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{prevStep}</span>&gt;</span><span class="hljs-symbol">&amp;lt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{nextStep}</span>&gt;</span><span class="hljs-symbol">&amp;gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>);
}
</code></pre></details>
<details id="ui-prop" markdown="1">
<summary>
but maybe I don't want to define UI for that.  I could mandate UI, but let the user pass in the components:
</summary>

<pre><code class="hljs language-tsx"><span class="hljs-keyword">function</span> <span class="hljs-title function_">Stepper</span>(<span class="hljs-params">{children, nextButton: NextButton, prevButton: PrevButton}</span>) {
    <span class="hljs-keyword">const</span> [step, setStep] = <span class="hljs-title function_">useState</span>(<span class="hljs-number">0</span>);
    <span class="hljs-keyword">const</span> nextStep = <span class="hljs-title function_">useCallback</span>(<span class="hljs-function">() =&gt;</span> <span class="hljs-title function_">setStep</span>(<span class="hljs-function"><span class="hljs-params">step</span> =&gt;</span> step + <span class="hljs-number">1</span>), [setStep]);
    <span class="hljs-keyword">const</span> prevStep = <span class="hljs-title function_">useCallback</span>(<span class="hljs-function">() =&gt;</span> <span class="hljs-title function_">setStep</span>(<span class="hljs-function"><span class="hljs-params">step</span> =&gt;</span> step - <span class="hljs-number">1</span>), [setStep]);
    <span class="hljs-keyword">return</span> (<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        {React.Children.toArray(children)[step]}
        <span class="hljs-tag">&lt;<span class="hljs-name">PrevButton</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{prevStep}</span>&gt;</span><span class="hljs-symbol">&amp;lt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">PrevButton</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">NextButton</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{nextStep}</span>&gt;</span><span class="hljs-symbol">&amp;gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">NextButton</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>);
}
</code></pre></details>
Which will help a bit, but those components aren't very flexible. Placement can't be changed and they can't really be part of the content.  If I wanted to have them be before/after I could add a prop, but I could add _a lot_ of props to make it more flexible.. or try a different approach entirely.

<h3 id="hooks-and-props">Hooks and Props</h3>
<details id="step-prop" markdown="1">
<summary>
If the step were a prop, and I just listen for the change, then there's no UI to worry about.
</summary>

<pre><code class="hljs language-tsx"><span class="hljs-keyword">function</span> <span class="hljs-title function_">Stepper</span>(<span class="hljs-params">{children, step}</span>) {
    <span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>{React.Children.toArray(children)[step]}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>;
}
<span class="hljs-comment">// usage</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">SteppedComponent</span> (<span class="hljs-params"></span>) {
    <span class="hljs-keyword">const</span> [step, setStep] = <span class="hljs-title function_">useStep</span>(<span class="hljs-number">0</span>);
    <span class="hljs-keyword">const</span> nextStep = <span class="hljs-title function_">useCallback</span>(<span class="hljs-function">() =&gt;</span> <span class="hljs-title function_">setStep</span>(<span class="hljs-function"><span class="hljs-params">step</span> =&gt;</span> step + <span class="hljs-number">1</span>), [setStep]);
    <span class="hljs-keyword">const</span> prevStep = <span class="hljs-title function_">useCallback</span>(<span class="hljs-function">() =&gt;</span> <span class="hljs-title function_">setStep</span>(<span class="hljs-function"><span class="hljs-params">step</span> =&gt;</span> step - <span class="hljs-number">1</span>), [setStep]);
    <span class="hljs-keyword">return</span> (
        <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Stepper</span> <span class="hljs-attr">step</span>=<span class="hljs-string">{step}</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
                Slide 1
                <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{nextStep}</span>&gt;</span>
                    Next Step
                <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
                Slide 2
                <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> {stepperRef.current?.prevStep()}}&gt;
                    Previous Step
                <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Stepper</span>&gt;</span></span>
    );
}
</code></pre></details>
but now this Stepper component doesn't really do anything. You still have to maintain the correct state outside of it, remember all the rules for changing steps, and reimplement this every time you use a Stepper.

<details id="step-hook" markdown="1">
<summary>
I could have a hook, or pair the Stepper with a hook.  Now the person using Stepper doesn't need to know all the rules.
</summary>

<pre><code class="hljs language-tsx"><span class="hljs-keyword">function</span> <span class="hljs-title function_">Stepper</span>(<span class="hljs-params">{children, step}</span>) {
    <span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>{React.Children.toArray(children)[step]}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>;
}
<span class="hljs-keyword">function</span> <span class="hljs-title function_">useSteps</span>(<span class="hljs-params">defaultStep = <span class="hljs-number">0</span></span>) {
    <span class="hljs-keyword">const</span> [step, setStep] = <span class="hljs-title function_">useStep</span>(<span class="hljs-number">0</span>);
    <span class="hljs-keyword">const</span> nextStep = <span class="hljs-title function_">useCallback</span>(<span class="hljs-function">() =&gt;</span> <span class="hljs-title function_">setStep</span>(<span class="hljs-function"><span class="hljs-params">step</span> =&gt;</span> step + <span class="hljs-number">1</span>), [setStep]);
    <span class="hljs-keyword">const</span> prevStep = <span class="hljs-title function_">useCallback</span>(<span class="hljs-function">() =&gt;</span> <span class="hljs-title function_">setStep</span>(<span class="hljs-function"><span class="hljs-params">step</span> =&gt;</span> step - <span class="hljs-number">1</span>), [setStep]);
    <span class="hljs-keyword">return</span> [step, {nextStep, prevStep}];
}
<span class="hljs-comment">// usage</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">SteppedComponent</span> (<span class="hljs-params"></span>) {
    <span class="hljs-keyword">const</span> [step, {nextStep, prevStep}] = useSteps;
    <span class="hljs-keyword">return</span> (
        <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Stepper</span> <span class="hljs-attr">step</span>=<span class="hljs-string">{step}</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
                Slide 1
                <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{nextStep}</span>&gt;</span>
                    Next Step
                <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
                Slide 2
                <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> {stepperRef.current?.prevStep()}}&gt;
                    Previous Step
                <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Stepper</span>&gt;</span></span>
    );
}
</code></pre></details>

<p>but they still need to remember the hook! There&#39;s a lot of manual coordination needed with this approach.  I could use <em>only</em> a hook, but there is <em>some UI</em> part involved in my Stepper Component in this example. It only shows one child at a time. I think it makes sense to render it as part of the React Tree.  What if, instead, I passed information down the tree? </p>
<h3 id="context">Context</h3>
<details markdown="1">
<summary>
My Stepper component could be a Provider and coordinate that way.
</summary>

<pre><code class="hljs language-tsx"><span class="hljs-keyword">const</span> <span class="hljs-title class_">StepperContext</span> = <span class="hljs-title function_">createContext</span>({<span class="hljs-attr">step</span>: <span class="hljs-number">0</span>, <span class="hljs-title function_">nextStep</span>(<span class="hljs-params"></span>) {}, <span class="hljs-title function_">prevStep</span>(<span class="hljs-params"></span>) {}});
<span class="hljs-keyword">const</span> useStepperContext = <span class="hljs-title function_">useContext</span>(<span class="hljs-title class_">StepperContext</span>);
<span class="hljs-keyword">function</span> <span class="hljs-title function_">Stepper</span>(<span class="hljs-params">{children, step}</span>) {
    <span class="hljs-keyword">const</span> [step, setStep] = <span class="hljs-title function_">useStep</span>(<span class="hljs-number">0</span>);
    <span class="hljs-keyword">const</span> nextStep = <span class="hljs-title function_">useCallback</span>(<span class="hljs-function">() =&gt;</span> <span class="hljs-title function_">setStep</span>(<span class="hljs-function"><span class="hljs-params">step</span> =&gt;</span> step + <span class="hljs-number">1</span>), [setStep]);
    <span class="hljs-keyword">const</span> prevStep = <span class="hljs-title function_">useCallback</span>(<span class="hljs-function">() =&gt;</span> <span class="hljs-title function_">setStep</span>(<span class="hljs-function"><span class="hljs-params">step</span> =&gt;</span> step - <span class="hljs-number">1</span>), [setStep]);
    <span class="hljs-keyword">return</span> (
        <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">StepperContext.Provider</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{</span> {<span class="hljs-attr">step</span>, <span class="hljs-attr">nextStep</span>, <span class="hljs-attr">prevStep</span>} }&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
                {React.Children.toArray(children)[step]}
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">StepperContext.Provider</span>&gt;</span></span>);
}
<span class="hljs-comment">// usage</span>
<span class="hljs-comment">/** context aware child */</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">StepperChildOne</span>(<span class="hljs-params"></span>) {
    <span class="hljs-keyword">const</span> {nextStep} = <span class="hljs-title function_">useStepperContext</span>(); 
    <span class="hljs-keyword">return</span>(<span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        Slide 1
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{nextStep}</span>&gt;</span>
            Next Step
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>);
}
<span class="hljs-keyword">function</span> <span class="hljs-title function_">SteppedComponent</span> (<span class="hljs-params"></span>) {
    <span class="hljs-keyword">const</span> [step, {nextStep, prevStep}] = useSteps;
    <span class="hljs-keyword">return</span> (
        <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Stepper</span> <span class="hljs-attr">step</span>=<span class="hljs-string">{step}</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">StepperChildOne</span> /&gt;</span>
            /* function as child */
            <span class="hljs-tag">&lt;<span class="hljs-name">StepperContext.Consumer</span>&gt;</span>{({prevStep}) =&gt; (
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
                    Slide 2
                    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> {stepperRef.current?.prevStep()}}&gt;
                        Previous Step
                    <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>)}
            <span class="hljs-tag">&lt;/<span class="hljs-name">StepperContext.Consumer</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Stepper</span>&gt;</span></span>
    );
}
</code></pre></details>

<p>This limits updates to children, but lessens the coordination between components.  The children need to know to access my Context, making them less independent.  They&#39;re only useful <em>in this Context</em>.  I could have an army of Stepper-aware components, or ones to help coordinate, but no approach is going to capture every possible look for a UI. And what if I want them changing automatically on an interval?</p>
<div class="box plain packed" style="font-size: 0.8em" markdown="1">
Technically, Context.Consumer would allow updates this component, but React and I agree we don't like this approach. They call it [the old way](https://react.dev/reference/react/createContext#consumer), I think it clutters the component.
</div>

<p>So I want:</p>
<ul>
<li>The children to be blissfully unaware about the Stepper component,</li>
<li>the component using the Stepper to coordinate changing steps</li>
<li>without the component using my Stepper to write too much boilerplate.</li>
</ul>
<p>If I&#39;m writing components <a href="/p/2024-08-05-react-eventing-like-the-dom.html">like the DOM</a>, then all children should have events/callback I can access for the various things they can do.  Can I use those?  Can I plug in to what the children of my Stepper should already have available?  But what does the component do to change the step of the Stepper on those events?</p>
<h3 id="events">Events</h3>
<p>I don&#39;t want to use event bubbling, because what event would I listen for?  Clicks or form submissions are the obvious answer, but I usually call <code>stopProgation</code> on forms and clicks could be any number of interactions where I <em>don&#39;t</em> want to change the step.</p>
<p>Something meant for the Stepper itself would be better.  But with custom events, my children still need to know if they&#39;re in the context of a Stepper.  And the dev writing using the Stepper component would need to know what event to fire!</p>
<p>This is not as big an issue in plain HTML. With templates you have a better understanding of the flow/make up of your page. For actions on the component, you can set methods on the component directly to call.  In React, the closest thing to that is the ref and it&#39;s imperative handle.</p>
<h3 id="refs">Refs</h3>
<p>Imperative changes allow us to explicitly (not implicitly or declaratively, like props) tell our object to do something.  Think of <a href="https://react.dev/learn/manipulating-the-dom-with-refs#example-focusing-a-text-input">focusing an element</a>, it imperatively tells the browser to set focus on that element.</p>
<p>With our Stepper component, we want the control to tell it when to move to the next or previous step.  All the builtin components have refs where we can do these types of action, but how do we set up a component for imperative changes?  </p>
<details open markdown="1">
<summary>
Expose a ref via `forwardRef` and attach actions to it via `useImperativeHandle`:
</summary>

<pre><code class="hljs language-tsx"><span class="hljs-comment">// makes typescript happier and Intellisense easier to follow</span>
<span class="hljs-keyword">type</span> <span class="hljs-title class_">StepperRef</span> = {
    <span class="hljs-title function_">nextStep</span>(): <span class="hljs-built_in">void</span>;
    <span class="hljs-title function_">prevStep</span>(): <span class="hljs-built_in">void</span>;
};
<span class="hljs-keyword">const</span> <span class="hljs-title class_">Stepper</span> = <span class="hljs-title function_">forwardRef</span>(<span class="hljs-function">(<span class="hljs-params">{children}, <span class="hljs-attr">ref</span>: <span class="hljs-title class_">StepperRef</span></span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> [step, setStep] = <span class="hljs-title function_">useStep</span>(<span class="hljs-number">0</span>);
    <span class="hljs-title function_">useImperativeHandle</span>(ref, <span class="hljs-function">() =&gt;</span> ({
        <span class="hljs-title function_">nextStep</span>(<span class="hljs-params"></span>) { <span class="hljs-title function_">setStep</span>(<span class="hljs-function"><span class="hljs-params">step</span> =&gt;</span> step + <span class="hljs-number">1</span>); },
        <span class="hljs-title function_">prevStep</span>(<span class="hljs-params"></span>) { <span class="hljs-title function_">setStep</span>(<span class="hljs-function"><span class="hljs-params">step</span> =&gt;</span> step - <span class="hljs-number">1</span>); }
    }), [setStep]); <span class="hljs-comment">// setStep doesn&#x27;t change, so this a very stable reference</span>
    <span class="hljs-keyword">return</span> <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>{React.Children.toArray(children)}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>;
});
<span class="hljs-comment">// Usage:</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">SteppedComponent</span>(<span class="hljs-params"></span>) {
    <span class="hljs-keyword">const</span> stepperRef = useRef&lt;<span class="hljs-title class_">StepperRef</span> | <span class="hljs-literal">null</span>&gt;();
    <span class="hljs-keyword">return</span> (
        <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Stepper</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">{stepperRef}</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
                Step 1
                <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> {stepperRef.current?.nextStep()}}&gt;
                    Next Step
                <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
                Step 2
                <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> {stepperRef.current?.prevStep()}}&gt;
                    Previous Step
                <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Stepper</span>&gt;</span></span>
    );
}
</code></pre></details>

<h2 id="in-conclusion">In Conclusion</h2>
<p>Now I can have buttons, form submissions, timeouts, intervals, you name it, this Stepper can handle it.  Just tell it when to move to the previous or next step.  Bring Your Own Event, so to speak, because Stepper doesn&#39;t prescribe it.  SteppedComponent doesn&#39;t coordinate any state. There&#39;s very little boilerplate.</p>
<p>Stepper can tightly control the behavior of state changes.  In my examples, I don&#39;t have bounds on next/previous steps, so you can &quot;go too far&quot;.  But we could easily make it wrap, or just stay on the last/first step.  Or call a callback passed to Stepper when we&#39;re at the end.  Stepper could let SteppedComponent set the step explicitly by exposing a <code>setStep</code> function on our imperative handle.  Stepper props can be used to define the types of behavior the steps should have, instead of necessitating SteppedComponent making that logic.</p>
<p>It opens up a world of possibilities, and the only code we&#39;ve added from our beginning example is a <code>useRef</code> outside the component and a <code>useImperativeHandle</code> inside the component.  This is behavior that will need to be documented, but in a similar way to how props are documented.  It isn&#39;t as opaque as Context or CustomEvents.</p>
<p>React&#39;s docs say you should limit your use of imperative code and prefer declarative instead. At the <a href="https://react.dev/reference/react/useImperativeHandle">bottom of their <code>useImperativeHandle</code> page</a>, they even point towards using Effects instead.  But some actions need to be done imperatively.  Again from React&#39;s site: &quot;focusing a node, triggering an animation, selecting text and so on&quot;.  I think this type of action and state encapsulation benefits from the use of some imperative code, exposed on a ref via <code>useImperativeHandle</code>.</p>
<hr>
<h2 id="taking-it-further">Taking it Further</h2>
<p>More Ref examples, with the same Stepper Component.  In these examples I assume we&#39;ve expanded the functionality beyond my example Stepper above.</p>
<details markdown="1">
<summary>Next step on interval</summary>

<pre><code class="hljs language-tsx"><span class="hljs-comment">// move to the next slide every 1 second. Assumes wrapping behavior of Stepper</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">SteppedComponent</span>(<span class="hljs-params"></span>) {
    <span class="hljs-keyword">const</span> interval = useRef&lt;<span class="hljs-built_in">number</span> | <span class="hljs-literal">null</span>&gt;();
    <span class="hljs-keyword">const</span> stepperRef = <span class="hljs-title function_">useCallback</span>(<span class="hljs-function">(<span class="hljs-params">ref</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span>(!!ref) interval.<span class="hljs-property">current</span> = <span class="hljs-built_in">setInterval</span>(<span class="hljs-function">() =&gt;</span> ref.<span class="hljs-title function_">nextStep</span>(), <span class="hljs-number">1000</span>);
        <span class="hljs-keyword">else</span> {
            <span class="hljs-built_in">clearInterval</span>(interval.<span class="hljs-property">current</span>);
            interval.<span class="hljs-property">current</span> = <span class="hljs-number">0</span>;
        }
    });
    <span class="hljs-keyword">return</span> (
        <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Stepper</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">{stepperRef}</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>Step 1<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>Step 2<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">Stepper</span>&gt;</span></span>
}
</code></pre></details>
<details markdown="1">
<summary>onComplete handler</summary>

<p>Perform an action after reaching the last step of the Stepper. Assumes an onComplete handler.  In this example, TextboxPreview&#39;s onConfirm could also call onSubmit directly, but I like the fluidity of this approach.  I like being able to look at what happens when the Stepper completes separately from how it gets there. And since the last step is necessarily the last child, I already know where to look (this is an implementation detail I suppose, but with arrays being so commonplace, intuitive to most users)</p>
<pre><code class="hljs language-tsx"><span class="hljs-keyword">function</span> <span class="hljs-title function_">ConfirmedTextSubmission</span>(<span class="hljs-params">{onSubmit}</span>) {
    <span class="hljs-keyword">const</span> stepperRef = useRef&lt;<span class="hljs-title class_">StepperRef</span> | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>);
    <span class="hljs-keyword">const</span> [preview, setPreview] = <span class="hljs-title function_">useState</span>(<span class="hljs-string">&#x27;&#x27;</span>);
    <span class="hljs-keyword">const</span> [hidden, setHidden] = <span class="hljs-title function_">useState</span>(<span class="hljs-literal">false</span>);
    <span class="hljs-keyword">return</span> (
        <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">hidden</span>=<span class="hljs-string">{hidden}</span>
            &lt;<span class="hljs-attr">Stepper</span> <span class="hljs-attr">onComplete</span>=<span class="hljs-string">{()</span> =&gt;</span> { onSubmit(preview); }}&gt;
                <span class="hljs-tag">&lt;<span class="hljs-name">TextBoxForm</span> 
                    <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{(text)</span> =&gt;</span> {
                        setPreview(text);
                        stepperRef.current?.nextStep();
                        }}
                /&gt;
                <span class="hljs-tag">&lt;<span class="hljs-name">TextBoxPreview</span> <span class="hljs-attr">onConfirm</span>=<span class="hljs-string">{()</span> =&gt;</span> { stepperRef.current?.nextStep()}}&gt;
                    {preview}
                <span class="hljs-tag">&lt;/<span class="hljs-name">TextBoxPreview</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Stepper</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    );
}
</code></pre></details>

</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Thu, 12 Dec 2024 00:00:00 GMT</pubDate>
      <title>Node, TLS, and DNS</title>
      <link>https://benkenawell.com/p/2024-12-12-node-tls-dns.html</link>
      <guid>https://benkenawell.com/p/2024-12-12-node-tls-dns.html</guid>
      <description>&lt;p&gt;Node presents different problems when using local domain names. &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Node, TLS, and DNS</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Node, TLS, and DNS</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2024-12-12T00:00:00+00:00">Dec 12th, 2024</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>Node presents different problems when using local domain names. <!--more--> In <a href="/p/2024-12-10-reverse-proxy-with-caddy.html">my previous post about reverse proxying</a>, I was able to connect to all my local services with verified domain names. But when I went to have my frontend address my backend as &quot;backend.localhost&quot; instead of &quot;localhost:3256&quot;, I quickly learned that Node works very differently from the web browser. </p>
<h3 id="ssl-certs">SSL Certs</h3>
<p>For starters, Node uses it&#39;s own hard coded list of root SSL certificates. The only way to add to them is the NODE_EXTRA_CA_CERTS environment variable, which would need to be set with every command! So I&#39;m exporting that from my .zshrc file. It makes it harder to share the configuration, but I can live with it. </p>
<h3 id="dns-resolution">DNS resolution</h3>
<p>Then I found node won&#39;t resolve *.localhost DNS queries to the local loopback the same way my browser will. The quick and easy way is to edit the /etc/hosts file, and feel free to do that if you want.</p>
<p>But I decided to try a second time to run <a href="https://pi-hole.net/">PiHole</a>. Knowing I couldn&#39;t publish port 53 from docker on my Mac, I booted up a Debian VM, configured a little bridge network, and I&#39;m now running PiHole off that. I set my DNS resolution to that VM&#39;s IP address and I have a slick little solution on my laptop! Debian is running in Low Memory Mode, but PiHole doesn&#39;t seem to be skipping a beat. I&#39;d call this a good solution for my local problem, but I plan to share the hosts file solution with my coworkers, or take it all a step further and put it into docker to use that name resolution.</p>
<h3 id="vercel-serverless">Vercel serverless</h3>
<p>Now that Node can find my services, and trusts them, I thought I&#39;d be all set! Then those Vercel serverless functions started complaining to me again. Turns out, I no longer need to rewrite the Host header! Because I made the VERCEL_URL my frontend.localhost, so that&#39;s the Host header Vercel expects.  That actually makes my reverse proxy setup so much cleaner. </p>
<h3 id="conclusion">Conclusion</h3>
<p>My conclusion is that none of this was difficult to set up once, but it doesn&#39;t scale or translate as well to my coworkers&#39; computers. It&#39;s a lot more work than just <code>docker compose up</code>, which is what I was hoping to achieve. As it is now, my coworkers&#39; would need to know a little bit more about networking than I wanted them to need to know.  A solution to that might be more Docker.</p>
<p>If I containerized all the dev services, I could probably share the Caddy cert via docker volume (No more manual envvar). I could change the reverse proxy to work on container names, instead of localhost:port. Would I still need DNS? It&#39;s maybe the largest problem, since I know I can&#39;t publish the DNS port to my Mac, full stop. But maybe if I took advantage of docker&#39;s networking name resolution, node would get it. Then my inter-container API calls would still be to a different name than accessing from the same host name as my web browser (still going through the reverse proxy), but that might be okay too.  Would SSL resolution work between docker containers?  That isn&#39;t something I&#39;ve tried.</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Tue, 10 Dec 2024 00:00:00 GMT</pubDate>
      <title>Reverse Proxy With Caddy</title>
      <link>https://benkenawell.com/p/2024-12-10-reverse-proxy-with-caddy.html</link>
      <guid>https://benkenawell.com/p/2024-12-10-reverse-proxy-with-caddy.html</guid>
      <description>&lt;p&gt;I&amp;#39;ve never set up a reverse proxy before. &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Reverse Proxy With Caddy</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Reverse Proxy With Caddy</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2024-12-10T00:00:00+00:00">Dec 10th, 2024</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>I&#39;ve never set up a reverse proxy before. <!--more--> The only thing I&#39;ve done with Nginx is set up a simple file server for local development, based straight on their docker documentation. </p>
<p>But I wanted to give my local development environments <em>names.</em> To make them fel like real websites. My backed could live at &quot;backend&quot; instead of &quot;localhost:3256&quot;. My frontend at &quot;frontend&quot; and so on.</p>
<p>There always felt like so many moving pieces. The easiest method, modifying <code>/etc/hosts</code> didn&#39;t solve it all. It would be hard to maintain, impossible to share with my coworkers, no SSL certificate, and I still needed port numbers. I needed a process that was easy to stand up, share, and felt magical without being too complicated. </p>
<p>Part of the reason I&#39;m writing this blog is to centralize some of my most interesting or frustrating technical moments.  So I&#39;m writing this to help me recall this set up and hopefully for you to learn something from.</p>
<h2 id="my-journey">My Journey</h2>
<h3 id="dns">DNS</h3>
<p>I started the day trying to run <a href="https://pi-hole.net/">pihole</a> to resolve my services&#39; names.  I got pihole up and running in docker only to learn that port 53 is already used in a Mac. Turns out the Bonjour service uses it, so I can&#39;t run my DNS locally! In the end, it didn&#39;t matter. Web browsers will resolve anything ending in <code>.localhost</code> to the loopback interface anyway. Great, problem solved!  Along the way, I realized all my services run at different ports. But web browsers only ever connect at one port, 80 or 443. And while I could maybe use SRV records that seemed overly complicated, when I couldn&#39;t even get pihole running. That&#39;s when I knew I needed a <em>reverse proxy</em>.</p>
<h3 id="the-reverse-proxy">The Reverse Proxy</h3>
<p>A reverse proxy could listen to ports 80 and 443, then send that traffic to the right service running on a different port.  Since all the services will have names, the reverse proxy can use those to decide where to route the traffic.</p>
<p>I knew I didn&#39;t understand Nginx&#39;s configuration very well. Traefik&#39;s auto discovery bewilders me. Then I saw a two line <a href="https://caddyserver.com/docs/caddyfile/patterns#reverse-proxy">Caddyfile for a reverse proxy</a> and thought, &quot;hmm, that&#39;s so easy!&quot; It still took me an afternoon of tweaks though. The container networking had to be set to <code>host</code> so Caddy could proxy to ports on my local machine (since those services don&#39;t run in docker). Great, everything can talk to each other now! Except now my web browser doesn&#39;t understand I&#39;m in a secure context (no longer localhost Host), so I need an SSL certificate...</p>
<h3 id="ssl-certificate">SSL Certificate</h3>
<p>And Caddy created a cert for me! One line in bash and I had it copied out of the docker container. It still took me an hour to figure out to trust the thing in Keychain Access on a Mac, but I didn&#39;t have to generate it!</p>
<p>My final hiccup of the day was with Vercel serverless functions. Locally, <code>vercel dev</code> <em>also</em> proxies whatever it&#39;s running, and it didn&#39;t like the custom name I gave it (frontend.localhost). It thought it was still hosted at localhost. Eventually, I figured out how to rewrite the Host header in the upstream and it was all good. And it was only ~3 lines in a Caddyfile! I feel very accomplished! I was lucky vercel dev worked with the Host rewrite because that&#39;s still about all I know about proxying.</p>
<h2 id="the-next-day">The Next Day</h2>
<p>I&#39;d learn the next day that Node doesn&#39;t read your local SSL certs, or resolve <code>*.localhost</code> names the way the browser does. At least for now I&#39;m able to effectively access all my sites with their names, instead of <code>localhost:&lt;port number&gt;</code>.  That alone feels like a win, even if the configuration of my services still need to reference the localhost address.  The rest of this I&#39;ll write about <a href="/p/2024-12-12-node-tls-dns.html">another day</a>.</p>
<hr>
<hr>
<h4 id="caddy-example">Caddy example</h4>
<p>I&#39;ve changed the port numbers from my local dev environment, but this is how my Caddyfile is laid out with the lines for Vercel serverless under <code>frontend.localhost</code>.</p>
<pre><code>backend.localhost {
  reverse_proxy localhost:4321
}

frontend.localhost {
  reverse_proxy /api/* localhost:2345 {
    header_up Host {upstream_hostport}
  }
  reverse_proxy localhost:2345
}

admin.localhost {
  reverse_proxy localhost:5738
}
</code></pre></main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Sat, 07 Sep 2024 00:00:00 GMT</pubDate>
      <title>Not Your Parent&apos;s Software</title>
      <link>https://benkenawell.com/p/2024-09-07-not-your-parents-software.html</link>
      <guid>https://benkenawell.com/p/2024-09-07-not-your-parents-software.html</guid>
      <description>&lt;p&gt;&lt;a href=&quot;https://once.com/&quot;&gt;ONCE&lt;/a&gt; is a great example of software we can own, built for the modern world. &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Not Your Parent's Software</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Not Your Parent's Software</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2024-09-07T00:00:00+00:00">Sep 7th, 2024</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p><a href="https://once.com/">ONCE</a> is a great example of software we can own, built for the modern world. <!--more--></p>
<p>Years ago (when I was a kid), software mostly ran on the computer it was installed on.  I <em>just barely</em> remember dial up, before computers had a persistent internet connection.  Once they always had internet,the software on them started to shift.  Your software could be on <em>someone else&#39;s</em> computer and you could still use it.  You could access your data on any device. You didn&#39;t need to manage updates.
{: #software}</p>
<p>At first, this was great.  It felt like your computer gained new superpowers, doing more with less.  These <em>services</em> were different than the software that used run on our computers.  They were typically &quot;multi-tenant&quot; with a very complicated setup, often running over multiple machines.  You might not know it, but they took a dedicated system administration/operations team to run.
{: #software-tech-details }</p>
<p>Services often represent an ongoing relationship with the expectation is that you continue to pay.  This was great for the companies moving to this model, they found a more steady stream of revenue.  The revenue was much larger than they could generate selling software only once.
{: #what-is-service}</p>
<p>And some of these computer &quot;services&quot; meet the services definition.  For example, I get new music on <a href="https://www.deezer.com/us/">Deezer</a> whenever an artist drops a new album, a ongoing service.  Email really does have a lot of complexity, when companies communicate between each other.  But a lot of software doesn&#39;t meet those requirements, so shouldn&#39;t require that sort of ongoing relationship.
{: #services}</p>
<p>Defense of the service model includes ongoing maintenance costs, payment for new features, or many other arguments.  But these arguments rarely hold water.  A lot of the software doesn&#39;t need significant ongoing maintenance (this is why software is so valuable).  You didn&#39;t agree to new features, nor do you have a choice about them.  They are typically foisted upon you, sometimes to the point of being barely recognizable.  Changes this significant <em>could and should just be sold again</em>.  Companies hope that losing all the data you&#39;ve collected in their software will be enough to keep you until you&#39;re used to the new changes.
{: #why-services }</p>
<p>That&#39;s where software like <a href="https://once.com/">ONCE</a> comes in. To bridge the gap between our parents&#39; software and our modern expectations.  After purchasing, it&#39;s <em>your software</em> with all the data freedoms our parents (unknowingly) enjoyed and all the access we expect from the modern day. </p>
<p>If this sounds too good to be true, there&#39;s some technology under the hood to make it happen.  First, it&#39;s &quot;single tenant&quot;, running on a single machine <em>just for you</em>.  It gets updates like the software on your computer (regularly, when you aren&#39;t using, without downtime).  It runs on your machine (although probably not your laptop).  It&#39;s <em>very</em> self-contained.
{: #tech-overview}</p>
<p>What all software works like this?  ONCE has <a href="https://once.com/campfire">chat</a> and <a href="https://once.com/writebook">book writing</a> software.  They&#39;ve alluded to <a href="https://37signals.com/podcast/once-again/">parking/permit managing</a> software as well.  I can think of <a href="https://bitwarden.com/blog/new-deployment-option-for-self-hosting-bitwarden/">password managers</a>, podcast subscription apps, note taking apps, maybe even <a href="https://pleroma.social/">joining the Fediverse</a>.
{: #possibilities}</p>
<p>There are <a href="https://en.wikipedia.org/wiki/Self-hosting_(web_services)">self hosted versions</a> of all of these ideas already.  What ONCE does better is focus on the User Experience of installing and maintaining.  The install is one line.  Maintenance happens automatically overnight.  If a command line is intimidating, DHH demonstrated the whole process <a href="https://www.youtube.com/watch?v=yESRTbpKGg8">in an 8 minute video</a>!
{: #how-to }</p>
<p>I hope to build software like this one day.  And I hope I won&#39;t be the only one!</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Wed, 04 Sep 2024 00:00:00 GMT</pubDate>
      <title>GraphQL is Not Expressive</title>
      <link>https://benkenawell.com/p/2024-09-04-graphql-is-not-expressive.html</link>
      <guid>https://benkenawell.com/p/2024-09-04-graphql-is-not-expressive.html</guid>
      <description>&lt;p&gt;It has only two categories of inquiry&lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - GraphQL is Not Expressive</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">GraphQL is Not Expressive</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2024-09-04T00:00:00+00:00">Sep 4th, 2024</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>It has only two categories of inquiry<!--more-->: Query and Mutation, with Resolvers knowing how to construct the return value.  It&#39;s Query field lets you choose what results you want, that&#39;s great.  But there&#39;s no way to relate a <em>Mutation</em> to another <a href="https://graphql.org/learn/schema/">Type</a>.  For instance, a <a id="rest-endpoint" href="https://htmx.org/essays/how-did-rest-come-to-mean-the-opposite-of-rest/" target="_blank">REST endpoint</a> of <code>conversation/:conversationId/chatEvent/create</code> easily tells the reader that we&#39;re creating a chat event <em>in relation to</em> a specific conversation.  The body of the requestcan then all be related to the creation, instead of being split between querying and creation.</p>
<p>In GraphQL, a Mutation has no &quot;related&quot; or &quot;subset of&quot; expression.  Instead, a similar mutation to our above endpoint would look like: </p>
<pre><code class="hljs language-graphql"><span class="hljs-keyword">type</span> <span class="hljs-keyword">Mutation</span> <span class="hljs-punctuation">{</span>
  createChatEvent<span class="hljs-punctuation">(</span>
    <span class="hljs-symbol">conversationId</span><span class="hljs-punctuation">:</span> ID<span class="hljs-punctuation">!</span>
    <span class="hljs-symbol">input</span><span class="hljs-punctuation">:</span> ChatEventBodyInput<span class="hljs-punctuation">!</span>
  <span class="hljs-punctuation">)</span><span class="hljs-punctuation">:</span> ChatEvent
<span class="hljs-punctuation">}</span>

<span class="hljs-keyword">type</span> ChatEventBodyInput <span class="hljs-punctuation">{</span>
  <span class="hljs-symbol">body</span><span class="hljs-punctuation">:</span> String
  <span class="hljs-symbol">metadata</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span>String<span class="hljs-punctuation">]</span>
<span class="hljs-punctuation">}</span>
</code></pre><br />
A naive mutation might even lump all the args together in a flat list, instead of nesting them like my above example.  But in either version, the conversation id is painfully front and center, making the arguments for creating a chat event a little muddier, a little less obvious.

<p>Related mutations could be called anything.  Resources have no graph.  You can&#39;t show relatedness, except through enforced naming conventions.  All this feels antithetical to GraphQL&#39;s intended purpose.</p>
<h2 id="another-example-the-nested-mutation">Another example, the Nested mutation</h2>
<pre><code class="hljs language-graphql"><span class="hljs-keyword">type</span> <span class="hljs-keyword">Mutation</span> <span class="hljs-punctuation">{</span>
  conversation<span class="hljs-punctuation">(</span>
    <span class="hljs-symbol">id</span><span class="hljs-punctuation">:</span> ID<span class="hljs-punctuation">!</span>
    <span class="hljs-symbol">createChatEvent</span><span class="hljs-punctuation">:</span> ChatEventInput
  <span class="hljs-punctuation">)</span><span class="hljs-punctuation">:</span> Conversation
<span class="hljs-punctuation">}</span>
</code></pre><p>This mutation example <em>does</em> nest and relate like our REST example.  It adds the possible mutations as arguments under a single Mutation operation.  But at least <a href="https://docs.nestjs.com/graphql/mutations">in NestJS</a>, there&#39;s no support for the idiom.  It&#39;d all filter through a single Mutation handler and we&#39;d have to dispatch it ourselves, very unlike Resolvers.  Then we&#39;d need the resolvers to traverse the graph back to the item we just created!  I&#39;ve never seen this version in the wild, but very quickly it has already become too much work.  If you want a <a href="#rest-endpoint">REST-like</a> system, use RESTful endpoints.</p>
<h2 id="one-last-example-the-resolver-mutation">One last example, the Resolver Mutation</h2>
<pre><code class="hljs language-graphql"><span class="hljs-keyword">type</span> <span class="hljs-keyword">Mutation</span> <span class="hljs-punctuation">{</span>
  conversation<span class="hljs-punctuation">(</span><span class="hljs-symbol">id</span><span class="hljs-punctuation">:</span> ID<span class="hljs-punctuation">!</span><span class="hljs-punctuation">)</span><span class="hljs-punctuation">:</span> ConversationMutations
<span class="hljs-punctuation">}</span> 

<span class="hljs-keyword">type</span> ConversationMutations <span class="hljs-punctuation">{</span>
  chatEvent<span class="hljs-punctuation">(</span><span class="hljs-symbol">id</span><span class="hljs-punctuation">:</span> ID<span class="hljs-punctuation">!</span><span class="hljs-punctuation">)</span><span class="hljs-punctuation">:</span> ChatEventMutation
<span class="hljs-punctuation">}</span>

<span class="hljs-keyword">type</span> ChatEventMutation <span class="hljs-punctuation">{</span>
  create<span class="hljs-punctuation">(</span><span class="hljs-symbol">input</span><span class="hljs-punctuation">:</span> ChatEventInput<span class="hljs-punctuation">!</span><span class="hljs-punctuation">)</span><span class="hljs-punctuation">:</span> ChatEvent
<span class="hljs-punctuation">}</span>
</code></pre><p>This version is performing operations depending on which fields on our Mutation are resolved.  It uses the <em>return value</em> to perform the actual mutation.  This is much more expressive, it looks a lot like our REST pattern.  It would have <a href="https://docs.nestjs.com/graphql/resolvers">great support in NestJS</a>. But it&#39;s not a pattern the <a href="https://graphql.org/learn/queries/#mutations">graphql docs</a> look at, and it would likely defeat some of the ordering guaratees.  For those reasons, I conclude that this isn&#39;t really a pattern graphql supports.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Graphql favors the return value <em>so much more</em> than the Query/Mutation interface that it creates lopsided APIs.  <a href="https://graphql.org/learn/thinking-in-graphs/">&quot;Thinking in graphs&quot;</a> works for the return value, which is very expressive. But the input and arguments are not even close.  Is this a fair trade off for REST endpoints, where the input seems more expressive but the output more rigid?  I don&#39;t think so, because it&#39;s easy to <a href="https://htmx.org/essays/two-approaches-to-decoupling/">create expressive endpoints</a>.  When you develop both sides, <em>you control the output</em>.</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Tue, 03 Sep 2024 00:00:00 GMT</pubDate>
      <title>Eventing Like React</title>
      <link>https://benkenawell.com/p/2024-09-03-eventing-like-react.html</link>
      <guid>https://benkenawell.com/p/2024-09-03-eventing-like-react.html</guid>
      <description>&lt;p&gt;I have [a previous article]({% post_url 2024-08-05-react-eventing-like-the-dom %}) about using React to mirror DOM eventing style &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Eventing Like React</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Eventing Like React</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2024-09-03T00:00:00+00:00">Sep 3rd, 2024</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>I have <a href="/p/2024-08-05-react-eventing-like-the-dom.html">a previous article</a> about using React to mirror DOM eventing style <!--more--> while still being able to use other React goodies. In that article, I gave an example of Submitting or Skipping a form.  You might think that sort of semantic is something only React can do, but that&#39;s far from the truth! With <a href="https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent">Custom Events</a> we can do the exact same thing with a normal <code>form</code> element. The code looks much different, but the end result is the same.  Or maybe even more powerful, given it uses the browser to it&#39;s full advantage, instead of glossing over it like React does.</p>
<p>The browser has so many rich APIs for interacting, including a strong eventing system! Using it, we get so much for free and can easily listen to the parts of the document we need. That&#39;s a blessing and a curse, if we aren&#39;t careful we could end up with a convoluted mess. I&#39;ve managed to make convoluted messes in React too, though.  This skip example is a great example of how powerful augmenting it directly can be. Let&#39;s dive in:</p>
<pre><code class="hljs language-html"><span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">form</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">&quot;name&quot;</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">&quot;submit&quot;</span>&gt;</span>Submit<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- inline is not recommended, but used for illustrative purposes --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">&quot;button&quot;</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">&quot;skipForm&quot;</span>&gt;</span>Skip<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="language-javascript">
    <span class="hljs-comment">/**
     * A barebones implementation of our skip event
     * <span class="hljs-doctag">@example</span>
     * elem.dispatchEvent(new SkipEvent());
     * */</span>
    <span class="hljs-keyword">class</span> <span class="hljs-title class_">SkipEvent</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_ inherited__">CustomEvent</span> {
        <span class="hljs-title function_">constructor</span>(<span class="hljs-params">detail</span>) {
            <span class="hljs-variable language_">super</span>(<span class="hljs-string">&#x27;skip&#x27;</span>, {
              detail,
              <span class="hljs-attr">bubbles</span>: <span class="hljs-literal">true</span>,
              <span class="hljs-attr">cancelable</span>: <span class="hljs-literal">true</span>
            });
        }
    }

    <span class="hljs-comment">/**
     * fires a SkipEvent
     * */</span>
    <span class="hljs-keyword">function</span> <span class="hljs-title function_">skipForm</span>(<span class="hljs-params">event</span>) {
      event.<span class="hljs-title function_">stopPropagation</span>();
      <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">dispatchEvent</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">SkipEvent</span>())
    }

    <span class="hljs-comment">// add listeners on the document so we can see it fire when it bubbles up</span>
    <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">&#x27;skip&#x27;</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&#x27;form skipped&#x27;</span>, e));
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
</code></pre><p>Which will show up on our document as a skipped event!  This is the parallel to how our onSkip handler worked in React, except this version pulls in all of the power of the browser&#39;s native event system!  We can attach that behavior to any element we want very easily because we&#39;ve described the <em>behavior</em> and the <em>view</em> separately!</p>
<h3 id="one-step-further">One Step Further</h3>
<p>In our above example, we&#39;ve only fired our skip event on the button.  We could easily fire it on the form as well, maybe if we receive a blank submission. Unlike our React example, we can actually get better granularity here!  In React, we can&#39;t tell what caused <code>onSkip</code> to be called.  The skip button might have fired the event, or an empty input may have!  Since DOM Events have a <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/target"><code>target</code> property</a>, it&#39;s easy to check.  We can model this in React by passing the SynctheticEvent as part of our <code>onSkip</code> callback, but the DOM provides this out of the box.</p>
<pre><code class="hljs language-html"><span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">form</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- inline is not recommended, but used for illustrative purposes --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">&quot;name&quot;</span> <span class="hljs-attr">onsubmit</span>=<span class="hljs-string">&quot;submitForm&quot;</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">&quot;submit&quot;</span>&gt;</span>Submit<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">&quot;button&quot;</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">&quot;skipForm&quot;</span>&gt;</span>Skip<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="language-javascript">
    <span class="hljs-comment">/**
     * A barebones implementation of our skip event
     * <span class="hljs-doctag">@example</span>
     * elem.dispatchEvent(new SkipEvent());
     * */</span>
    <span class="hljs-keyword">class</span> <span class="hljs-title class_">SkipEvent</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_ inherited__">CustomEvent</span> {
        <span class="hljs-title function_">constructor</span>(<span class="hljs-params">detail</span>) {
            <span class="hljs-variable language_">super</span>(<span class="hljs-string">&#x27;skip&#x27;</span>, {
              detail,
              <span class="hljs-attr">bubbles</span>: <span class="hljs-literal">true</span>,
              <span class="hljs-attr">cancelable</span>: <span class="hljs-literal">true</span>
            });
        }
    }

    <span class="hljs-comment">/**
     * fires a SkipEvent
     * */</span>
    <span class="hljs-keyword">function</span> <span class="hljs-title function_">skipForm</span>(<span class="hljs-params">event</span>) {
      event.<span class="hljs-title function_">stopPropagation</span>();
      <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">dispatchEvent</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">SkipEvent</span>())
    }

    <span class="hljs-comment">/**
     * skips if empty input, submits if not
     */</span>
    <span class="hljs-keyword">function</span> <span class="hljs-title function_">submitForm</span>(<span class="hljs-params">event</span>) {
      event.<span class="hljs-title function_">preventDefault</span>(); <span class="hljs-comment">// so the page doesn&#x27;t refresh on us</span>
      <span class="hljs-keyword">const</span> formData = <span class="hljs-keyword">new</span> <span class="hljs-title class_">FormData</span>(event.<span class="hljs-property">target</span>); <span class="hljs-comment">// the data the form is submitting</span>
      <span class="hljs-comment">// get the name out of our form data, if it&#x27;s blank stop the current event and fire a SkipEvent from here</span>
      <span class="hljs-keyword">const</span> name = formData.<span class="hljs-title function_">get</span>(<span class="hljs-string">&#x27;name&#x27;</span>);
      <span class="hljs-keyword">if</span>(!name) {
        event.<span class="hljs-title function_">stopPropagation</span>();
        <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">dispatchEvent</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">SkipEvent</span>());
      }
    }

    <span class="hljs-comment">// add listeners on the document so we can see it fire when it bubbles up</span>
    <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">&#x27;skip&#x27;</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&#x27;form skipped&#x27;</span>, e));
    <span class="hljs-variable language_">document</span>.<span class="hljs-title function_">addEventListener</span>(<span class="hljs-string">&#x27;submit&#x27;</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> <span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-string">&#x27;form submitted&#x27;</span>, e));
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
</code></pre><br />

<p>Which version do you like better?  Which is more legible?  Which do you think is easier to maintain?  Which provides more context?  Which one can communicate with the backend more easily?</p>
<p>My last parting thought is that the above is a complete body to a webpage.  Give it a <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head">head</a> and <a href="https://developer.mozilla.org/en-US/docs/Glossary/Doctype">doctype</a> and you can host it anywhere.  Just use it like that and a browser is probably kind enough to show it to you still.  In my React example, there&#39;s some boilerplate to bootstrap the app client side I didn&#39;t include.  Why not just use the native client side, instead of requiring a bootstrapped context?</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Mon, 05 Aug 2024 00:00:00 GMT</pubDate>
      <title>Reach Eventing Like the DOM</title>
      <link>https://benkenawell.com/p/2024-08-05-react-eventing-like-the-dom.html</link>
      <guid>https://benkenawell.com/p/2024-08-05-react-eventing-like-the-dom.html</guid>
      <description>&lt;p&gt;React doesn&amp;#39;t have a builtin eventing system like the browser does, but you can model your components after it. &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Reach Eventing Like the DOM</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Reach Eventing Like the DOM</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2024-08-05T00:00:00+00:00">Aug 5th, 2024</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>React doesn&#39;t have a builtin eventing system like the browser does, but you can model your components after it. <!--more--> You can leverage the DOM Events through on* handler props on JSX elements, which produce Synthetic Events and gives access to them, but you can&#39;t easily fire your own <a href="https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent">Custom Events</a> off of the Nodes React creates.  This means any event you want an element to listen to needs to get an event handler or signal or callback from somewhere.  Often that&#39;s the state management library, but it might just be prop drilling. </p>
<p>So sticking with the Synthetic Event model, we can write a familiar interface for our own components. <code>onClick</code>  is called everytime the mouse clicks that element, <a href="https://react.dev/reference/react-dom/components/input#controlling-an-input-with-a-state-variable"><code>onChange</code> will be called</a> when the input changes (weirdly on an input, that&#39;s the browser&#39;s <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/input_event">oninput event</a>). When we&#39;re building our own components, we should strive to do the same thing. Have events as callbacks that start with &quot;on___&quot;. Give access to the event that is finally fired from the browser, if you can.  The higher level component can tie into state management for us; that&#39;s a nice separation of concerns and allows us to reuse our component in more places.  This will push us to think like the nodes we&#39;re manipulating, the DOM.</p>
<h3 id="example">Example</h3>
<p>Have a form that can be submitted or skipped? Have the components that encapsulates the form include onSubmit and onSkip props. What fires the onSubmit? Form submission should. Then have that callback take two arguments, the processed form data (for ease of use) and the <code>React.FormEvent&lt;HTMLFormElement&gt;</code> event that the form element has, to give as much information as we have access to. How do you skip? When a button is pressed? Great, just have that whole callback be a <code>React.MouseHandler&lt;HTMLButtonElement&gt;</code>. Oh, you want to call onSkip if the form is submitted empty as well? Then change the type to <code>(event: React.MouseEvent&lt;HTMLButtonElement&gt; | React.FormEvent&lt;HTMLFormElement&gt;) =&gt; void;</code>. Maybe that isn&#39;t quite as pretty, but it&#39;s honest and actually gives information about how the event was triggered, unlike a simple <code>() =&gt; void;</code> that I see in a lot of code.</p>
<p>Setting up your components like this might be more work initially, but like refs it helps us focus our effort in making components that work with the browser model better. It forces us to think about component boundaries in a way that we can work with and learn about the DOM as we&#39;re building.  The React goodness on top and not being tied <em>directly</em> to the DOM allows us to contextualize what we&#39;re writing to the domain at hand. Put another way, this style helps ground us in the rendered truth of the browsers DOM while letting us express our problem domain.</p>
<p>What does it look like in practice? In the simple example, with no preprocessing:</p>
<pre><code class="hljs language-tsx"><span class="hljs-keyword">type</span> <span class="hljs-title class_">NameFormProps</span> = {
  <span class="hljs-attr">onSubmit</span>: <span class="hljs-title class_">React</span>.<span class="hljs-property">FormEventHandler</span>&lt;<span class="hljs-title class_">HTMLFormElement</span>&gt;;
  <span class="hljs-attr">onSkip</span>: <span class="hljs-title class_">React</span>.<span class="hljs-property">MouseEventHandler</span>&lt;<span class="hljs-title class_">HTMLButtonElement</span>&gt;;
}
<span class="hljs-keyword">function</span> <span class="hljs-title function_">NameForm</span>(<span class="hljs-params">{onSubmit, onSkip}: <span class="hljs-title class_">NameFormProps</span></span>) {
  <span class="hljs-keyword">return</span> (
    <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{onSubmit}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">&quot;name&quot;</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">&quot;submit&quot;</span>&gt;</span>Submit<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">&quot;button&quot;</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{onSkip}</span>&gt;</span>Skip<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></span>
  )
}
</code></pre><p>A little more complicated, to tie into React better:</p>
<pre><code class="hljs language-tsx"><span class="hljs-keyword">type</span> <span class="hljs-title class_">NameFormProps</span> = {
  <span class="hljs-attr">onSubmit</span>: <span class="hljs-function">(<span class="hljs-params"><span class="hljs-attr">name</span>: <span class="hljs-built_in">string</span>, <span class="hljs-attr">event</span>: <span class="hljs-title class_">React</span>.<span class="hljs-title class_">FormEvent</span>&lt;<span class="hljs-title class_">HTMLFormElement</span>&gt;</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
  <span class="hljs-attr">onSkip</span>: <span class="hljs-function">(<span class="hljs-params"><span class="hljs-attr">event</span>: <span class="hljs-title class_">React</span>.<span class="hljs-title class_">MouseEvent</span>&lt;<span class="hljs-title class_">HTMLButtonElement</span>&gt; | <span class="hljs-title class_">React</span>.<span class="hljs-title class_">FormEvent</span>&lt;<span class="hljs-title class_">HTMLFormElement</span>&gt;</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
}
<span class="hljs-keyword">function</span> <span class="hljs-title function_">NameForm</span>(<span class="hljs-params">{onSubmit, onSkip}: <span class="hljs-title class_">NameFormProps</span></span>) {
  <span class="hljs-keyword">const</span> submitHandler = <span class="hljs-title function_">useCallback</span>(<span class="hljs-function">(<span class="hljs-params">ev</span>) =&gt;</span> {
    ev.<span class="hljs-title function_">preventDefault</span>(); <span class="hljs-comment">// stop the network request forms make by default</span>
    <span class="hljs-comment">// collect the name field from the form</span>
    <span class="hljs-keyword">const</span> formData = <span class="hljs-title class_">Object</span>.<span class="hljs-title function_">fromEntries</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">FormData</span>(ev.<span class="hljs-property">target</span>).<span class="hljs-title function_">entries</span>());
    <span class="hljs-keyword">if</span>(formData.<span class="hljs-property">name</span>.<span class="hljs-title function_">trim</span>()) <span class="hljs-title function_">onSubmit</span>(formData.<span class="hljs-property">name</span>, ev);
    <span class="hljs-keyword">else</span> <span class="hljs-title function_">onSkip</span>(ev);
  }, [onSubmit, onSkip]);
  <span class="hljs-keyword">return</span> (
    <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{submitHandler}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">&quot;name&quot;</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">&quot;submit&quot;</span>&gt;</span>Submit<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">&quot;button&quot;</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{onSkip}</span>&gt;</span>Skip<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></span>
  )
}
</code></pre><br />

<p>Can the browser do this?  What would it look like?  I mentioned Custom Events above, here&#39;s <a href="/p/2024-09-03-eventing-like-react.html">my article about implementing it that way</a></p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Fri, 26 Jul 2024 00:00:00 GMT</pubDate>
      <title>JSX: Writing the Language To Meet You</title>
      <link>https://benkenawell.com/p/2024-07-26-jsx-writing-language-up.html</link>
      <guid>https://benkenawell.com/p/2024-07-26-jsx-writing-language-up.html</guid>
      <description>&lt;p&gt;Like &lt;a href=&quot;https://common-lisp.net/documentation&quot;&gt;LISP&lt;/a&gt;, &lt;a href=&quot;https://react.dev/learn/writing-markup-with-jsx&quot;&gt;JSX&lt;/a&gt; allows html to be written &amp;quot;up&amp;quot; to easily define new concepts. &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - JSX: Writing the Language To Meet You</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">JSX: Writing the Language To Meet You</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2024-07-26T00:00:00+00:00">Jul 26th, 2024</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>Like <a href="https://common-lisp.net/documentation">LISP</a>, <a href="https://react.dev/learn/writing-markup-with-jsx">JSX</a> allows html to be written &quot;up&quot; to easily define new concepts. <!--more--></p>
<p>Like LISP macros, poorly written components and abstractions make them <em>harder</em> to work with. But well constructed ones make hard things easy to express. </p>
<p>Templating languages seem unable to allow the language to be written up to express harder concepts. But since JSX is an XML that closely mirrors HTML, where &quot;tags&quot; can be created and defined and expand into any amount of other tags, the markup language can be written up to the concepts they need to express. Making hard things easy to express, just like macros. Able to make hard things really hard to reason about, if done poorly, just like macros.</p>
<p>Almost like custom elements, although the compile-time vs run-time behavior still makes them a bit different enough that I don&#39;t think custom elements would provide an useful implementation of this pattern.  You can argue React/JSX is run time as well, but JSX has started to be <a href="https://github.com/developit/vhtml">found more places</a>. </p>
<p>I got the inspiration from Lisp, but Elixir has good support for macros. I wish JavaScript had <em>any</em> type of macro system, instead of needing to bolt on code generation.</p>
<p>A good abstraction helps you understand the underlying technology, so remember you&#39;re ultimately modelling HTML.  Being able to use your abstraction layer while in the mindset of writing HTML is a good gauge on a good abstraction.  A good example might be a modal or <a href="https://www.radix-ui.com/primitives/docs/components/dialog">dialog</a>, where the parent and child tags work together (like select/option or ul/li or table...):</p>
<pre><code class="hljs language-jsx">&lt;<span class="hljs-title class_">Modal</span>.<span class="hljs-property">Root</span>&gt;
  <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Modal.Trigger</span>&gt;</span>
    Click Here!
  <span class="hljs-tag">&lt;/<span class="hljs-name">Modal.Trigger</span>&gt;</span></span>
  <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">Modal.Body</span>&gt;</span>
    You are now looking at the content.  Click anywhere to close.
  <span class="hljs-tag">&lt;/<span class="hljs-name">Modal.Body</span>&gt;</span></span>
&lt;/<span class="hljs-title class_">Modal</span>.<span class="hljs-property">Root</span>&gt;
</code></pre><p>Of course having a good mental model of HMTL helps yourself.  But if you don&#39;t start with one, you&#39;ll learn as your write.  Having a good goal to strive for is half the battle! Good luck on writing your React components.</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Sat, 20 Jul 2024 00:00:00 GMT</pubDate>
      <title>Very Semantic HTML</title>
      <link>https://benkenawell.com/p/2024-07-20-very-semantic-html.html</link>
      <guid>https://benkenawell.com/p/2024-07-20-very-semantic-html.html</guid>
      <description>&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/curriculum/core/semantic-html/&quot;&gt;Semantic HTML&lt;/a&gt; can be a very charged topic.  A quick search &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Very Semantic HTML</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Very Semantic HTML</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2024-07-20T00:00:00+00:00">Jul 20th, 2024</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p><a href="https://developer.mozilla.org/en-US/curriculum/core/semantic-html/">Semantic HTML</a> can be a very charged topic.  A quick search <!--more--> on the internet will yield plenty of results about how you should use each tag.  Some of them should be pretty much common sense. <code>nav</code> wraps navigation elements. <code>main</code> is your main content on the page.  <code>header</code>, <code>footer</code> are for their respective parts.  But <code>div</code>, <code>article</code>, and <code>section</code> have  different nuances  for different people.</p>
<p>This article is about taking this idea one step further: what if we could make our tags have even more semantic meaning? What if the markup could have semantic meaning <em>for the project we&#39;re in</em>?  What if we could <em>model the domain as part of our markup</em>?  You want to display an Avatar? Use this <code>Avatar</code> component.  But a single component isn&#39;t always enough.  Here are all of the pieces of our Avatar components, use what you need.  It has a Root container, a Face, Initials to fallback to, and a Popover that we sometimes include for more information.</p>
<p>Or for form input: this <code>EmailCollection</code> object holds everything we typically use to request an email.  The Root, Label, and Inputs hold all the styling an info to make it easy for you to build one.  Need to add something extra?  Just add it to the label, or after the label, or wherever you need it.  We could write</p>
<pre><code class="hljs language-jsx">&lt;<span class="hljs-title class_">EmailCollection</span>.<span class="hljs-property">Root</span>&gt;
  <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">EmailCollection.Label</span>&gt;</span>
    Please enter your email:
  <span class="hljs-tag">&lt;/<span class="hljs-name">EmailCollection.Label</span>&gt;</span></span>
  <span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">EmailCollection.Input</span> /&gt;</span></span>
&lt;/<span class="hljs-title class_">EmailCollection</span>.<span class="hljs-property">Root</span>&gt;
</code></pre><p>and have that incorporate any styles and business logic related to collecting emails.  In raw html, the best I could think of would be something like a <a href="https://getbem.com/introduction/">BEM</a>-inspired:</p>
<pre><code class="hljs language-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;email-collection&quot;</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;email-collection--label&quot;</span>&gt;</span>
    Please enter your email
  <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">class</span>=<span class="hljs-string">&quot;email-collection--input&quot;</span> <span class="hljs-attr">type</span>=<span class="hljs-string">&quot;email&quot;</span> <span class="hljs-attr">name</span>=<span class="hljs-string">&quot;email&quot;</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">&quot;ben@example.com&quot;</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre><p>which is still pretty good and readable, although it doesn&#39;t have <a href="https://htmx.org/essays/locality-of-behaviour/">any behavior connected</a> to it.  But more importantly, where does that live that it can be reused?  How do I know to reach for <em>that</em> markup when I want to request an email input?  My <code>EmailCollection</code> component above is a React component <!-- TODO, write an article about how I like to write React in this way and link here --> that I can easily import and reuse wherever need be. It&#39;s easy to find in a code base.  I know I&#39;m not missing anything about how <em>my company</em> wants email collection to look or behave.  The element might literally be the same markup, but semantically I can speed read a page or larger component and easily pick out the email collection components.  BEM makes this easier in the html example, but I estimate that it&#39;s more likely to be tailwind utility classes in many companies.</p>
<p>Most templating systems can import/include other templates.  But file-based components mean utilities, types, children, and general extras need to live in a different place.  I can&#39;t have one object of related tags to build with.  It&#39;s tough to build the behavior into these templates.  How do I build the same level of flexibility into these templating systems?  In <a href="https://mozilla.github.io/nunjucks/templating.html#extends">Nunjucks</a>, the closest I can get is</p>
<pre><code class="hljs language-nunjucks">{% extends &quot;email-collection.njk&quot; %}
{% block label %}{{super()}}  Please enter your email{% endblock %}
{% block input %}{{super()}}{% endblock %}
</code></pre><p>or maybe something involving macros.  But both are whole different files for every new usage.  There&#39;s no concept of <em>children</em> or <em>nesting</em> after in includes, very different from html.  I&#39;m primarily a JS/TS dev (front- and back-end), are there other templating systems that could allow for something like that?</p>
<p>I&#39;ve been thinking about using Custom Elements for this concept, but those don&#39;t translate down to the raw html that I want.  Ultimately I would love to just serve the raw html.  Maybe I could use custom elements to define the behavior, but I would want to upgrade some raw markup with the custom element.  Otherwise, I&#39;m not sure what I&#39;ve gained over React (other than being a little counter culture and losing a dependency), but I know I&#39;ve still lost all the reactivity that makes React so popular and still a lot of the flexibility of their component system.</p>
<p>Reminder, I&#39;m primarily a JS/TS developer, so I got to wondering if there were a html tagged template literal that could be used as a rendering engine in express, maybe that would afford me the flexibility to encapsulate some behavior, define them flexibly, define children, etc.  Or maybe I&#39;m over thinking why file-based templating is really such a problem.  <a href="https://htmx.org/essays/template-fragments/">Template fragments</a> might help me extract parts of a page, but they don&#39;t help me make the templates I write <em>more semantic</em>; have semantic meaning <em>in the project I&#39;m writing for</em>.</p>
<p>Maybe there&#39;s a JSX-based, backend templating system that doesn&#39;t rely on React or try to mimic React that I could use.  It&#39;d be a great gateway system to being able to build my markup the way I want but still be able to transmit raw html over the wire. I did find <a href="https://dev.to/argonauta/jsx-unchained-make-a-template-engine-4h56">an article</a> <a href="https://evertpot.com/jsx-template/">or two</a> about it, but nothing with much steam.</p>
<p>Lots of questions and ideas, no good answers from me today!  Why do I want this?  I want to build server driven systems using the most powerful transfer markup, HTML!  Modern browsers give us so much goodness, and backends and client experiences can be shifted so much easier when you&#39;re only building one place.  There&#39;s lots of reasons, basically.  But I haven&#39;t found a templating system that makes me feel more productive in yet.</p>
<p>In conclusion, I realize I have a lot of requirements for a templating system!  I really like how semantic React allows my markup to become.  I want to use a server templating system and just deliver html, but I&#39;m not as happy with the templating options I&#39;ve found.  I&#39;ll leave a list of requirements I&#39;ve picked out in a list here at the bottom:</p>
<h2 id="requirements-for-building-very-semantic-html">Requirements for building very semantic html</h2>
<p>from most to least important:</p>
<ul>
<li>easily and flexbily create my own naming conventions, that mostly thin-wrap actual html components</li>
<li>output raw html</li>
<li>write in a way that allows for children within the templating spec</li>
<li>be able to pass attributes (and maybe properties) to each child independently</li>
<li>be able to define and export multiple components from a single file</li>
</ul>
<p>nice to have:</p>
<ul>
<li>include how a component behaves within the definition (likely include some js, then)</li>
<li>collect related components into single object</li>
<li>stretch: include some css that could get translated directly into the head?  Maybe this one is too framework-specific.  It might be nice, but definitely not a requirement.</li>
</ul>
<!-- do I give people a way to contact me here? I would like to hear from you... -->

</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Mon, 15 Jul 2024 00:00:00 GMT</pubDate>
      <title>Description of Wonka Operations</title>
      <link>https://benkenawell.com/p/2024-07-15-description-of-wonka-operators.html</link>
      <guid>https://benkenawell.com/p/2024-07-15-description-of-wonka-operators.html</guid>
      <description>&lt;p&gt;Suffice to say, wonka operators are a simple concept that becomes incredibly tricky very quickly! &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Description of Wonka Operations</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Description of Wonka Operations</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2024-07-15T00:00:00+00:00">Jul 15th, 2024</time>
    <span class="p-author">Ben</span>
  </div>
</header>




<p>Suffice to say, wonka operators are a simple concept that becomes incredibly tricky very quickly! <!--more-->  This post is about <a href="https://wonka.kitten.sh/">wonka streams</a> and <a href="https://staltz.com/why-we-need-callbags.htmlhttps://github.com/staltz/callbag-basics">callbags</a>: a fusion of Observables and Iterators.<br>I would <a href="#additional-reading" >read up on some of this</a> if anything in this post confuses you;
there isn&#39;t enough room to go over everything I&#39;d like to. The best metaphor for this methodology is a <em>pipeline</em> or a <em>river system</em>.</p>
<p><em>Operators</em> let us transform the stream of data that comes through.  In the wonka and callbag
specs these are very simple looking functions, that receive other functions, and sometimes call other functions.  All these functions flying around are
confusing!  Yes, they are all the same javascript construct, a function, but they have very different roles.</p>
<blockquote>
<p style="margin: 0">An operator is a function that returns a function</p>
<p style="margin: 0">An operator is a function that accepts a source function and returns a sink function</p>
<p style="margin: 0">An operator is a function that accepts an upstream function and returns a downstream function</p>
</blockquote>


<p>The upstream function should be called with a callback function that will be called with a signal, and doesn&#39;t return anything.
The upstream function determines <em>when</em> this function will be called, and can call it as many times as it&#39;d like.  It can signal to us that data will
begin flowing (START), that it has data to give us (PUSH) and also when it has run out of data (END).</p>
<pre><code class="hljs language-typescript"><span class="hljs-title function_">source</span>(<span class="hljs-function">(<span class="hljs-params">signal</span>) =&gt;</span> {<span class="hljs-comment">/* could be START, PUSH, or END */</span>})
</code></pre><br>

<p>A downstream function should be called with a signal, whenever you&#39;re ready in the operator.  You call it with the START, PUSH, and END signals to
talk to the function downstream of you in the pipeline.</p>
<pre><code class="hljs language-typescript"><span class="hljs-keyword">return</span> <span class="hljs-function">(<span class="hljs-params">sink</span>) =&gt;</span> {
  <span class="hljs-title function_">sink</span>(<span class="hljs-variable constant_">START</span>, <span class="hljs-comment">/* talkback */</span>);
  <span class="hljs-title function_">sink</span>(<span class="hljs-variable constant_">PUSH</span>, <span class="hljs-comment">/* data */</span>);
  <span class="hljs-title function_">sink</span>(<span class="hljs-variable constant_">END</span>);
}
</code></pre><br >

<p>When an upstream function calls the downstream function with START, it should provide <em>another function</em> to &quot;talkback&quot; to it, which the downstream
function can tell the upstream function it&#39;d like more data (PULL) or it&#39;s done receiving data (CLOSE).  This is usually called the talkback function.</p>
<pre><code class="hljs language-typescript"><span class="hljs-keyword">return</span> <span class="hljs-function">(<span class="hljs-params">sink</span>) =&gt;</span> {
  <span class="hljs-title function_">sink</span>(<span class="hljs-variable constant_">START</span>, <span class="hljs-function">(<span class="hljs-params">talkbackSignal</span>) =&gt;</span> {<span class="hljs-comment">/* could be PULL or CLOSE */</span>})
}
</code></pre><br >

<p>There is <em>a lot</em> of flexibility here... Our operator needs to call the source and call the sink; that&#39;s basically it for a functioning operator.  A
well-behaved operator will also END when asked, probably sending that signal downstream, and forward a CLOSE signal upstream.  But it might not,
depending on how you want to control the flow of data through your operator.  That&#39;s the beauty: you have the flexibility to manipulate
the data any way you see fit.  But it also means that it is on you to explain <em>how</em> your operator transforms the flow through it,
and <em>what</em> your operator does when it receives each of the different signals.</p>
<div style="display: flex; justify-content: center; margin-bottom: 2em">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 429.05427004396915 670.3999328613281" width="429.05427004396915" height="670.3999328613281">
  <!-- svg-source:excalidraw -->
  
  <defs>
    <style class="style-fonts">
      @font-face {
        font-family: "Virgil";
        src: url("https://excalidraw.com/Virgil.woff2");
      }
      @font-face {
        font-family: "Cascadia";
        src: url("https://excalidraw.com/Cascadia.woff2");
      }
      @font-face {
        font-family: "Assistant";
        src: url("https://excalidraw.com/Assistant-Regular.woff2");
      }
    </style>
    
  </defs>
  <rect x="0" y="0" width="429.05427004396915" height="670.3999328613281" fill="#ffffff"></rect><g stroke-linecap="round" transform="translate(10 246) rotate(0 154.79998779296875 41.19999694824219)"><path d="M20.6 0 C95.54 0.81, 168.7 1.62, 289 0 M20.6 0 C89.03 -0.85, 158.37 -0.29, 289 0 M289 0 C301.44 -1.56, 309.88 6.75, 309.6 20.6 M289 0 C304.14 0.54, 311.67 5.5, 309.6 20.6 M309.6 20.6 C307.72 33.48, 308.13 46.1, 309.6 61.8 M309.6 20.6 C308.91 37.02, 309.08 52.33, 309.6 61.8 M309.6 61.8 C309.63 76.52, 304.13 83.02, 289 82.4 M309.6 61.8 C311.74 76.8, 301.95 82.58, 289 82.4 M289 82.4 C202.42 82.69, 117.74 82.72, 20.6 82.4 M289 82.4 C199.74 83.06, 111.24 84.04, 20.6 82.4 M20.6 82.4 C5.44 83.25, 0.53 74.94, 0 61.8 M20.6 82.4 C5.82 82.75, 0.17 74.46, 0 61.8 M0 61.8 C-2.11 51.12, -2.09 38.51, 0 20.6 M0 61.8 C0.5 45.32, 0.5 30.05, 0 20.6 M0 20.6 C-0.32 4.88, 6.88 -0.26, 20.6 0 M0 20.6 C-0.33 7.6, 7.96 -1.44, 20.6 0" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(49.60003662109375 257.07277844289536) rotate(0 116.69738006591797 33.06361688206792)"><text x="0" y="46.34196542190641" font-family="Virgil, Segoe UI Emoji" font-size="52.90178701130868px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Operator</text></g><g stroke-linecap="round" transform="translate(25.20001220703125 10) rotate(0 142.39999389648438 32)"><path d="M16 0 C94.54 0.57, 175.42 -1.49, 268.8 0 M16 0 C70.93 -1.14, 125.6 -1.06, 268.8 0 M268.8 0 C280.63 1.21, 284.82 4.56, 284.8 16 M268.8 0 C277.19 -0.71, 282.93 5.75, 284.8 16 M284.8 16 C285.47 25.77, 286.06 35.7, 284.8 48 M284.8 16 C285.13 24.19, 285.84 33.61, 284.8 48 M284.8 48 C286.09 59.94, 280.58 63.9, 268.8 64 M284.8 48 C282.65 59.88, 278.51 64.62, 268.8 64 M268.8 64 C171.63 66.42, 70.99 64.09, 16 64 M268.8 64 C200.35 63.96, 131.69 63.52, 16 64 M16 64 C6.12 63.05, 1.8 57.25, 0 48 M16 64 C7.45 62.64, 1.98 57.93, 0 48 M0 48 C-0.39 36.9, 1.09 23.96, 0 16 M0 48 C-0.37 37.46, -1.13 25.62, 0 16 M0 16 C0.79 5.03, 3.57 1.94, 16 0 M0 16 C-0.25 5.8, 5.94 -0.91, 16 0" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(47.5999755859375 22.600013732910156) rotate(0 78.24760907470943 21.299999237060547)"><text x="0" y="29.854078930664045" font-family="Virgil, Segoe UI Emoji" font-size="34.07999877929685px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Upstream</text></g><g stroke-linecap="round" transform="translate(12.4000244140625 533.1999816894531) rotate(0 148.39999389648438 50.40000915527344)"><path d="M25.2 0 C120.79 -2.63, 215.21 -0.67, 271.6 0 M25.2 0 C114.09 -0.93, 202.03 -0.56, 271.6 0 M271.6 0 C286.45 -1.79, 297.59 8.1, 296.8 25.2 M271.6 0 C286.38 2.23, 296.55 8.87, 296.8 25.2 M296.8 25.2 C295.86 41.17, 297.06 60.02, 296.8 75.6 M296.8 25.2 C296.63 45.51, 296.66 64.04, 296.8 75.6 M296.8 75.6 C298.35 93.96, 287.1 99.75, 271.6 100.8 M296.8 75.6 C295.25 92.87, 288.92 101.41, 271.6 100.8 M271.6 100.8 C200.87 100.74, 129.08 99.9, 25.2 100.8 M271.6 100.8 C201.72 99.96, 130.14 100.27, 25.2 100.8 M25.2 100.8 C8.04 99.11, 0.11 90.6, 0 75.6 M25.2 100.8 C9.87 102.25, -2.11 92.31, 0 75.6 M0 75.6 C-0.72 62.11, 1.61 46.59, 0 25.2 M0 75.6 C0.63 58.38, 0.55 41.9, 0 25.2 M0 25.2 C-0.97 9.44, 9.15 1.55, 25.2 0 M0 25.2 C-1.96 8.05, 7.23 -1.84, 25.2 0" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(36.84575437009346 564.7999572753906) rotate(0 191.10425783693785 47.79998779296875)"><text x="0" y="33.4982314453125" font-family="Virgil, Segoe UI Emoji" font-size="38.23999023437499px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Downstream</text><text x="0" y="81.29821923828123" font-family="Virgil, Segoe UI Emoji" font-size="38.23999023437499px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic"></text></g><g stroke-linecap="round"><g transform="translate(22 331.5999450683594) rotate(0 -1.293843584004776 92.4734138879925)"><path d="M-0.04 -0.82 C-0.14 30.07, -1.25 154.65, -1.6 185.77 M-1.52 1.36 C-1.69 31.9, -2.27 153.7, -2.55 184.16" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(22 331.5999450683594) rotate(0 -1.293843584004776 92.4734138879925)"><path d="M-10.94 160.61 C-10.42 165.72, -5.61 169.91, -2.55 184.16 M-10.94 160.61 C-7.3 169.93, -3.85 179.4, -2.55 184.16" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(22 331.5999450683594) rotate(0 -1.293843584004776 92.4734138879925)"><path d="M6.16 160.72 C2.97 165.81, 4.07 169.97, -2.55 184.16 M6.16 160.72 C3.13 169.89, -0.1 179.32, -2.55 184.16" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g></g><mask></mask><g transform="translate(30 389.800048828125) rotate(0 43.84996795654297 25)"><text x="0" y="17.52" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Start</text><text x="0" y="42.519999999999996" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Talkback</text></g><g stroke-linecap="round"><g transform="translate(139.60003662109375 341.1999816894531) rotate(0 1.1847182752565004 86.01920247653499)"><path d="M0.74 0.58 C0.83 29.23, 0.5 143.56, 0.55 172.21 M-0.33 -0.17 C0.11 28.07, 2.66 141.77, 2.7 170.19" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(139.60003662109375 341.1999816894531) rotate(0 1.1847182752565004 86.01920247653499)"><path d="M-6.16 146.82 C-2.48 152.03, -0.43 159.81, 2.7 170.19 M-6.16 146.82 C-3.09 154.65, -0.65 162.56, 2.7 170.19" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(139.60003662109375 341.1999816894531) rotate(0 1.1847182752565004 86.01920247653499)"><path d="M10.94 146.59 C9.48 151.97, 6.39 159.83, 2.7 170.19 M10.94 146.59 C8.2 154.67, 4.83 162.65, 2.7 170.19" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g></g><mask></mask><g transform="translate(165.39996337890625 398.5999755859375) rotate(0 26.78998565673828 25)"><text x="0" y="17.52" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Push</text><text x="0" y="42.519999999999996" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Data</text></g><g stroke-linecap="round"><g transform="translate(239.60003662109375 345.99993896484375) rotate(0 0.9885054264217388 81.6070797464717)"><path d="M-0.32 -0.38 C0.19 27.07, 1.79 137.49, 2.29 164.84 M1.72 -1.62 C2.13 25.47, 1.23 135.4, 1.29 163.15" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(239.60003662109375 345.99993896484375) rotate(0 0.9885054264217388 81.6070797464717)"><path d="M-7.18 139.63 C-5.77 145.04, -1.74 151.26, 1.29 163.15 M-7.18 139.63 C-4.18 145.15, -1.32 152.99, 1.29 163.15" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(239.60003662109375 345.99993896484375) rotate(0 0.9885054264217388 81.6070797464717)"><path d="M9.92 139.69 C7.28 145.04, 7.26 151.25, 1.29 163.15 M9.92 139.69 C8.19 145.33, 6.31 153.15, 1.29 163.15" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g></g><mask></mask><g transform="translate(264.800048828125 411.99993896484375) rotate(0 17.099990844726562 12.5)"><text x="0" y="17.52" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">End</text></g><g stroke-linecap="round"><g transform="translate(74.39796340669682 227.60000610351562) rotate(0 -0.003499197211056071 -67.67018257474527)"><path d="M-0.82 1.19 C-0.69 -21.18, -0.2 -111.9, -0.14 -134.95 M0.95 0.76 C1.03 -21.85, -0.59 -113.66, -0.96 -136.53" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(74.39796340669682 227.60000610351562) rotate(0 -0.003499197211056071 -67.67018257474527)"><path d="M7.98 -113.18 C8.13 -117.99, 5.92 -123.72, -0.96 -136.53 M7.98 -113.18 C6.49 -116.99, 3.82 -123.04, -0.96 -136.53" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(74.39796340669682 227.60000610351562) rotate(0 -0.003499197211056071 -67.67018257474527)"><path d="M-9.12 -112.89 C-5.18 -117.65, -3.6 -123.44, -0.96 -136.53 M-9.12 -112.89 C-7.11 -116.69, -6.29 -122.8, -0.96 -136.53" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g></g><mask></mask><g transform="translate(89.20001220703125 143.39999389648438) rotate(0 17.54998016357422 12.5)"><text x="0" y="17.52" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Pull</text></g><g stroke-linecap="round"><g transform="translate(209.0399759969996 227.5999755859375) rotate(0 0.11943543378282584 -71.10426749451085)"><path d="M-1.1 -0.67 C-1.32 -24.2, -1 -118.17, -0.95 -141.93 M0.52 1.59 C0.65 -22.23, 1.73 -119.82, 1.33 -143.8" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(209.0399759969996 227.5999755859375) rotate(0 0.11943543378282584 -71.10426749451085)"><path d="M9.9 -120.31 C6.11 -129.47, 5.21 -136.94, 1.33 -143.8 M9.9 -120.31 C8.4 -125.3, 6.03 -132.16, 1.33 -143.8" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g><g transform="translate(209.0399759969996 227.5999755859375) rotate(0 0.11943543378282584 -71.10426749451085)"><path d="M-7.2 -120.3 C-5.37 -129.31, -0.66 -136.77, 1.33 -143.8 M-7.2 -120.3 C-4.71 -125.28, -3.08 -132.14, 1.33 -143.8" stroke="#1e1e1e" stroke-width="2" fill="none"></path></g></g><mask></mask><g transform="translate(228.40008544921875 145.1999969482422) rotate(0 67.60000610351562 12.5)"><text x="0" y="17.52" font-family="Virgil, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">Close</text></g></svg>
</div>

<br >

<p>This was my very high level overview.  They can be very confusing to look at, at first, so let me leave you with a few examples:</p>
<pre><code class="hljs language-typescript"><span class="hljs-comment">// no-op operator, pass the sink we were given directly to the source provided, add no behavior</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">operator</span>(<span class="hljs-params">source</span>) { <span class="hljs-keyword">return</span> <span class="hljs-function">(<span class="hljs-params">sink</span>) =&gt;</span> {<span class="hljs-title function_">source</span>(sink)})}
</code></pre><pre><code class="hljs language-typescript"><span class="hljs-comment">// no-op operator</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">operator</span>(<span class="hljs-params">upstream</span>) <span class="hljs-keyword">return</span> { <span class="hljs-keyword">return</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">downstream</span>(<span class="hljs-params">sink</span>) { <span class="hljs-title function_">source</span>(sink); }}
</code></pre><pre><code class="hljs language-typescript"><span class="hljs-comment">// basic shell, non functional since I&#x27;m not handling any of the signals</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">operator</span>(<span class="hljs-params">upstream</span>) {
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">downstream</span>(<span class="hljs-params">sink</span>) {
    <span class="hljs-comment">/* call the source */</span>
    <span class="hljs-title function_">upstream</span>(<span class="hljs-keyword">function</span> <span class="hljs-title function_">ourSink</span>(<span class="hljs-params">signal, payload</span>) {
      <span class="hljs-comment">/* do whatever we need with the START, PUSH, END signals */</span>
    })

    <span class="hljs-title function_">sink</span>(<span class="hljs-variable constant_">START</span>, <span class="hljs-keyword">function</span> <span class="hljs-title function_">talkback</span>(<span class="hljs-params">talkbackSignal, payload</span>) {
      <span class="hljs-comment">/* do whatever we need with the PULL and CLOSE signals */</span>
    }))
  }
}
</code></pre><pre><code class="hljs language-typescript"><span class="hljs-comment">// no-op operator, but a basic shell of what could be</span>
<span class="hljs-keyword">function</span> <span class="hljs-title function_">operator</span>(<span class="hljs-params">upstream</span>) {
  <span class="hljs-keyword">let</span> ended = <span class="hljs-literal">false</span>;
  <span class="hljs-keyword">let</span> <span class="hljs-title function_">talkback</span> = (<span class="hljs-params"></span>) =&gt; {};

  <span class="hljs-keyword">return</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">downstream</span>(<span class="hljs-params">sink</span>) {
    <span class="hljs-comment">/* now that we have the sink */</span>

    <span class="hljs-comment">/* call the source  */</span>
    <span class="hljs-title function_">upstream</span>(<span class="hljs-function">(<span class="hljs-params">signal, payload</span>) =&gt;</span> {
      <span class="hljs-keyword">if</span>(ended) {<span class="hljs-comment">/* this whole call will be a no-op if we&#x27;ve received an END signal already */</span> }
      <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(signal === <span class="hljs-variable constant_">END</span>) { 
        ended = <span class="hljs-literal">true</span>; 
        <span class="hljs-comment">/* what else might you want to teardown when it ends? */</span>
        }
      <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(signal === <span class="hljs-variable constant_">START</span>) { 
        talkback = payload; <span class="hljs-comment">// hold a reference to the talkback function, so we can talk to our source</span>
        <span class="hljs-comment">/* what else might you want to start to be ready to receive data? */</span>
        }
      <span class="hljs-keyword">else</span> {
        <span class="hljs-comment">/* do things here with Push signal */</span>

        <span class="hljs-comment">// would send the push signal we receive from upstream directly to downstream,</span>
        <span class="hljs-comment">// not typically useful by itself.</span>
        <span class="hljs-title function_">sink</span>(signal, payload); 
        <span class="hljs-comment">// cachedData = payload; // maybe store the data locally, so we can send it down later?</span>
      }
    });

    <span class="hljs-comment">// remember to start the sink so we can give it our talkback function!</span>
    <span class="hljs-title function_">sink</span>(<span class="hljs-variable constant_">START</span>, <span class="hljs-function">(<span class="hljs-params">signal, payload</span>) =&gt;</span> {
      <span class="hljs-comment">// since this is the talkback, signal will be CLOSE or PULL</span>
      <span class="hljs-keyword">if</span>(signal === <span class="hljs-variable constant_">CLOSE</span>) {
        <span class="hljs-title function_">talkback</span>(<span class="hljs-variable constant_">CLOSE</span>);
      } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> {
        <span class="hljs-comment">// do something with the PULL signal</span>
        <span class="hljs-title function_">talkback</span>(<span class="hljs-variable constant_">PULL</span>);  <span class="hljs-comment">// maybe ask the source for more data?</span>
        <span class="hljs-comment">// sink(PUSH, cachedData); // maybe push some locally cached data down right away, instead?</span>
      }
    }})

  }
}
</code></pre><pre><code class="hljs language-typescript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">map</span>(<span class="hljs-params"><span class="hljs-attr">mapper</span>: (x: X) =&gt; Y</span>) {
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">operator</span>(<span class="hljs-params"><span class="hljs-attr">upstream</span>: <span class="hljs-title class_">Source</span>&lt;X&gt;</span>) {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">downstream</span>(<span class="hljs-params"><span class="hljs-attr">sink</span>: <span class="hljs-title class_">Sink</span>&lt;Y&gt;</span>) {
      <span class="hljs-keyword">let</span> ended = <span class="hljs-literal">false</span>;
      <span class="hljs-keyword">let</span> <span class="hljs-title function_">talkback</span> = (<span class="hljs-params"></span>) =&gt; {};
      <span class="hljs-title function_">source</span>(<span class="hljs-function">(<span class="hljs-params">signal, payload</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span>(ended) {}
        <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(signal === <span class="hljs-variable constant_">END</span>) {ended = <span class="hljs-literal">true</span>}
        <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(signal === <span class="hljs-title class_">STARt</span>) {talkback = payload}
        <span class="hljs-keyword">else</span> {
          <span class="hljs-title function_">sink</span>(<span class="hljs-variable constant_">PUSH</span>, <span class="hljs-title function_">mapper</span>(payload));
        }
      });
      <span class="hljs-title function_">sink</span>(<span class="hljs-variable constant_">START</span>, <span class="hljs-function">(<span class="hljs-params">signal, payload</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span>(signal === <span class="hljs-variable constant_">CLOSE</span>) {
          <span class="hljs-title function_">talkback</span>(<span class="hljs-variable constant_">CLOSE</span>);
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> {
          <span class="hljs-title function_">talkback</span>(<span class="hljs-variable constant_">PULL</span>);
        }
      })
    }
  }
}

<span class="hljs-comment">// usage:</span>
<span class="hljs-title function_">pipe</span>(<span class="hljs-title function_">fromArray</span>([<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>]), <span class="hljs-title function_">map</span>(<span class="hljs-function"><span class="hljs-params">x</span> =&gt;</span> x + <span class="hljs-number">1</span>), <span class="hljs-title function_">subscribe</span>(<span class="hljs-variable language_">console</span>.<span class="hljs-property">log</span>); <span class="hljs-comment">// prints 2, 3, 4 each on their own line</span>
<span class="hljs-comment">// looks weird without the pipe though...</span>
<span class="hljs-title function_">subscribe</span>(<span class="hljs-variable language_">console</span>.<span class="hljs-property">log</span>)(<span class="hljs-title function_">map</span>(<span class="hljs-function"><span class="hljs-params">x</span> =&gt;</span> x + <span class="hljs-number">1</span>)(<span class="hljs-title function_">fromArray</span>([<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>])));
</code></pre><br >

<div id="additional-reading">Additional reading:</div>

<ul>
<li><a href="https://staltz.com/why-we-need-callbags.html">Why We Need Callbags</a></li>
<li><a href="https://phillipgreenii.github.io/introdunction_into_callbags/">Introduction to Callbags</a></li>
<li><a href="https://blog.krawaller.se/posts/explaining-callbags-via-typescript-definitions/">Explaining Callbags via TypeScript Definitions</a></li>
</ul>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Thu, 13 Jun 2024 00:00:00 GMT</pubDate>
      <title>Form Validations</title>
      <link>https://benkenawell.com/p/2024-06-13-form-validations.html</link>
      <guid>https://benkenawell.com/p/2024-06-13-form-validations.html</guid>
      <description>&lt;p&gt;When my front end is decoupled from my backend via JSON API, how does my front end know what kind of validation logic my backend does on the data I provide it?  Usually, it doesn&amp;#39;t! &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Form Validations</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Form Validations</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2024-06-13T00:00:00+00:00">Jun 13th, 2024</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>When my front end is decoupled from my backend via JSON API, how does my front end know what kind of validation logic my backend does on the data I provide it?  Usually, it doesn&#39;t! <!--more-->  How do I communicate what is a valid input to my user?</p>
<p>I won&#39;t lie, typically I just recreate the validation logic in both places.   It isn&#39;t very DRY, and it&#39;s a pain to change.  My backend is even in Javascript, so you&#39;d think it&#39;d be easier to share logic!  But the server and browser are different places, so how do I communcate that a date picker can only be from yesterday to next month?  In current form, the front end would &quot;know&quot; the requirement and the html produced would be:</p>
<pre><code class="hljs language-html"><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">&quot;date&quot;</span> <span class="hljs-attr">min</span>=<span class="hljs-string">&quot;2024-02-14&quot;</span> <span class="hljs-attr">max</span>=<span class="hljs-string">&quot;2024-03-15&quot;</span> <span class="hljs-attr">name</span>=<span class="hljs-string">&quot;reservation-date&quot;</span>&gt;</span>
</code></pre><p>Then the backend would have some validation logic that checked that requirement again.  A naive example would look like:</p>
<pre><code class="hljs language-javascript">reservationDate &gt; <span class="hljs-string">&quot;2024-02-14&quot;</span> &amp;&amp; reservationDate &lt; <span class="hljs-string">&quot;2024-03-15&quot;</span>
</code></pre><p>If the requirement changed to now allow <em>2 months</em> into the future, which would you change first?  I would relax the backend validation to the new requirement, then the front end.  2 changes in 2 codebases with 2 different deployments.  This case is simple too, since the change is simple.  But I still can&#39;t relax the front end requirement first, the backend would complain even though it <em>looks like a date I can choose</em>.</p>
<h2 id="server-driven-architecture">Server Driven Architecture</h2>
<p>What if I could encode all the requirements and validations into a response, sent by the server, that my front end could <em>generate</em> the right control, with all the right validations baked in?  Relaxing the allowable date from 1 month to 2 months would only requirea change to my backend, no change to my front end.  What does that look like in practice?  If I&#39;m using a JSON API, I might have this shape:</p>
<pre><code class="hljs language-json"><span class="hljs-punctuation">{</span>
  controls<span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-punctuation">{</span>
    type<span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;date&quot;</span><span class="hljs-punctuation">,</span>
    min<span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;2024-02-14&quot;</span><span class="hljs-punctuation">,</span>
    max<span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;2024-03-15&quot;</span><span class="hljs-punctuation">,</span>
    name<span class="hljs-punctuation">:</span> <span class="hljs-string">&quot;reservation-date&quot;</span>
  <span class="hljs-punctuation">}</span><span class="hljs-punctuation">]</span>
<span class="hljs-punctuation">}</span>
</code></pre><p>But notice how similar that is to the <code>input</code> above?</p>
<pre><code class="hljs language-html"><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">&quot;date&quot;</span> <span class="hljs-attr">min</span>=<span class="hljs-string">&quot;2024-02-14&quot;</span> <span class="hljs-attr">max</span>=<span class="hljs-string">&quot;2024-03-15&quot;</span> <span class="hljs-attr">name</span>=<span class="hljs-string">&quot;reservation-date&quot;</span>&gt;</span>
</code></pre><p>I&#39;ve basically encoded all the input attributes in json!  Why not save myself a step and just send the <code>input</code> element itself?  In a web application using React, with a decoupled JSON API, sending HTML is hard and unintuitive.  But if you&#39;re already serving HTML from your backend, this should be a breeze (and likely what you&#39;re already doing).  And templating language will suffice, I like <a href="https://mozilla.github.io/nunjucks/">Nunjucks</a> with <a href="https://expressjs.com/">Express</a></p>
<h2 id="more-complicated-validations">More complicated validations</h2>
<p>What about when the allowable date is dynamic, maybe based on the type of reservation?</p>
<pre><code class="hljs language-html"><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">&quot;radio&quot;</span> <span class="hljs-attr">name</span>=<span class="hljs-string">&quot;reservation-type&quot;</span> <span class="hljs-attr">value</span>=<span class="hljs-string">&quot;one&quot;</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">&quot;radio&quot;</span> <span class="hljs-attr">name</span>=<span class="hljs-string">&quot;reservation-type&quot;</span> <span class="hljs-attr">value</span>=<span class="hljs-string">&quot;two&quot;</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">&quot;radio&quot;</span> <span class="hljs-attr">name</span>=<span class="hljs-string">&quot;reservation-type&quot;</span> <span class="hljs-attr">value</span>=<span class="hljs-string">&quot;three&quot;</span></span>
</code></pre><p>Now you front end has logic to change the date picker min/max values based on the state of the &quot;reservation-type&quot; checkbox.  How do you code that in a <a href="https://medium.com/airbnb-engineering/a-deep-dive-into-airbnbs-server-driven-ui-system-842244c5f5">Server Driven Architecture</a>?  My only answer is I don&#39;t know; and it&#39;s too complicated for me to think of an answer that will satisfy all permutations of the equation.  So in my day job, react app, I encode the difference validations two places: react in the front end and the mutation in the backend.  But in a server driven architecture, we could have an endpoint to receive the date picker and call it every time the value of reservation-type changed.  If we could send html, we would just have three date pickers on the backend we could send.  This is a case for <a href="https://htmx.org/">HTMX</a>; I can reuse the validation logic from my backend and any updates can all be performed in once place, the backend.  I can send and receive and splice html with ease.  Or if there were <a href="https://react.dev/reference/react/use-server">another serialization format my front end could understand</a>, maybe I could use that.</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Fri, 12 Apr 2024 00:00:00 GMT</pubDate>
      <title>Tech Itch</title>
      <link>https://benkenawell.com/p/2024-04-12-tech-itch.html</link>
      <guid>https://benkenawell.com/p/2024-04-12-tech-itch.html</guid>
      <description>&lt;h2 id=&quot;what-is-the-tech-itch&quot;&gt;What is the tech itch?&lt;/h2&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Tech Itch</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Tech Itch</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2024-04-12T00:00:00+00:00">Apr 12th, 2024</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<h2 id="what-is-the-tech-itch">What is the tech itch?<!--more--></h2>
<p>For me, it&#39;s the feeling that a new technology would solve the problem I have in a simple manner, but taking my current project into the new tech is too heady a task and too big a risk for it to <em>maybe</em> solve the problem.</p>
<p>I define a <strong>new technology</strong> as anything me and my co workers are unfamiliar with, not that it&#39;s new to people.  They are technologies that I&#39;m unsure the best use case for and I don&#39;t know somebody who is knowledgable about it.  In these cases, I need to find external sources like blogs or videos to get an idea, or use it for myself.</p>
<p>Usually the itch isn&#39;t satisfied until I&#39;ve determined whether a technology will or won&#39;t work for my use case, until I no longer have that problem, or until I&#39;m not feeling the pain of the problem anymore and no team weight was behind solving it.</p>
<p>This itch is usually what leads me into hobby projects (including this blog!)</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Tue, 09 Apr 2024 00:00:00 GMT</pubDate>
      <title>Repo Local Todos</title>
      <link>https://benkenawell.com/p/2024-04-09-repo-local-todos.html</link>
      <guid>https://benkenawell.com/p/2024-04-09-repo-local-todos.html</guid>
      <description>&lt;p&gt;Not all software I write is very collaborative.  Many hobby projects I start are just me writing a project, &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Repo Local Todos</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Repo Local Todos</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2024-04-09T00:00:00+00:00">Apr 9th, 2024</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>Not all software I write is very collaborative.  Many hobby projects I start are just me writing a project, <!--more--> or part of one, to learn a new concept or <a href="/p/2024-04-12-tech-itch.html">scratch some itch</a> I can&#39;t at work.  There&#39;s not as much structure around these projects, but as they start to grow, I have ideas about things I want <strong>to do</strong> in them that I don&#39;t have time for <em>right now</em>.  Every project has a README, so I use some <a href="https://github.github.com/gfm/#task-list-items-extension-">github-flavored markdown to include some checkboxes</a> in a list of things I wanted to get done, eventually.  That saves it right in the repo and I have it as a reference for later, without going out to another tool.</p>
<p>I can never remember where I leave those tools, and don&#39;t always open them when I&#39;m working on hobby projects anyway.</p>
<h4 id="the-problem">The Problem</h4>
<p>As a professional programmer, I&#39;m competent with git, so I branch my repo to explore and complete my different todos.  Then I can merge (or often rebase) back into my <a id="reference-0" class="reference" data-footnote="0" href="#footnote-0">stable branch for deployment</a>.  Git is a wonderful tool for this, of course, but I start running into issues with my README!</p>
<p>I write down the todos in my README whenever the inspiraiton strikes me.  I usually put my new todos in a separate commit, but I check off todos in the same commit I complete the work in; both put me in an awkward situation with git.  In two branches I&#39;ll have added two todos on the same line and git doesn&#39;t know what to do!  It gives me a merge conflict and leaves my otherwise painless merge/rebase in a seeming state of disarray.  See, my <em>code</em> doesn&#39;t conflict, just the README file!</p>
<h4 id="the-solution">The Solution</h4>
<p>While working through some other, code related, merge conflicts at work the other day, I realized that git is much better about these merges <em>at the file level</em>.  If I add two files in the same directory, git will just add them both in the final merge too.  If I model each todo as a file, then adding todos is as easy and merge free as adding files as long as I plan on <a id="reference-1" class="reference" data-footnote="1" href="#footnote-1">storing them in my repo with git anyway</a>.</p>
<p>If I delete and edit todos in different branches then I&#39;ll have merge conflicts.  But deletion will only happen when I&#39;m done with the task, which will only happen in <em>one branch</em> since my branches focus on my todos.  This also means that any deletions can theoretically supersede any changes; maybe those changes became a new todo, but if a todo file is deleted then it&#39;s done.</p>
<h4 id="an-extension-collaborating-with-others">An Extension, collaborating with others</h4>
<p>Theoretically, I could collaborate with others using this same system.  All the above applies with more than one person; I&#39;m hardly even using a thin layer over git itself.  But I think coordinating work would be hard.  In my branch I&#39;m &quot;assigned&quot; Todo A, but I made that change <em>after</em> you branched off.  If you start doing that work <em>also</em> before merging in our shared branch when I&#39;ve assigned it to myself, we may end up both doing the same work!  With a little bit of git discipline, I still think it&#39;s possible; maybe there&#39;s a <code>todos</code> branch that you could cheaply merge in any time because the todos are so <a href="https://vlcn.io/blog/intro-to-crdts">conflict-free</a>.  Maybe you&#39;re only really considered assigned that work if the patch is in that branch.  It wouldn&#39;t necessarily require a central repository (like github or gitlab) to take advantage of either, just sending the todos branch patch to everyone could work as well.</p>
<p class="footnote" data-footnote="[0]"><a id="footnote-0" href="#reference-0">An aside, only a few of my projects actually <em>have deployments</em>, but I like having a place where I know the code is relatively stable and doesn't contain half completed todos.</a></p>

<p class="footnote" data-footnote="[1]"><a id="footnote-1" href="#reference-1">I like having my todos checked into git so I can see the evolution of work over time, and reveal some of the structure that created the list in the first place.</a></p>

</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Sat, 23 Mar 2024 00:00:00 GMT</pubDate>
      <title>Turbo, Helmet, and CSP</title>
      <link>https://benkenawell.com/p/2024-03-23-Turbo,-Helment,-and-CSP.html</link>
      <guid>https://benkenawell.com/p/2024-03-23-Turbo,-Helment,-and-CSP.html</guid>
      <description>&lt;p&gt;I had some trouble with &lt;a href=&quot;https://turbo.hotwired.dev/&quot;&gt;Turbo&lt;/a&gt;, &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Turbo, Helmet, and CSP</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Turbo, Helmet, and CSP</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2024-03-23T00:00:00+00:00">Mar 23rd, 2024</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>I had some trouble with <a href="https://turbo.hotwired.dev/">Turbo</a>, <!--more--> but I couldn&#39;t find anything online about it.  It appeared my requests were being upgraded automatically from http to https, and I couldn&#39;t figure out why.  I&#39;m building a little node web app for easier calendar entry of my fiance&#39;s irregular work schedule.  It&#39;s mostly a learning project, I wanted to learn a little about <a href="https://en.wikipedia.org/wiki/WebDAV">WebDAV</a> and <a href="/p/2024-03-11-deployments.html">different ways to deploy</a> to production.  I know javascript, node, express pretty well so I started there.  Got everything working, rendering <a href="https://mozilla.github.io/nunjucks/">Nunjucks templates</a> and absolutely no client side javascript.  I learned some stuff about user authentication, and installed <a href="https://www.npmjs.com/package/helmet">Helmet</a> into my application, but didn&#39;t change too many of the defaults.</p>
<p>Then I wanted to learn more about Turbo.  I knew I wanted <em>some</em> javascript to add a little more feedback here and there, but this site was meant as an experiment in good progressive enhancement techniques, so the site didn&#39;t <em>need</em> any javascript running.  I toyed with the idea of <a href="https://htmx.org/">HTMX</a>, but Turbo did so much right out of the gate that I was hooked.  It also seems to have a good story about progressive enhancment as well.  Except forms wouldn&#39;t reload properly!</p>
<p>Finally I just used <code>data-turbo=&quot;false&quot;</code> to disable it for my main form, but that wasn&#39;t want I wanted long term.  None of my requests were doing this before Turbo, but I couldn&#39;t figure out what difference was causing the issue.  See, Turbo swaps the browser&#39;s builtin request mechanisms for ajax calls.  It&#39;s nearly transparent and they&#39;re both network requests, so what&#39;s the big deal?  Browsers treat these two things <em>very differently</em> though, because normal navigation can be trusted (the browser must think, &quot;I made it myself&quot;) but ajax calls are untrusted and <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy">Content Security Policies</a> (CSP) greatly affect how scripts can run.</p>
<p>There&#39;s <a href="https://content-security-policy.com/">a lot of options for CSP header directives</a>, I don&#39;t fully understand them all.  But that&#39;s what Helment is for!  Good defaults and strong security, except even <a href="https://www.npmjs.com/package/helmet#reference">it acknowledges that it&#39;s CSP header may need some configuration</a>. After a little trial and error I settled on the &quot;update insecure requests&quot; directive was the one causing the issue.  My server was requesting the browser upgrade any requests being made, and the browser was happily complying!  It wasn&#39;t happening to normal redirects because the server asks the browser to make those, so there&#39;s no client side involvement.  But with Turbo intercepting those redirect requests, the browser needed to make sure those would be secure, that&#39;s what the server asked for!</p>
<p>End of the story is that I disabled that directive of the CSP header in development and now my forms work <a href="https://turbo.hotwired.dev/handbook/drive#form-submissions">just as advertised</a>!  I don&#39;t expect this to be an issue in production, where I <em>want</em> all requests to be made securely.  Hopefully this little tidbit is helpful!</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Wed, 20 Mar 2024 00:00:00 GMT</pubDate>
      <title>Is React a Framework?</title>
      <link>https://benkenawell.com/p/2024-03-20-is-react-a-framework.html</link>
      <guid>https://benkenawell.com/p/2024-03-20-is-react-a-framework.html</guid>
      <description>&lt;p&gt;Or is React still a library? &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Is React a Framework?</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Is React a Framework?</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2024-03-20T00:00:00+00:00">Mar 20th, 2024</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>Or is React still a library? <!--more--> React started as a library to enhance a webpage from server rendered templates, like <a href="https://rubyonrails.org/">Rails</a>, <a href="https://laravel.com/">Laravel</a>, or <a href="https://www.phoenixframework.org/">Phoenix</a>. Sprinkle a little extra interactivity on and you can enable much more than the browser on its own.  Did this ever mean you were mounting React in multiple places on the page?  I wasn&#39;t there for it, so I don&#39;t know.  I&#39;ve never seen a website like that, but it would fit with progressive enhancement and the ethos of React being a library.</p>
<p>Now, <strong>React <a href="https://nextjs.org/">has a framework</a></strong> and a majority of apps send only enough html over the wire to mount their React app (if any, some apps bootstrap through javascript too!).  With React Server Components (RSC) and Server Side Rendering (SSR), what&#39;s different with React than server rendered templates a couple of decades ago?  They&#39;ve been <a href="https://turbo.hotwired.dev/">making improvements too</a>!</p>
<p><strong>React as a templating language</strong> can be much more expressive than things like Handlebars.  But <a href="https://github.com/ruby/erb">erb</a> and <a href="https://hexdocs.pm/phoenix_live_view/assigns-eex.html">heex</a> also fully embed the language, so Javascript is then the choice language, not the <em>only</em> language.</p>
<p>Then there&#39;s the argument for one language for front and back end, like <a href="https://jamstack.org/">JAMStack</a> has always evangelized.  But most React apps seem to still <em>only be the frontend</em>, communicating wiht a backend that could be in any number of languages.  If it&#39;s doing this, now you need to have 2 deployments, typically 2 repos, etc.</p>
<p>If your Next.js project is your entire app, you might still argue for the better interactivity it provides; since with RSCs it provides server actions and streaming.  But they aren&#39;t the only game in town; <a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html">LiveView</a> takes this up another notch by providing stateful sessions.  <a href="https://hotwired.dev/">Hotwire</a> enables full control communicating back and forth in a language agnostic way through a few html-compliant web components, and even <a href="https://strada.hotwired.dev/">dips strongly into native apps</a>.</p>
<p>If React were still a library, it should be competing with the likes of <a href="https://stimulus.hotwired.dev/">Stimulus</a> not the likes of Rails.  If it is a framework, running as much on a server as in a broswer, then it&#39;s not as batteries included as Rails, nor does it have as refined an ecosystem as Laravel.  It feels like React is straddling two worlds today, but I&#39;m not sure it&#39;s doing either well anymore.</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Mon, 11 Mar 2024 00:00:00 GMT</pubDate>
      <title>Deployments</title>
      <link>https://benkenawell.com/p/2024-03-11-deployments.html</link>
      <guid>https://benkenawell.com/p/2024-03-11-deployments.html</guid>
      <description>&lt;p&gt;How do you deploy your projects? &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Deployments</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Deployments</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2024-03-11T00:00:00+00:00">Mar 11th, 2024</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>How do you deploy your projects? <!--more-->  At work, we use <a href="https://vercel.com/">Vercel</a>, but my dream is to just have a server and be able to take my code anywhere.  I have a little computer at my house I use to host some small docker applications, but nothing that I&#39;ve created.  It&#39;s tough, connecting it to the outside world.  But it&#39;s a problem I&#39;m working on.  For now, I&#39;m happy to pay <a href="https://www.digitalocean.com/">Digital Ocean</a> for some small servers, not even $10/month, to run some stuff.</p>
<p>But writing software and deploying it are two orthognal problems.  If I went with something like Vercel, I would never be able to run it (easily, at least) on my computer at home whenever I figure out the best way to expose that.  Enter <a href="https://kamal-deploy.org/">Kamal</a>, a tool that claims to let you &quot;deploy web apps anywhere&quot;, &quot;from bare metal to cloud vms&quot;.  Which sounds great, that&#39;s the flexibility I want!  Made by the people behind <a href="https://rubyonrails.org/">Rails</a> and <a href="https://basecamp.com/">Basecamp</a> at <a href="https://37signals.com/">37 Signals</a>, whom I&#39;ve been looking up to since starting my web dev career 5 years ago.  They have a good track record, I&#39;ll take a look.</p>
<p>I made a quick Rails app; I have no experience with Rails in particular but all I really need is one route and a <a href="https://stimulus.hotwired.dev/">Stimulus controller </a>, it took me a short morning to create.  It&#39;s not a fancy page, maybe I&#39;ll link to it after it&#39;s been running for a few days.  Two days later, I got it to deploy!  Not bad considering I never even needed to ssh into another machine!  I found a <a href="https://greg.molnar.io/blog/deploying-a-rails-app-with-kamal/">couple</a> <a href="https://www.honeybadger.io/blog/deploy-rails-with-kamal/">tutorials</a> to help me with my <a href="https://doc.traefik.io/traefik/">traefik config</a>, fixed some problems with my <a href="https://www.codingzeal.com/post/managing-secrets-credentials-yml-vs-environment-variables-in-ruby-on-rails">rails master key</a>, and now it&#39;s on the web!</p>
<p>The one external resource Kamal relies on is a container registry.  I didn&#39;t want to sign up for a whole docker account or whatever just to push some dev images up, so I looked for another solution.  I tried Github Container Registry, but I think you need to connect it to a code repo. I&#39;ve been hosting my code on <a href="https://sourcehut.org/">sourcehut</a>, and trying to minimize my reliance on GitHub as well, so I continued to look.  I settled on <a href="https://www.digitalocean.com/products/container-registry">Digital Ocean&#39;s container registry</a>; added that to my <code>config/deploy.yml</code> file and watched <code>kamal deploy</code> push it up!  It felt almost magical.</p>
<p>Kamal has a wealth of other commands to look at logs and run commands.  It makes me feel like I have my own personal command center, capable of commanding all the servers I want with one command.  Able to pull statuses and uptime in the blink of an eye; now I just have to decide if 512 MB is too little memory to run a rails server...</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Sun, 21 Jan 2024 00:00:00 GMT</pubDate>
      <title>Terminal vs TUI</title>
      <link>https://benkenawell.com/p/2024-01-21-terminal-vs-tui.html</link>
      <guid>https://benkenawell.com/p/2024-01-21-terminal-vs-tui.html</guid>
      <description>&lt;p&gt;For a long time I&amp;#39;ve loved using the terminal, but couldn&amp;#39;t quite figure out what I liked about it. &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Terminal vs TUI</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Terminal vs TUI</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2024-01-21T00:00:00+00:00">Jan 21st, 2024</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>For a long time I&#39;ve loved using the terminal, but couldn&#39;t quite figure out what I liked about it. <!--more--> Things I did all the time I could make faster relatively easily, mostly.  Not all software was that way though, even on the terminal.  What was the difference between the software that I could automate away for my daily tasks and the ones I couldn&#39;t?  The ones that had Command Line Interfaces (CLIs) vs Text-based User Interfaces (TUIs).  I wanted to like Text-based User Interfaces (TUIs) because they were on the terminal, and the terminal made things easier for me, past the initial learning curve.  But the two have very different user philosophies, even though they both run in the same place.  The largest different, I&#39;ve decided, is the captive interface.</p>
<h2 id="command-line-interface">Command Line Interface</h2>
<p>A CLI has no captive interface.  It takes your command as input and spits out output.  This allows it to be easily used in scripting and extended to a users content.  Add goodies like aliases in there, and you can redefine how most of these work for you, simplifying and streamlining common workflows for yourself.  The &quot;for yourself&quot; is important.  Designers didn&#39;t have to create the perfect experience for you.  They gave you tools that made it easy to create the perfect experience for yourself.</p>
<p>My favorite types of CLI tools?  Non interactive, plain text (easily parsable) output, and the most useful ones conform to the <a href="https://en.wikipedia.org/wiki/Unix_philosophy">Unix philosophy Point 2</a>:</p>
<blockquote>
<p>Expect the output of every program to become the input to another, as yet unknown, program. Don&#39;t clutter output with extraneous information. Avoid stringently columnar or binary input formats. Don&#39;t insist on interactive input.</p>
</blockquote>
<p>A great example of this that I use every day is a git alias: <code>git logo</code> which expands to <code>git log --oneline</code>; it&#39;s 8 characters instead of 17 and for the amount I type it every day, probably saves me hundreds of key strokes a month (and many typos, <code>--oneline</code> is tough for me for some reason).  Another example is <code>git delete-gone</code>; it strings together a few git commands and grep to delete branches that have been deleted from the origin remote.  I don&#39;t even know what command it exactly runs without looking at it, but it&#39;s a task I know how to do.</p>
<h2 id="text-based-user-interface">Text-based User Interface</h2>
<p>On the other hand, TUIs, or <a href="https://flylib.com/books/en/2.506.1.34/1/">CUIs (captive user interfaces)</a>, are typically keyboard and menu driven experiences that don&#39;t script well. It&#39;s meant for user interaction, often offering fewer keystrokes to accomplish a goal (maybe <a href="https://aerc-mail.org/">reading email</a>), but makes it less extensible.  Keybindings can usually be reconfigured, but you can&#39;t connect it with other applications.</p>
<h2 id="the-inbetween">The Inbetween</h2>
<p>There are a few tools that change how they behave in respoinse to their environment.  Don&#39;t supply the flag, but the tool needs that information?  It will ask for it interactively.  An old example is <code>apt install</code>, where you are then prompted if you want to install the found software, but you can pass a <code>-y</code> flag to ignore this prompt and run the whole command noninteractively.</p>
<p>A better example is <a href="https://github.com/BurntSushi/ripgrep">ripgrep</a>, where it will default to pretty printing in your terminal, but has a plain text mode and other formatting options that make it more suitable for scripting.  It does some checks to pick the best method by default and provides flags to override.</p>
<p>Github&#39;s cli tool is another good example, supply the <code>-b</code> flag and a body and it won&#39;t ask for one.   Don&#39;t supply one and you get an interactive question in your terminal.  I haven&#39;t poked around enough to know if you can turn off all the questions, even without supplying the flags, but it&#39;s a very lightweight interface I appreciate when opening PRs.</p>
<p>Possibly somewhere inbetween are server-client memory models like tmux or kakoune which are used interactively but have detached modes where commands can be sent via a CLI. I like these best because they offer the interactivity of a CUI with the scriptablitly of a CLI. How are these systems built?  I have no idea, but it&#39;s something I plan on exploring.</p>
<p>These &quot;inbetween&quot; softwares demonstrate that software transcends a single philosophy and can pragmatically support multiple, which can provide <em>more users a better experience while keeping the familiarity between modalities</em>.  Software that works best for all users is the best kind of software.</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
    <item>
      <pubDate>Wed, 01 Mar 2023 00:00:00 GMT</pubDate>
      <title>Software I Use</title>
      <link>https://benkenawell.com/p/2023-03-01-software-i-use.html</link>
      <guid>https://benkenawell.com/p/2023-03-01-software-i-use.html</guid>
      <description>&lt;p&gt;I’ve enjoyed using the terminal ever since I found one on my computer.  I’ve seen “scripting” as a way to get a computer to do anything, magical. &lt;/p&gt;
</description>
      <content:encoded><![CDATA[<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'none'">
  <meta name="referrer" content="no-referrer">
  <title>Ben's Blog - Software I Use</title>
  <meta name="description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <link rel="icon" type="image/x-icon" href="">
  <link rel="stylesheet" href="/css/missing.min.css">
  <link rel="stylesheet" href="/css/styles.css">
  <link rel="stylesheet" href="/css/navigation-view-transition.css">
  <link rel="alternate" type="application/atom+xml" title="Ben's Blog" href="/feed.xml">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Ben's Blog">
  <meta property="og:url" content="https://benkenawell.com">
  <meta property="og:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta property="og:image" content="https://benkenawell.com/images/crystal_landscape.jpg">

  <!-- Twitter Meta Tags -->
  <meta name="twitter:card" content="summary">
  <meta property="twitter:url" content="https://benkenawell.com">
  <meta name="twitter:title" content="Ben's Blog">
  <meta name="twitter:description" content="Some blog posts and extraneous information about me, Ben Kenawell">
  <meta name="twitter:image" content="https://benkenawell.com">

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <script src="/scripts/nav-numbers.js" type="module"></script>

  <link rel="stylesheet" href="/css/post.css">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/css/highlight/styles/atom-one-light.min.css" media="(prefers-color-scheme: light)">

  
</head>

<body>
  <header class="navbar hidden@print">
  <nav>
    <ul role="list" style="list-style: decimal">
        <li>
          <a href="/">
            Blog
          </a>
        </li>
        <li>
          <a href="/resume/">
            Resume
          </a>
        </li>
        <li>
          <a href="/portfolio/">
            Portfolio
          </a>
        </li>
        <li>
          <a href="/links/">
            Links
          </a>
        </li>
        <li>
          <a href="/now/">
            Now
          </a>
        </li></ul>
  </nav>
</header>
<main class="crowded">
<header>
  <h1 style="margin-block-end: 0">Software I Use</h1>
  <div style="margin-block: 0; display: flex; justify-content: space-between; color: var(--faded-fg)">
    <time datetime="2023-03-01T00:00:00+00:00">Mar 1st, 2023</time>
    <span class="p-author">Ben</span>
  </div>
</header>


<p>I’ve enjoyed using the terminal ever since I found one on my computer.  I’ve seen “scripting” as a way to get a computer to do anything, magical. <!--more-->  Even magical compared to computers more generally; everything interoperates in a terminal and feedback is consistent in place, if not always in format.</p>
<p>What makes the terminal magical?  The more I learn about the terminal, the more I realize it’s the Unix philosophy and POSIX that has really made the terminal what it is.  It isn’t the terminal directly, but the practices around software that runs in the terminal that really makes it great.</p>
<p>“Everything is text”  and “Everything is a file” abstractions are a great lowest common denominator, especially when used in conjunction with line processors like sed or awk.</p>
<p>But the beauty of the terminal doesn’t end with POSIX or commands written before I was born; there are new utilities that follow this philosophy and have made my work more productive.  They are:</p>
<ul>
<li><a href="#jq">jq</a></li>
<li><a href="#csvq">csvq</a></li>
<li><a href="#fzf">fzf</a></li>
<li><a href="#kakoune">kakoune</a></li>
<li><a href="#ripgrep">ripgrep</a></li>
</ul>
<p id="jq">having an easy way to parse json (a format that gained popularity while I <em>was alive</em>) makes a lot of sense today, and <a href="https://jqlang.github.io/jq/">jq</a> does a great job of following the Unix philosophy.  It plays well with stdin/stdout, other files, etc.</p>

<p id="csvq">like jq, <a href="https://mithrandie.github.io/csvq/">csvq</a> makes it easy to work with csv data sets on the command line, using sql syntax.  Sometimes the data takes a little cleaning up, but sql is such a powerful tool that my coworkers think I'm some sort of wizard after I have the answer to their question 5 seconds after giving me a csv file.</p>

<p id="fzf"><a href="https://github.com/junegunn/fzf">fzf, or the fuzzy search finder</a>, is a unique one in my mind.  It gives me a menu of options but somehow still plays nicely with stdout, using stderr to show the options.  I'm not sure if that use would be frowned upon by Unix purists, but it's very practical in my mind.  It also plays great with the next item, kakoune.</p>

<p id="kakoune"><a href="https://kakoune.org/">kakoune (kak in the terminal)</a> is a great editor, inspired by vim but selection oriented, running in a server/client configuration to really be able to configure it as a light ide.  It gives enough commands `([a-]!&|)` to interact and transform text with other command line tools that this interactive terminal application still integrates very well with other commands (following Unix philosophy).  Getting the lsp integrated was a huge bonus, and their clippy character has been amazingly helpful in finding commands.  I could spend more time setting keybindings, but honestly most commands are pretty straightforward and searchable.  I’ve been excited to find answers to nearly all my questions online already, even though I haven’t heard much about this editor in my day to day. If I had to gripe, I'd complain that formatting commands have been tough for me to figure out.</p>

<p id="ripgrep">finally <a href="https://github.com/BurntSushi/ripgrep">ripgrep</a>, which is supposed to be just a faster, prettier grep.  The default recursive search feature has been very nice, as well as the output controls.  The default gives me plenty of information to the find and edit what I’m looking for, but I can also turn it all off easily enough for piping to other commands. It also respects the gitignore of my project unless I tell it not to, which means I'm always searching through files I want.</p>

<p><a href="https://github.com/jarun/nnn"><em>Bonus: nnn.</em></a>  I’ve never used noice, and I’m still learning nnn.  But it’s nice to have such an interactive file manager in the terminal.  This <em>should</em> integrate very well with kakoune, although I haven&#39;t quite figured out how yet.</p>
<p>The future:  I’ve installed <a href="https://sxmo.org/">sxmo</a> on a <a href="https://www.pine64.org/pinephone/">pinephone</a> I have lying around.  I was able to install all of these (I didn’t try nnn) on it.  I basically have access to all of the same tools on Mac and PostMarketOS/SXMO because of Unix and POSIX.  And once I get ssh setup, I could conceivably connect to it and type on a full keyboard over the internet.</p>
</main>
  <footer class="f-row justify-content:space-between hidden@print">
    <span>&copy; <a href="/about">Benjamin Kenawell</a>, 2023 - 2026</span>
    
  </footer>
</body>

</html>
]]></content:encoded>
    </item>
  </channel>
</rss>