<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
    <channel>
      <title>Brian's Waste of Time</title>
      <link>https://skife.org</link>
      <description></description>
      <generator>Zola (custom template)</generator>
      <language>en</language>
      <atom:link href="https://skife.org/index.xml" rel="self" type="application/rss+xml"/>
      <lastBuildDate>Sun, 01 Feb 2026 00:00:00 +0000</lastBuildDate>
      <item>
          <title>bdsh: A dsh-alike for me</title>
          <pubDate>Sun, 01 Feb 2026 00:00:00 +0000</pubDate>
          <author>Brian McCallister</author>
          <link>https://skife.org/b4/bdsh/</link>
          <guid>https://skife.org/b4/bdsh/</guid>
          <description>&lt;p&gt;I finally put enough of a bow-tie on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;brianm&#x2F;bdsh&quot;&gt;bdsh&lt;&#x2F;a&gt; that I can use it as a daily tool. WOO HOO! I have long wanted a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.netfort.gr.jp&#x2F;~dancer&#x2F;software&#x2F;dsh.html.en&quot;&gt;dsh&lt;&#x2F;a&gt; which did a few additional things, and now I have them. In honor of dsh&#x27;s backronym ambiguity, I am not sure if bdsh is &quot;better (distributed|dancer&#x27;s) shell&quot;, &quot;Brian&#x27;s (distributed|dancer&#x27;s) shell&quot;, or &quot;BreakDancer&#x27;s Shell&quot;. Regardless, I am super happy with this little tool :-) So, what does it do differently?&lt;&#x2F;p&gt;
&lt;p&gt;First, it has the idea of a consensus view for the outputs frmo each host. When running it builds and displays a consensus view of the most common line-by-line output, with drill down into where it differs. This is probably best illustrated with a demo:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;skife.org&#x2F;b4&#x2F;bdsh&#x2F;cowsay.gif&quot; alt=&quot;cowsay demo&quot; title=&quot;cowsay across three hosts&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This demo shows running cowsay across three hosts, rendering the unified output view, and drilling down into a difference.&lt;&#x2F;p&gt;
&lt;p&gt;Second, notice it is running in &lt;code&gt;tmux&lt;&#x2F;code&gt;. This lets you switch over to the windows and see them, and critically if something there requires interaction, you can interact&lt;side-note&gt;a common thing I use it for is &lt;code&gt;bdsh :bsd -- sudo &quot;sh -c &#x27;freebsd-update fetch &amp;amp;&amp;amp; freebsd-update install; pkg update &amp;amp;&amp;amp; pkg upgrade&#x27;&quot;&lt;&#x2F;code&gt; which wants input on updates. You can circumvent this, but FreeBSD makes you feel guilty when you do.&lt;&#x2F;side-note&gt;. If you are doing this across hundreds of hosts, not so great, but I am ususally doing it across a reasonable number, so is actually useful. I also added a heuristic to detect if it thinks a given host is waiting on input, and changing the status indactor to a little blinking keyboard:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;skife.org&#x2F;b4&#x2F;bdsh&#x2F;input.gif&quot; alt=&quot;input demo&quot; title=&quot;input needed on hosts&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Window 0 in tmux is the unified output rendering process. When it thinks input is desired it does the little kwyboard icon and tells you the window number, so you can jump to it quickly. That window 0 process will exit on &lt;code&gt;q&lt;&#x2F;code&gt; but the actual invocations will continue in their windows, exiting their window when they finish, and cleaning up tmux.&lt;&#x2F;p&gt;
&lt;p&gt;Finally, in these examples I list the hosts to execute on, but it can pull from a hosts file at &lt;code&gt;~&#x2F;.config&#x2F;bdsh&#x2F;hosts&lt;&#x2F;code&gt;, from a shell command, or script, etc. You can &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;brianm&#x2F;bdsh&#x2F;blob&#x2F;main&#x2F;doc&#x2F;bdsh-extra.md#examples&quot;&gt;read about it&lt;&#x2F;a&gt;. The format of output here is just line delimited hostnames with optional tags, in the form:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;hati.brianm.dev         :bsd    :cloud&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;h0001.brianm.dev        :arch   :cloud&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;h0002.brianm.dev        :arch   :cloud&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;pancake.home            :bsd    :home&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;freki.home              :bsd    :home&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;badb.home               :bsd    :home&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;v0003.home              :arch   :home&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;v0004.home              :arch   :home&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;m0001.home              :bsd    :home&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;m0002.home              :bsd    :home&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;m0003.barn              :bsd    :barn&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;m0004.barn              :bsd    :barn&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Where you can filter on tags, like: &lt;code&gt;bdsh :bsd:home -- do_stuff&lt;&#x2F;code&gt; which &lt;code&gt;AND&lt;&#x2F;code&gt;s or &lt;code&gt;bdsh :barn,:cloud -- do_stuff&lt;&#x2F;code&gt; which &lt;code&gt;OR&lt;&#x2F;code&gt;s. Of course, you can combine them as well, with &lt;code&gt;AND&lt;&#x2F;code&gt; binding tighter. With no filter, everything is selected.&lt;&#x2F;p&gt;
&lt;p&gt;Currently &lt;code&gt;bdsh&lt;&#x2F;code&gt; is packaged for homebrew (&lt;code&gt;brew install brianm&#x2F;tools&#x2F;bdsh&lt;&#x2F;code&gt;) and arch (&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;aur.archlinux.org&#x2F;packages&#x2F;bdsh&quot;&gt;bdsh in AUR&lt;&#x2F;a&gt;), but I&#x27;ll probably publish a deb soon, and once I think it is stable, and if there is evidence of anyone besides myself using it, see if I can get a port&#x2F;pkg merged for FreeBSD. &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;brianm&#x2F;bdsh&quot;&gt;Source&lt;&#x2F;a&gt;, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;brianm&#x2F;bdsh&#x2F;blob&#x2F;main&#x2F;README.md&quot;&gt;basic docs&lt;&#x2F;a&gt;, and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;brianm&#x2F;bdsh&#x2F;blob&#x2F;main&#x2F;doc&#x2F;bdsh-extra.md&quot;&gt;more docs&lt;&#x2F;a&gt; all on github, Apache-2.0 licensed.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>D&amp;D (prep) with Claude Code</title>
          <pubDate>Sun, 11 Jan 2026 00:00:00 +0000</pubDate>
          <author>Brian McCallister</author>
          <link>https://skife.org/b4/dnd-with-cc/</link>
          <guid>https://skife.org/b4/dnd-with-cc/</guid>
          <description>&lt;p&gt;I have teenage kids, and they are possibly dorkier than me, which is awesome. Since the pandemic, we have had a D&amp;amp;D game going, off and on, with some of their friends and friends&#x27; parents. It&#x27;s been super fun. I am, however, the perma-DM. I am 99% fine with this, as I &lt;em&gt;love&lt;&#x2F;em&gt; the world building aspects which the DM gets to lean more heavily into than players.&lt;&#x2F;p&gt;
&lt;p&gt;Sometime this past year I started doing my session prep in Claude Code. It started with discussing ideas for arcs and settings, and rapidly turned into shockingly useful session prep. While I am impressed by frontier models&#x27; ability to program, I am in &lt;em&gt;awe&lt;&#x2F;em&gt; of their ability to help plan and prep D&amp;amp;D because this is very much about predicting human behavior, setting up interesting situations and being ready for what real people may do in them. That Claude can make rustc sing is nice, that it can make my &lt;em&gt;players&lt;&#x2F;em&gt; sing was unexpected.&lt;&#x2F;p&gt;
&lt;p&gt;I have, for a long time, been keeping my notes in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;obsidian.md&#x2F;&quot;&gt;Obsidian&lt;&#x2F;a&gt; and git (private GH repo), laid out more or less like:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── encounters&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── agadol_ambush.md&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── Audience with Kerral.md&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   └── [more, elided ...]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── factions&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── Arjun.md&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── Tollkeepers.md&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   └── [more elided ...]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── ideas&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── beechport_arc_ideas.md&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── The Red Dragon.md&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   └── [more, elided ...]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── npc&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── Agustin.md&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── Chalan.md&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   └── [and more, elided...]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── pc&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── Borin - Notes.md&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── Ethex - Notes.md&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── Nameless - Notes.md&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   └── Ragux - Notes.md&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;├── places&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── Aurum.md&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   ├── Beechport.md&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;│   └── [and more, elided ...]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;└── sessions&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ├── [and more elided ...]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    ├── Session 026 - 2026-01-03.md&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    └── Session 027 - 2026-01-17.md&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Claude took to this structure like, well, a coding agent to a tree of markdown :-) I have long described myself as working best when I can &quot;pair think&quot; -- put me at a whiteboard with another programmer and I, at least, am more than 2x as effective. &lt;side-note&gt;Cannot vouch for any given pair-thinker, but this seems to be common enough that I suspect the sum is greater than the parts.&lt;&#x2F;side-note&gt; Rubber ducking, even with &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;watch?v=GSDy9Cj3oSM&quot;&gt;Jonathan Aquino&#x27;s excellent assistance&lt;&#x2F;a&gt;, &lt;side-note&gt;Thank you for that video, Jon, I still use it!&lt;&#x2F;side-note&gt; just is not the same. Turns out DM planning works this way to, and Claude is a good enough pair-thinker that the results are great.&lt;&#x2F;p&gt;
&lt;p&gt;Mechanically, I fire up a claude-code web session to plan the next session and just let thoughts wander all over. I let claude organize what we come up with into NPC, Faction Idea, or Session files as it makes sense, then iterate on key things for the next session. I generally have a bunch of &quot;here is other stuff going on&quot; in a per-arc doc (&lt;code&gt;beechport_arc_ideas.md&lt;&#x2F;code&gt; above, for example), which act as a catch all for &quot;this might happen, and I think here are some things that might fall out of it...&quot; type thoughts. The arc-doc is the big picture context for the next session of planning, and it will refer to key NPCs, places, factions, etc.&lt;&#x2F;p&gt;
&lt;p&gt;Historically I used something similar to the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;slyflourish.com&#x2F;eight_steps_2023.html&quot;&gt;Lazy DM approach&lt;&#x2F;a&gt;, but have been trying Brennan Lee Mulligan&#x27;s &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;DungeonMasters&#x2F;comments&#x2F;1q24b11&#x2F;brennan_lee_mulligans_prep&#x2F;&quot;&gt;toy approach&lt;&#x2F;a&gt; lately. In both cases, Claude has been able to take a description of the materials I want at the table and produce them well enough to almost just use.&lt;&#x2F;p&gt;
&lt;p&gt;As I mentioned, the thing that has most impressed me is in encounter planning. Claude is surprisingly good at predicting what players will do in given situations, estimating the time for a given encounter, estimating the difficulty, highlighting things which will likely have an emotional impact on a specific player, etc. It can predict good beats to highlight aspects of individual characters even, and help set opportunities for specific individuals to showcase some aspect of themselves. It sometimes gets confused between NPCs and PCs, and will optimize to allow an NPC to shine, but it course corrects on this well enough.&lt;&#x2F;p&gt;
&lt;p&gt;The main thing that burnt me early was when Claude would go off the rails &lt;side-note&gt;Is it still hallucination if the thing it is hallucinating is completely imaginary and poorly defined in the first place?&lt;&#x2F;side-note&gt; on aspects of the world or players and I didn&#x27;t bother to correct it, because, who cares. Later when it referred to past notes the misunderstandings started magnifying and the overall quality as a planning partner deteriorated greatly. Spending time getting on the same page, and getting Claude to record it in the various docs, was &lt;em&gt;well&lt;&#x2F;em&gt; worth it, as now it makes really good connections without help.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Fun With HTTP Caching</title>
          <pubDate>Sat, 27 Dec 2025 00:00:00 +0000</pubDate>
          <author>Brian McCallister</author>
          <link>https://skife.org/b4/epithet-discovery-caching/</link>
          <guid>https://skife.org/b4/epithet-discovery-caching/</guid>
          <description>&lt;p&gt;The two fun problems in computer science: cache invalidation, naming things, and off-by-one errors. Today I want to talk about the first one.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve been working on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;epithet-ssh&#x2F;epithet&quot;&gt;Epithet&lt;&#x2F;a&gt;, an SSH certificate authority &lt;side-note&gt;Really, an agent, a CA, and policy server&lt;&#x2F;side-note&gt; that makes certificate-based authentication easy. Part of the system involves a &quot;discovery&quot; endpoint where clients learn which hosts the CA handles. The question: how do you cache this efficiently while still allowing updates to propagate?&lt;&#x2F;p&gt;
&lt;p&gt;The policy server component provides a discovery endpoint for this, which serves a simple JSON document:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;&amp;quot;matchPatterns&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;&amp;quot;*.example.com&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;prod-*&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This content rarely changes, only when you add a new host &lt;em&gt;pattern&lt;&#x2F;em&gt; to your policy, generally. But when it does change, you want clients to pick it up reasonably quickly. My first implementation used aggressive caching:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;w.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;Header&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;Set&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;&amp;quot;Cache-Control&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;max-age=31536000, immutable&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Cache it forever! The URL is content-addressed (&lt;code&gt;&#x2F;d&#x2F;{hash}&lt;&#x2F;code&gt;), so if the content changes, the hash changes, and you get a new URL. Problem solved, right?&lt;&#x2F;p&gt;
&lt;p&gt;Not quite. The client learns the discovery URL from a &lt;code&gt;Link&lt;&#x2F;code&gt; header in other responses:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Link: &amp;lt;&#x2F;d&#x2F;abc123&amp;gt;; rel=&amp;quot;discovery&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;But the client caches this URL. If the server starts returning a &lt;em&gt;different&lt;&#x2F;em&gt; URL in the Link header, the client won&#x27;t notice until... when exactly?&lt;&#x2F;p&gt;
&lt;p&gt;The discovery document at &lt;code&gt;&#x2F;d&#x2F;abc123&lt;&#x2F;code&gt; is immutable and cached forever. The client has no reason to re-fetch it. And it has no reason to make other requests that would reveal the new Link header. We&#x27;ve created an immortal cache entry.&lt;&#x2F;p&gt;
&lt;p&gt;The obvious fix: use &lt;code&gt;ETag&lt;&#x2F;code&gt; and &lt;code&gt;If-None-Match&lt;&#x2F;code&gt; for cache revalidation.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;w.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;Header&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;Set&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;&amp;quot;Cache-Control&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;max-age=300&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;  &#x2F;&#x2F; 5 minutes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;w.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;Header&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;Set&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;&amp;quot;ETag&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; `&amp;quot;`&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span&gt; hash&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; +&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; `&amp;quot;`&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; r.Header.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;Get&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;&amp;quot;If-None-Match&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; ==&lt;&#x2F;span&gt;&lt;span&gt; expectedETag {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    w.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;WriteHeader&lt;&#x2F;span&gt;&lt;span&gt;(http.StatusNotModified)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;    return&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This works: after 5 minutes, the client revalidates. If the content hasn&#x27;t changed, it gets a quick 304. If it has, it gets the new content.&lt;&#x2F;p&gt;
&lt;p&gt;But there&#x27;s a wrinkle. I want to support deploying discovery documents to a CDN or static file server (probably S3 fronted by a CDN, to be honest). If we rely on each of these URLs sending a 404 or a redirect then we need to either update every URL ever published when we make a change (to redirect to the new location), maintain a very long chain of redirects, or rely on out of band behavior if they start 404&#x27;ing (know to go fetch something which will include the new Link header). YUCK.&lt;&#x2F;p&gt;
&lt;p&gt;So, a layer of indirection solves everything, right? What if we separate &quot;what&#x27;s the current discovery document?&quot; from &quot;what&#x27;s in that document?&quot;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;pointer&lt;&#x2F;strong&gt; (&lt;code&gt;&#x2F;d&#x2F;current&lt;&#x2F;code&gt;) that says &quot;the current discovery is at &lt;code&gt;&#x2F;d&#x2F;abc123&lt;&#x2F;code&gt;&quot;&lt;&#x2F;li&gt;
&lt;li&gt;The &lt;strong&gt;content&lt;&#x2F;strong&gt; (&lt;code&gt;&#x2F;d&#x2F;abc123&lt;&#x2F;code&gt;) which is truly immutable&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The pointer can have a short cache lifetime. The content can be cached forever. When the content changes:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Deploy new content at &lt;code&gt;&#x2F;d&#x2F;xyz789&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Update the pointer to redirect to &lt;code&gt;&#x2F;d&#x2F;xyz789&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Clients&#x27; cached pointers expire after 5 minutes&lt;&#x2F;li&gt;
&lt;li&gt;They fetch the pointer, get redirected to the new content&lt;&#x2F;li&gt;
&lt;li&gt;Old content at &lt;code&gt;&#x2F;d&#x2F;abc123&lt;&#x2F;code&gt; can stay around (or be garbage collected later)&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;The built-in policy server is in Go, so the redirect handler is trivial:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;func&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; NewDiscoveryRedirectHandler&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E36209;&quot;&gt;hash&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; string&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; http&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;HandlerFunc&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;    return func&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E36209;&quot;&gt;w&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; http&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;ResponseWriter&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E36209;&quot;&gt; r&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;http&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;Request&lt;&#x2F;span&gt;&lt;span&gt;) {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        w.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;Header&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;Set&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;&amp;quot;Cache-Control&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;max-age=300&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        w.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;Header&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;Set&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;&amp;quot;Location&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;&#x2F;d&#x2F;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;+&lt;&#x2F;span&gt;&lt;span&gt;hash)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;        w.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;WriteHeader&lt;&#x2F;span&gt;&lt;span&gt;(http.StatusFound)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The content handler remains unchanged with immutable caching:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;w.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;Header&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;Set&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;&amp;quot;Cache-Control&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;max-age=31536000, immutable&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The Link header now always points to &lt;code&gt;&#x2F;d&#x2F;current&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;w.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;Header&lt;&#x2F;span&gt;&lt;span&gt;().&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;Set&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;&amp;quot;Link&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;&amp;lt;&#x2F;d&#x2F;current&amp;gt;; rel=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;discovery&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;\&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;HTTP caches handle this beautifully:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Client requests &lt;code&gt;&#x2F;d&#x2F;current&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Gets 302 redirect to &lt;code&gt;&#x2F;d&#x2F;abc123&lt;&#x2F;code&gt; with &lt;code&gt;max-age=300&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Follows redirect, gets content with &lt;code&gt;immutable&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Both responses are cached appropriately&lt;&#x2F;li&gt;
&lt;li&gt;After 5 minutes, the redirect expires&lt;&#x2F;li&gt;
&lt;li&gt;Next request fetches &lt;code&gt;&#x2F;d&#x2F;current&lt;&#x2F;code&gt; again&lt;&#x2F;li&gt;
&lt;li&gt;Might get same redirect (cache hit on content) or new one&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;The content-addressed URLs can be served from anywhere: a CDN, S3, a static file server. They never need invalidation logic. The redirect endpoint is the only &quot;dynamic&quot; part, and it&#x27;s just returning a Location header.&lt;&#x2F;p&gt;
&lt;p&gt;The final implementation is about 10 lines of code. Most of the work was figuring out the right design :-)&lt;&#x2F;p&gt;
&lt;p&gt;The best part: this doesn&#x27;t mandate a specific implementation. The contract between client and server is just &quot;respect HTTP caching headers.&quot;&lt;&#x2F;p&gt;
&lt;p&gt;A policy server could:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Use this same redirect pattern&lt;&#x2F;li&gt;
&lt;li&gt;Use ETag with conditional requests&lt;&#x2F;li&gt;
&lt;li&gt;Use a short &lt;code&gt;max-age&lt;&#x2F;code&gt; without &lt;code&gt;immutable&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Some other scheme entirely&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;As long as the server sets appropriate &lt;code&gt;Cache-Control&lt;&#x2F;code&gt; headers and the client respects them, it works. The client just uses a standard RFC 7234 compliant HTTP cache (in my case, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gregjones&#x2F;httpcache&quot;&gt;&lt;code&gt;github.com&#x2F;gregjones&#x2F;httpcache&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Sometimes the best solution is realizing HTTP already solved your problem decades ago.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Want to try Epithet? Check out the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;epithet-ssh&#x2F;epithet&quot;&gt;GitHub repo&lt;&#x2F;a&gt;.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Kong + CUE</title>
          <pubDate>Mon, 08 Dec 2025 00:00:00 +0000</pubDate>
          <author>Brian McCallister</author>
          <link>https://skife.org/b4/kongcue/</link>
          <guid>https://skife.org/b4/kongcue/</guid>
          <description>&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;alecthomas&#x2F;kong&quot;&gt;Kong&lt;&#x2F;a&gt; is my go-to CLI parser in go, so I used it for &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;epithet-ssh&#x2F;epithet&quot;&gt;epithet&lt;&#x2F;a&gt;. It has pretty good built-in configuration handling, I appreciate how it lines up config values with CLI flags by basically treating config as a serialized tree of it&#x27;s structs. This is great for simple things, but for epithet I needed slightly more complex mapping, with dynamic keys (user identities) and such. I wanted to keep the core of its model though as it&#x27;s really nice.&lt;&#x2F;p&gt;
&lt;p&gt;So, I wrote a thing to configure it using &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;cuelang.org&quot;&gt;CUE&lt;&#x2F;a&gt;. CUE seems complicated if you read its docs, because it dives straight into it&#x27;s prolog-y evaluation roots, but you can basicaly ignore that for simple use cases (and then use it for hairy ones). It turns out I really liked this, so extracted it to a library, meet &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;brianm&#x2F;kongcue&quot;&gt;kongcue&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;It works mostly like the built-in Kong config mechanism, except it happily handles YAML, JSON, and CUE syntax, unifies across multiple files elegantly, and allows me to treat validation issues as &lt;em&gt;configuration&lt;&#x2F;em&gt; issues (with useful error messages, lone and column pointers, etc) if you use a config file, or fallback to kong&#x27;s flag based errors if you don&#x27;t use a config file. It can also simply generate a CUE schema file for artbitrary config documentation and validation!&lt;&#x2F;p&gt;
&lt;p&gt;Example time! Let&#x27;s take a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;raw.githubusercontent.com&#x2F;brianm&#x2F;kongcue&#x2F;refs&#x2F;heads&#x2F;main&#x2F;example&#x2F;main.go&quot;&gt;small hello-world style kong app&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;package&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; main&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;import&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;	&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;fmt&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;	&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;github.com&#x2F;alecthomas&#x2F;kong&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;	&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;github.com&#x2F;brianm&#x2F;kongcue&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;func&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; main&lt;&#x2F;span&gt;&lt;span&gt;() {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;	var&lt;&#x2F;span&gt;&lt;span&gt; c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; cli&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	ktx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; :=&lt;&#x2F;span&gt;&lt;span&gt; kong.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;Parse&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;c, kongcue.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;AllowUnknownFields&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;&amp;quot;messy&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	ktx.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;FatalIfErrorf&lt;&#x2F;span&gt;&lt;span&gt;(ktx.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;Run&lt;&#x2F;span&gt;&lt;span&gt;(c))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; cli&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; struct&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	Name&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;      string&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;            `default:&amp;quot;world&amp;quot; help:&amp;quot;The name of the person to greet&amp;quot;`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	Stuff&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;     bool&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;              `required:&amp;quot;&amp;quot; help:&amp;quot;A required flag, about stuff&amp;quot;`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	Greet&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;     GreetCmd&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;          `cmd:&amp;quot;&amp;quot; help:&amp;quot;Issue a greeting&amp;quot;`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	Depart&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;    DepartCmd&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;         `cmd:&amp;quot;&amp;quot; help:&amp;quot;Issue a valediction&amp;quot;`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	Config&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;    kongcue&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;Config&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;    `default:&amp;quot;.&#x2F;example.{yml,json,cue}&amp;quot; sep:&amp;quot;;&amp;quot; help:&amp;quot;Config file paths&amp;quot;`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	ConfigDoc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; kongcue&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;ConfigDoc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; `cmd:&amp;quot;&amp;quot; help:&amp;quot;Print config schema&amp;quot;`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	message&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; GreetCmd&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; struct&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	Excited&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; int&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; `default:&amp;quot;0&amp;quot; help:&amp;quot;How excited are you to see this person?&amp;quot;`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;func&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E36209;&quot;&gt;g &lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;GreetCmd&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; Run&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E36209;&quot;&gt;c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;cli&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; error&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; elided&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;type&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; DepartCmd&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; struct&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	Sadness&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; int&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; `help:&amp;quot;How sad are you to be leaving?&amp;quot; required:&amp;quot;&amp;quot;`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;func&lt;&#x2F;span&gt;&lt;span&gt; (&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E36209;&quot;&gt;g &lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;DepartCmd&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; Run&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #E36209;&quot;&gt;c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; *&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;cli&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; error&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; elided&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It has a couple sub-commands, some flags, all of it is just demo-ware. Note the lines:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	Config    kongcue.Config&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;    `default:&amp;quot;.&#x2F;example.{yml,json,cue}&amp;quot; sep:&amp;quot;;&amp;quot; help:&amp;quot;Config file paths&amp;quot;`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	ConfigDoc kongcue.ConfigDoc&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; `cmd:&amp;quot;&amp;quot; help:&amp;quot;Print config schema&amp;quot;`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;These set up configuration handling, and a sub-command to dump the schema and documentation! This does what it looks like, globs across the possible config file names, loads all that match, etc. The schema part is nifty, as it generats config docs, effectively. Invoke &lt;code&gt;example config-doc&lt;&#x2F;code&gt; and you get:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;cue&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F; Configuration schema for validating config files.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F; This schema is written in CUE, a configuration language that&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F; validates and defines data. Learn more at https:&#x2F;&#x2F;cuelang.org&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F; To validate your config file against this schema:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F;   1. Save this schema to a file (e.g., schema.cue)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F;   2. Run: cue vet -d &amp;#39;#Root&amp;#39; schema.cue your-config.yaml&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F; Fields marked with ? are optional. Fields without ? are required.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#Root:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; close&lt;&#x2F;span&gt;&lt;span&gt;({&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; The name of the person to greet&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	name?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; A required flag, about stuff&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	stuff:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; bool&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Issue a greeting&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	greet?: #Greet&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Issue a valediction&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	depart?: #Depart&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	messy?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;  _&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;})&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#Depart:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; close&lt;&#x2F;span&gt;&lt;span&gt;({&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; How sad are you to be leaving?&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	sadness:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; int&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;})&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#Greet:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; close&lt;&#x2F;span&gt;&lt;span&gt;({&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; How excited are you to see this person?&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	excited?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; int&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;})&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;While this is a bit obscure, it is not that hard to follow even if you are not familiar with CUE&#x27;s schema&#x2F;constraint declarations.&lt;&#x2F;p&gt;
&lt;p&gt;If you invoke it with a bad config file, you get a nice config error:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;kongcue&#x2F;example&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; on  main ❯ cat bad.yml&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;name:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;test&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;stuff:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;walrus&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;kongcue&#x2F;example&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; on  main ❯&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;kongcue&#x2F;example&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; on  main ❯ go build&lt;&#x2F;span&gt;&lt;span&gt; &amp;amp;&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; .&#x2F;example&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; --config&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; .&#x2F;bad.yml&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;example:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; error: stuff: conflicting values &amp;quot;walrus&amp;quot; and bool&lt;&#x2F;span&gt;&lt;span&gt; (mismatched&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; types string and bool&lt;&#x2F;span&gt;&lt;span&gt;):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;                    &#x2F;Users&#x2F;brianm&#x2F;src&#x2F;github.com&#x2F;brianm&#x2F;kongcue&#x2F;example&#x2F;bad.yml:2:8&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;kongcue&#x2F;example&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; on  main ❯&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Without a config file (default is not present, none specified) it gives you CLI oriented errors:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;kongcue&#x2F;example&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; on  main ❯ .&#x2F;example&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;example:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; error: missing flags:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; --stuff&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;kongcue&#x2F;example&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; on  main ❯&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It has a simple escape hatch for non-CLI compatible config sections, which you see in the above example:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;go&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	ktx&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; :=&lt;&#x2F;span&gt;&lt;span&gt; kong.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;Parse&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span&gt;c, kongcue.&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;AllowUnknownFields&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;&amp;quot;messy&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;))&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This allows the &lt;code&gt;messy&lt;&#x2F;code&gt; thing in the example config, even though it does not appear in the kong struct tree.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;Brian&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;stuff&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; true&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;depart&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;  sadness&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 3&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;messy&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;  woof&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;meow&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;  splat&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 7&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;kongcue.AllowUnknownFields(&quot;messy&quot;)&lt;&#x2F;code&gt; tells it to just accept anything in the &lt;code&gt;messy&lt;&#x2F;code&gt; value. At some point I&#x27;ll add a mechanism to define a schema for these, but for now it just relies on deserializing to structs erroring out.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;cue.Value&lt;&#x2F;code&gt; for the config tree is made available as a kong binding, so you can get it in whatever command you need to pull additional values from. This allows epithet policy configs like:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;users&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;  alice@example.com&lt;&#x2F;span&gt;&lt;span&gt;: [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;admin&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; eng&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;  bob@example.com&lt;&#x2F;span&gt;&lt;span&gt;: [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;eng&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;  charlie@example.com&lt;&#x2F;span&gt;&lt;span&gt;: [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;ops&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;  diana@example.com&lt;&#x2F;span&gt;&lt;span&gt;: [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;admin&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; ops&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;which can be defined nicely in CUE, but not naturally kong&#x27;s cli constraints.&lt;&#x2F;p&gt;
&lt;p&gt;For a more complex, and real, schema generated from epithet, take a look at epithet&#x27;s:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;cue&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F; Configuration schema for validating config files.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F; This schema is written in CUE, a configuration language that&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F; validates and defines data. Learn more at https:&#x2F;&#x2F;cuelang.org&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F; To validate your config file against this schema:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F;   1. Save this schema to a file (e.g., schema.cue)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F;   2. Run: cue vet -d &amp;#39;#Root&amp;#39; schema.cue your-config.yaml&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;&#x2F;&#x2F; Fields marked with ? are optional. Fields without ? are required.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#Root:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; close&lt;&#x2F;span&gt;&lt;span&gt;({&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Print version information&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	version?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; bool&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Increase verbosity (-v for debug, -vv for trace)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	verbose?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; int&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Path to log file (supports ~ expansion)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	log_file?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Disable TLS certificate verification (NOT RECOMMENDED)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	insecure?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; bool&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Path to PEM file with trusted CA certificates&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	tls_ca_cert?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Start the epithet agent (or use &amp;#39;agent inspect&amp;#39; to inspect state)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	agent?: #Agent&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Invoked during ssh invocation in a &amp;#39;Match exec ...&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	match?: #Match&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Run the epithet CA server&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	ca?: #Ca&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Run the policy server with OIDC-based authorization&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	policy?: #Policy&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Authentication commands (OIDC, SAML, etc.)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	auth?: #Auth&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;})&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#Agent:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; close&lt;&#x2F;span&gt;&lt;span&gt;({&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Match patterns&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	match?: [...&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; CA URL (repeatable, format: priority=N:https:&#x2F;&#x2F;url or https:&#x2F;&#x2F;url)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	ca_url?: [...&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Authentication command&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	auth?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Per-request timeout for CA requests&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	ca_timeout?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; int&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Circuit breaker cooldown for failed CAs&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	ca_cooldown?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; int&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Start the epithet agent&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	start?: #AgentStart&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Inspect broker state (certificates, agents)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	inspect?: #AgentInspect&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;})&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#AgentInspect:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; close&lt;&#x2F;span&gt;&lt;span&gt;({&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Broker socket path (overrides config-based discovery)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	broker?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Output in JSON format&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	json?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; bool&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;})&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#AgentStart:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; close&lt;&#x2F;span&gt;&lt;span&gt;({})&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#Auth:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; close&lt;&#x2F;span&gt;&lt;span&gt;({&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Authenticate using OIDC&#x2F;OAuth2 (Google Workspace, Okta, Azure AD, etc.)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	oidc?: #AuthOidc&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;})&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#AuthOidc:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; close&lt;&#x2F;span&gt;&lt;span&gt;({&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; OIDC issuer URL (e.g., https:&#x2F;&#x2F;accounts.google.com)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	issuer:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; OAuth2 client ID&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	client_id:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; OAuth2 client secret (optional if using PKCE)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	client_secret?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; OAuth2 scopes (comma-separated)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	scopes?: [...&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;})&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#Ca:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; close&lt;&#x2F;span&gt;&lt;span&gt;({&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; URL for policy service&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	policy:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Path to ca private key&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	key?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Address to listen on&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	listen?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;})&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#Match:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; close&lt;&#x2F;span&gt;&lt;span&gt;({&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Remote host (%h)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	host:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Remote port (%p)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	port:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; int&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Remote user (%r)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	user:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Connection hash (%C)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	hash:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; ProxyJump configuration (%j)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	jump?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Broker socket path&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	broker?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;})&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;#Policy:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; close&lt;&#x2F;span&gt;&lt;span&gt;({&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Address to listen on&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	listen?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; OIDC issuer URL&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	oidc_issuer?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; OIDC audience (client ID)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	oidc_audience?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; CA public key (URL, file path, or literal SSH key)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	ca_pubkey?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;	&#x2F;&#x2F; Default certificate expiration (e.g., 5m)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	default_expiration?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	users?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;              _&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	defaults?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;           _&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;	hosts?:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;              _&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;})&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It&#x27;s not &lt;em&gt;good&lt;&#x2F;em&gt; docs, but it directly generated from the code, so it is at least accurate, and close to free :-)&lt;&#x2F;p&gt;
&lt;p&gt;I think the CUE website does a pretty bad job of explaining its value, to be honest. It&#x27;s a darned useful tool even in the small. The website just hides that basic usefulness behind piles of articles of the &quot;look at all the really powerful stuff it can do&quot; for advanced cases.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Setting up Epithet as of v0.1.7</title>
          <pubDate>Sun, 30 Nov 2025 00:00:00 +0000</pubDate>
          <author>Brian McCallister</author>
          <link>https://skife.org/b4/epithet-setup-v0.1.7/</link>
          <guid>https://skife.org/b4/epithet-setup-v0.1.7/</guid>
          <description>&lt;h2 id=&quot;the-goal&quot;&gt;The Goal&lt;&#x2F;h2&gt;
&lt;p&gt;We want to set up a real &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;epithet-ssh&#x2F;epithet&#x2F;&quot;&gt;epithet&lt;&#x2F;a&gt; system, end to end, and use it. Because everything is self hosted, there are a number of steps:&lt;&#x2F;p&gt;
&lt;ol start=&quot;0&quot;&gt;
&lt;li&gt;Install epithet on your local Mac&lt;&#x2F;li&gt;
&lt;li&gt;Configure an SSO provider&lt;&#x2F;li&gt;
&lt;li&gt;Set up CA and Policy services&lt;&#x2F;li&gt;
&lt;li&gt;Configure the epithet agent&lt;&#x2F;li&gt;
&lt;li&gt;Create a cloud-init config for new VMs to rely on the CA&lt;&#x2F;li&gt;
&lt;li&gt;Actually SSH to the VM!&lt;&#x2F;li&gt;
&lt;li&gt;Run epithet as a daemon&lt;&#x2F;li&gt;
&lt;li&gt;Cleaning up config and playing around
&lt;br&#x2F;&gt;
&lt;br&#x2F;&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;&#x2F;h2&gt;
&lt;p&gt;Have &lt;code&gt;tofu&lt;&#x2F;code&gt; and &lt;code&gt;awscli&lt;&#x2F;code&gt; installed. Make sure you have configured &lt;code&gt;awscli&lt;&#x2F;code&gt;. You will need to run this on a machine capable of spawning a web browser you interract with, so if you are doing it on a VM or such, do it under X (or Wayland, VNC, etc). The article assumes you are on a Mac, because I am, but it should adapt alright if you are on a flavor of linux or whatnot. I have not tried any of this on Windows yet. If it works, let me know!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;install-epithet-on-your-local-machine&quot;&gt;Install epithet on your local machine&lt;&#x2F;h2&gt;
&lt;p&gt;As mentioned, I&#x27;m working on a Mac, and use Homebrew, so this article is kind of oriented around that:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew install epithet-ssh&#x2F;tap&#x2F;epithet&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you are not on a mac, or not using homebrew, you will need to build and install it yourself. Clone &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;epithet-ssh&#x2F;epithet&#x2F;&quot;&gt;epithet&lt;&#x2F;a&gt; and check out &lt;code&gt;v0.1.7&lt;&#x2F;code&gt; which is the version this article is written against. It&#x27;s implemented in Go, so you&#x27;ll need that installed. You can build it with &lt;code&gt;make&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;git clone https:&#x2F;&#x2F;github.com&#x2F;epithet-ssh&#x2F;epithet&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cd epithet&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;git checkout v0.1.7&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;make epithet # or just `make` if you want to run the tests&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you built it youself, put the binary somewhere on your &lt;code&gt;$PATH&lt;&#x2F;code&gt;. If you don&#x27;t have it on your &lt;code&gt;$PATH&lt;&#x2F;code&gt;, later stuff we do will be trickier, and changing the examples to use the full path is an exercise left to you.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;configure-an-sso-provider&quot;&gt;Configure an SSO provider&lt;&#x2F;h2&gt;
&lt;p&gt;We&#x27;ll use Google for SSO, but you could as easily use Microsoft, Apple, AWS (Cognito), Facebook, Github, Gitlab, Okta, Yahoo!, Keycloak, Authentik, and so on. OIDC&#x2F;OAuth2 is pretty widespread nowadays, and you don&#x27;t need a full IDP (ie, a full SCIM provider) for Epithet, just someone to provide identity at the other end of OIDC. I&#x27;m using Google because GMail is widely used, so it is useful for many people.&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;console.cloud.google.com&#x2F;&quot;&gt;Google Cloud Console&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Select or create a project&lt;&#x2F;li&gt;
&lt;li&gt;Navigate to APIs &amp;amp; Services → Credentials&lt;&#x2F;li&gt;
&lt;li&gt;Configure a consent screen&lt;&#x2F;li&gt;
&lt;li&gt;Add a name for your App, I used &quot;Epithet Demo&quot;&lt;&#x2F;li&gt;
&lt;li&gt;For Audience, if you are using Google Workspace you can make it Internal and be good to go. If using general GMail, then make it External. I am making this demo External.&lt;&#x2F;li&gt;
&lt;li&gt;This dropped me on a screen with a &quot;Create OAuth Client&quot; -- which I picked. If you are getting there differently it is fine, you need to create an OAuth Client&#x2F;Credential.&lt;&#x2F;li&gt;
&lt;li&gt;Choose Application type: I use Universal Windows Platform (UWP). Despite the name, it is not limited to Windows apps, and it does PKCE. &quot;Desktop App&quot; does not support PKCE, so you need to embed the OAuth secret in the agent config. The internet tells me this is fine, and secure, and the secret is not really a secret. I want to just use PKCE, so I use UWP here.&lt;&#x2F;li&gt;
&lt;li&gt;Enter a name: I used &quot;Epithet Agent&quot;&lt;&#x2F;li&gt;
&lt;li&gt;If you are using UWP you will need to include a Store ID, I don&#x27;t believe it is used anywhere. I used &quot;epithet-demo&quot;&lt;&#x2F;li&gt;
&lt;li&gt;Click Create&lt;&#x2F;li&gt;
&lt;li&gt;Make note of the Client ID.&lt;&#x2F;li&gt;
&lt;li&gt;Select the &quot;Audience&quot; tab on the left.&lt;&#x2F;li&gt;
&lt;li&gt;In the &quot;Test Users&quot; section add a couple users (email addresses) who you want to be able to use the app. I added a couple google accounts I have, a general public one which I am using to set up this demo, and a Workspace email that I actually use for things. I set up two so that I can configure different policies for them later. If you created an &quot;Internal&quot; app type earlier, you can skip this step -- your Workspace users should be able to use the app automagically.
&lt;br &#x2F;&gt;
&lt;br &#x2F;&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;You can test the configuration using the &lt;code&gt;epithet auth oidc&lt;&#x2F;code&gt; command:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;echo &amp;quot;&amp;quot; | epithet -vvv auth oidc --issuer=https:&#x2F;&#x2F;accounts.google.com --client-id=&amp;lt;OAUTH_CLIENT_ID&amp;gt; 3&amp;gt;&#x2F;dev&#x2F;null&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note, use &lt;em&gt;your&lt;&#x2F;em&gt; client id, not the &lt;code&gt;&amp;lt;OAUTH_CLIENT_ID&amp;gt;&lt;&#x2F;code&gt; placeholder. Also note the &lt;code&gt;echo &quot;&quot; |&lt;&#x2F;code&gt; at the beginning and &lt;code&gt;3&amp;gt;&#x2F;dev&#x2F;null&lt;&#x2F;code&gt; at the end: these are for epithet&#x27;s auth plugin protocol, which expects input on stdin and to write state to FD 3. This should pop up the authentication flow and if you log in will let you know it was successful and show you the auth token.&lt;&#x2F;p&gt;
&lt;p&gt;In my case, it was successful, so I am moving on!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;set-up-ca-and-policy-services&quot;&gt;Set up CA and Policy services&lt;&#x2F;h2&gt;
&lt;p&gt;These are generally low volume things which are more or less perfect use cases for FaaS platforms. Because AWS is the most popular I made Lambda wrappers for epithet&#x27;s built in basic CA and Policy services. Start by &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;epithet-ssh&#x2F;epithet-aws&quot;&gt;cloning the repo&lt;&#x2F;a&gt; for them, which contains the terraform (tofu) configs to actually set them up (as well as the wrapper code if you want to play with it). I set it up as a template repo, so you can use the &quot;Use this template&quot; to clone it in Github if you like. Make sure to set the privacy to &quot;Private&quot; if you do -- it &lt;em&gt;can&lt;&#x2F;em&gt; be public, but Github will complain about you checking in your OAuth Client ID if you do.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;git clone https:&#x2F;&#x2F;github.com&#x2F;epithet-ssh&#x2F;epithet-aws epithet-demo&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cd epithet-demo&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;configuring-the-policy-server&quot;&gt;Configuring the Policy Server&lt;&#x2F;h3&gt;
&lt;p&gt;Now, let&#x27;s do some basic configuration for the deployment. We do this with some terraform variables, created and edit &lt;code&gt;terraform&#x2F;terraform.tfvars&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;aws_region     = &amp;quot;us-east-1&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;project_name   = &amp;quot;epithet-demo&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I am using &lt;code&gt;us-east-1&lt;&#x2F;code&gt; for the demo (unofficial designated demo region) and picked a name (&lt;code&gt;epithet-demo&lt;&#x2F;code&gt;).&lt;&#x2F;p&gt;
&lt;p&gt;Now we need to make a basic policy for the Policy service. It should be in &lt;code&gt;config&#x2F;policy.yaml&lt;&#x2F;code&gt;. I&#x27;m going to use:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# CA public key is loaded from SSM Parameter Store at runtime&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# This placeholder satisfies config validation&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;ca_public_key&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;placeholder - loaded from SSM&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;oidc&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;  issuer&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;https:&#x2F;&#x2F;accounts.google.com&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;  audience&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;&amp;lt;OAUTH_CLIENT_ID&amp;gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;users&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;  &amp;quot;brianm@skife.org&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;&amp;quot;wheel&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;  &amp;quot;brian.mccallister@gmail.com&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;&amp;quot;dev&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;defaults&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;  allow&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    arch&lt;&#x2F;span&gt;&lt;span&gt;: [&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;&amp;quot;dev&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;wheel&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;  expiration&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;5m&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;#hosts:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;#  &amp;quot;prod-*.example.com&amp;quot;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;#    allow:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;#      deploy: [&amp;quot;dev&amp;quot;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;#    expiration: &amp;quot;2m&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Let&#x27;s talk about what it does and how it works.&lt;&#x2F;p&gt;
&lt;p&gt;The Policy server needs the CA&#x27;s public key in &lt;code&gt;ca_public_key&lt;&#x2F;code&gt; as it validates requests that come with it (the CA signs them using the CA private key). For the Lambda wrapper we load the public key from AWS&#x27;s config service (SSM) so the actual configuration value is ignored.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;oidc&lt;&#x2F;code&gt; section needs to know the provider URL (&lt;code&gt;issuer&lt;&#x2F;code&gt;) and the client id (called &lt;code&gt;audience&lt;&#x2F;code&gt; here, for OIDC reasons). Put your client id for the &lt;code&gt;audience&lt;&#x2F;code&gt; attribute.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;users&lt;&#x2F;code&gt; section configures allowed users. The built-in Policy
server uses static configurations, so it needs to know which users are
allowed to use it, up front. Additionally, it uses a group-style
matching system and assumes that &quot;remote-user == principal&quot;. For demo
purposes I use two groups, &lt;code&gt;wheel&lt;&#x2F;code&gt; and &lt;code&gt;dev&lt;&#x2F;code&gt;, but that choice is
arbitrary. It also has &lt;em&gt;no&lt;&#x2F;em&gt; relationship to actual unix groups on the
hosts -- it is purely used internally for matching users to principals
in the policy server. I configured two users so I can mess around later and experiment.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;defaults&lt;&#x2F;code&gt; section establishes defaults for host configuration. In this case it gives the &lt;code&gt;arch&lt;&#x2F;code&gt; principal to anyone in the &lt;code&gt;dev&lt;&#x2F;code&gt; or &lt;code&gt;wheel&lt;&#x2F;code&gt; group, unless overridden for a specific host. We also set up a 5 minute expiration for certificates.&lt;&#x2F;p&gt;
&lt;p&gt;Finally, the &lt;code&gt;hosts&lt;&#x2F;code&gt; section lets you do per-host configration. It is a map of host-glob -&amp;gt; config. I have it commented out for now, but am leaing it as a reminded for playing around later.&lt;&#x2F;p&gt;
&lt;p&gt;This overall config means that, assuming one of the users authenticates successfully (which it checks), it will issue a cert for any remote host and remote user with the &lt;code&gt;arch&lt;&#x2F;code&gt; principal. I used that principal because I am going to use Arch Linux cloud images to test things, and those provision the &lt;code&gt;arch&lt;&#x2F;code&gt; user by default.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;deploy-the-services&quot;&gt;Deploy the services&lt;&#x2F;h3&gt;
&lt;p&gt;I am &lt;em&gt;not&lt;&#x2F;em&gt; going to step through setting up a new AWS acount for you, so I&#x27;ll prefix this next section by saying -- have an AWS account, and have the aws-cli configured to use it!&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;make init&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;make apply&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Watch tofu set up a pile of stuff in AWS:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;API Gateway for CA&lt;&#x2F;li&gt;
&lt;li&gt;API Gateway for Policy&lt;&#x2F;li&gt;
&lt;li&gt;IAM policies&lt;&#x2F;li&gt;
&lt;li&gt;Secret to hold the private key&lt;&#x2F;li&gt;
&lt;li&gt;SSM to hold the public key&lt;&#x2F;li&gt;
&lt;li&gt;S3 bucket to keep certificate log in&lt;&#x2F;li&gt;
&lt;li&gt;Other stuff, look in &lt;code&gt;terraform&#x2F;&lt;&#x2F;code&gt; of the repo you cloned for everything. I believe there are 32 things total it sets up.
&lt;br&#x2F;&gt;
&lt;br&#x2F;&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;When it is finished it will spit out a bunch of information for you:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ca_public_key = &amp;lt;sensitive&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ca_public_key_command = &amp;quot;aws ssm get-parameter --name &#x2F;epithet-demo-9230323c&#x2F;ca-public-key --query Parameter.Value --output text&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ca_public_key_parameter = &amp;quot;&#x2F;epithet-demo-9230323c&#x2F;ca-public-key&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ca_secret_arn = &amp;quot;arn:aws:secretsmanager:us-east-1:378899212612:secret:epithet-demo-9230323c-ca-key-encO9c&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ca_secret_name = &amp;quot;epithet-demo-9230323c-ca-key&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ca_url = &amp;quot;https:&#x2F;&#x2F;ir6kilkap3.execute-api.us-east-1.amazonaws.com&#x2F;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cert_archive_bucket = &amp;quot;epithet-demo-9230323c-cert-archive&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cert_archive_bucket_arn = &amp;quot;arn:aws:s3:::epithet-demo-9230323c-cert-archive&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;policy_url = &amp;quot;https:&#x2F;&#x2F;e2np6k2uj5.execute-api.us-east-1.amazonaws.com&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;region = &amp;quot;us-east-1&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Save this info somewhere, you will need the CA URL later, and you may want to poke around the S3 bucket where info about issued certs is stored, or look at the various logs.&lt;&#x2F;p&gt;
&lt;p&gt;But we are not done, we need to actually make the CA key and upload it:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;make setup-ca-key&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We have to create the key pair after we provision everything else because we need the Secret and SSM to be created in AWS before we can put the private and public keys in them respectively.&lt;&#x2F;p&gt;
&lt;p&gt;You can test if the CA is running by issuing a GET to it, it should return the public key:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;epithet-demo took 3s [!?] on  main ❯ xh GET https:&#x2F;&#x2F;ir6kilkap3.execute-api.us-east-1.amazonaws.com&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;HTTP&#x2F;2.0 200 OK&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;apigw-requestid: U35tYhMyoAMEcRw=&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;content-length: 81&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;content-type: text&#x2F;plain&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;date: Sun, 30 Nov 2025 20:03:46 GMT&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIiLCOTQVHFqu7zP5j3KCjrfYavXqk7wasuckA6QvQgP&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;epithet-demo [!?] on  main ❯&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I should probably put the same kind of smoke test on the policy server, now that I think about it.&lt;&#x2F;p&gt;
&lt;p&gt;Despite having made a pile of AWS resources, most of them are cheap and idle, so for a couple of users authenticating a few times a day, it should cost under a dollar a month. Sometimes the cloud is kind of cool.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;configure-the-epithet-agent&quot;&gt;Configure the epithet agent&lt;&#x2F;h2&gt;
&lt;p&gt;Okay, we have the system up, let&#x27;s configure our agent to use it! Epithet uses &lt;code&gt;~&#x2F;.epithet&#x2F;&lt;&#x2F;code&gt; for its various configs and running stat. Yes, this is not XDG style. I am open to XDG, but this seems simpler and is not totally unexpected.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cd ~&#x2F;.epithet&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$EDITOR .&#x2F;config&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add content like:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ca-url      &amp;lt;Your CA URL&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;match       *&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;auth        epithet auth oidc --issuer https:&#x2F;&#x2F;accounts.google.com --client-id &amp;lt;YOUR OAUTH CLIENT ID&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This config is fine for testing, for now, but the &lt;code&gt;match *&lt;&#x2F;code&gt; line is not something we&#x27;ll want to leave in long term -- it is telling epithet that it should be used for &lt;em&gt;every&lt;&#x2F;em&gt; attempted connection, which we probably don&#x27;t want. Once we have a VM or three to try against, we&#x27;ll constrain it down.&lt;&#x2F;p&gt;
&lt;p&gt;We also need to tell SSH to use epithet, to do this we drop a line in &lt;code&gt;~&#x2F;.ssh&#x2F;config&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Include ~&#x2F;.epithet&#x2F;run&#x2F;*&#x2F;ssh-config.conf&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Do this somewhere near the top, and definitely before anywhere you might set up an ssh agent socket.&lt;&#x2F;p&gt;
&lt;p&gt;Now, to debug we will start the agent manually -- run:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;epithet -vvv agent&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You should get some debug log output.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;create-a-cloud-init-config-for-new-vms-to-rely-on-the-ca&quot;&gt;Create a cloud-init config for new VMs to rely on the CA&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;cloud-init&quot;&gt;cloud-init&lt;&#x2F;h3&gt;
&lt;p&gt;So the easiest way I have found for basic setup of a new vm is &lt;code&gt;cloud-init&lt;&#x2F;code&gt;, every major distribution and provider supports it, including my favorite, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;freebsd&#x2F;vm-bhyve&quot;&gt;vm-bhyve&lt;&#x2F;a&gt; (which is not really a major provider, but what I use to manage VMs on my machines).&lt;&#x2F;p&gt;
&lt;p&gt;We&#x27;ll make a user-data config for &lt;code&gt;cloud-init&lt;&#x2F;code&gt; which configures sshd to respect our CA key. Make a file named &lt;code&gt;user-data&lt;&#x2F;code&gt; in your current directory which contains:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;#cloud-config&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;resize_rootfs&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; True&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;manage_etc_hosts&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; localhost&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;write_files&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt; path&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &#x2F;etc&#x2F;ssh&#x2F;epithet_ca.pub&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    content&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;      ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIiLCOTQVHFqu7zP5j3KCjrfYavXqk7wasuckA6QvQgP&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    owner&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; root:root&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    permissions&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;#39;0644&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt; path&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &#x2F;etc&#x2F;ssh&#x2F;sshd_config.d&#x2F;100-epithet.conf&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    content&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; |&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;      TrustedUserCAKeys &#x2F;etc&#x2F;ssh&#x2F;epithet_ca.pub&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    owner&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; root:root&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    permissions&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;#39;0644&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;But instead of using the public key for my CA (&lt;code&gt;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIiLCOTQVHFqu7zP5j3KCjrfYavXqk7wasuckA6QvQgP&lt;&#x2F;code&gt;) use the one for &lt;em&gt;your&lt;&#x2F;em&gt; CA.&lt;&#x2F;p&gt;
&lt;p&gt;This creates two files in &lt;code&gt;&#x2F;etc&#x2F;ssh&#x2F;&lt;&#x2F;code&gt; -- one with the public key, and one which tells sshd to trust that public key.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;a-vm&quot;&gt;A VM&lt;&#x2F;h3&gt;
&lt;p&gt;Okay, so we have a CA, an agent, and some cloud-init config, but we still need a server to use it on. Since we have used AWS so far, let&#x27;s keep using it. Sadly, EC2 is pretty painful for basic VM setup nowadays. That said, we&#x27;ll do it.&lt;&#x2F;p&gt;
&lt;p&gt;For this, I am going to set up in a region I never use, so that I have to do things from scratch. Clean slate makes for good demo. If you have a VPC you like, which allows SSH ingress, then you can skip the setup for the most part and just spin up your instance there. If not, hang on, we have some AWS to do.&lt;&#x2F;p&gt;
&lt;p&gt;I am going to us &lt;code&gt;us-east-2&lt;&#x2F;code&gt; because I have never used it before. If you pick a different region you will need to find the Arch AMI for that region. You can find them at &lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;arch-ami-list.drzee.net&quot;&gt;arch-ami-list.drzee.net&lt;&#x2F;a&gt;. For &lt;code&gt;us-east-2&lt;&#x2F;code&gt; the AMI is &lt;code&gt;ami-0c87f4f769e675bf8&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;If you are in bash or a regular sh:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Create a default VPC (if one doesn&amp;#39;t exist)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;aws ec2 create-default-vpc --region us-east-2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Create security group for SSH&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;sg_id=$(aws ec2 create-security-group \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --region us-east-2 \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --group-name ssh-access \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --description &amp;quot;SSH access&amp;quot; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --query &amp;#39;GroupId&amp;#39; --output text)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Allow inbound SSH&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;aws ec2 authorize-security-group-ingress \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --region us-east-2 \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --group-id $sg_id \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --protocol tcp \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --port 22 \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --cidr 0.0.0.0&#x2F;0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Launch the instance&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;instance_id=$(aws ec2 run-instances \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --region us-east-2 \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --image-id ami-0c87f4f769e675bf8 \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --instance-type t3.micro \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --security-group-ids $sg_id \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --user-data file:&#x2F;&#x2F;.&#x2F;user-data \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --query &amp;#39;Instances[0].InstanceId&amp;#39; --output text)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Wait for it to be running&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;aws ec2 wait instance-running --region us-east-2 --instance-ids $instance_id&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Get the public IP&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ip=$(aws ec2 describe-instances \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --region us-east-2 \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --instance-ids $instance_id \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --query &amp;#39;Reservations[0].Instances[0].PublicIpAddress&amp;#39; --output text)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;echo $ip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you are in fish, like me:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Create a default VPC (if one doesn&amp;#39;t exist)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;aws ec2 create-default-vpc --region us-east-2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Create security group for SSH&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;set sg_id (aws ec2 create-security-group \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --region us-east-2 \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --group-name ssh-access \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --description &amp;quot;SSH access&amp;quot; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --query &amp;#39;GroupId&amp;#39; --output text)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Allow inbound SSH&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;aws ec2 authorize-security-group-ingress \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --region us-east-2 \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --group-id $sg_id \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --protocol tcp \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --port 22 \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --cidr 0.0.0.0&#x2F;0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Launch the instance&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;set instance_id (aws ec2 run-instances \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --region us-east-2 \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --image-id ami-0c87f4f769e675bf8 \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --instance-type t3.micro \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --security-group-ids $sg_id \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --user-data file:&#x2F;&#x2F;.&#x2F;user-data \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --query &amp;#39;Instances[0].InstanceId&amp;#39; --output text)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Wait for it to be running&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;aws ec2 wait instance-running --region us-east-2 --instance-ids $instance_id&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;# Get the public IP&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;set ip (aws ec2 describe-instances \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --region us-east-2 \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --instance-ids $instance_id \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  --query &amp;#39;Reservations[0].Instances[0].PublicIpAddress&amp;#39; --output text)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;echo $ip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note that when we made this VM we did &lt;em&gt;not&lt;&#x2F;em&gt; give it a public key. The &lt;em&gt;only&lt;&#x2F;em&gt; way to access it is via a certificate it trusts!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;actually-ssh-to-the-vm&quot;&gt;Actually SSH to the VM!&lt;&#x2F;h2&gt;
&lt;p&gt;Given the above, we can ssh in:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ssh arch@$ip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It should pop a browser which asks you to log in, which you should do. When everything authenticates you should be back in ssh, and be able to connect:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;epithet-demo [!?] on  main ❯ ssh arch@$ip&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;       .&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      &#x2F; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;     &#x2F;   \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &#x2F;^.   \     Arch Linux AMI (std)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;   &#x2F;  .-.  \    https:&#x2F;&#x2F;archlinux.org&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &#x2F;  (   ) _\&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt; &#x2F; _.~   ~._^\&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&#x2F;.^         ^.\ TM&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;[arch@ip-172-31-46-139 ~]$&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Yea!&lt;&#x2F;p&gt;
&lt;p&gt;If you look over at the agent there will be a bunch of debug output showing what it did.&lt;&#x2F;p&gt;
&lt;p&gt;If you log out (or open a new terminal) you can inspect the state of the epithet agent:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;epithet inspect&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Which will show you the ssh agent sockets it has running and the certs it has:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Broker State&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;============&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Socket: &#x2F;Users&#x2F;brianm&#x2F;.epithet&#x2F;run&#x2F;d6d98793bf4b428a&#x2F;broker.sock&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Agent Dir: &#x2F;Users&#x2F;brianm&#x2F;.epithet&#x2F;run&#x2F;d6d98793bf4b428a&#x2F;agent&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Match Patterns: [*]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Agents (1)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-----------&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  8fea92165cf18c9783b9b4e9b30ac50c559dd352&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Socket: &#x2F;Users&#x2F;brianm&#x2F;.epithet&#x2F;run&#x2F;d6d98793bf4b428a&#x2F;agent&#x2F;8fea92165cf18c9783b9b4e9b30ac50c559dd352&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Expires: 2025-11-30T13:45:52-08:00 (valid, 2m17s)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Certificate: SHA256:cadNJy5p6U7h&#x2F;tfExl5M5GYZ&#x2F;kJMpFoPEwf7dvFmGss&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Certificates (1)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-----------------&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  [0]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Fingerprint: SHA256:cadNJy5p6U7h&#x2F;tfExl5M5GYZ&#x2F;kJMpFoPEwf7dvFmGss&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Identity: brianm@skife.org&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Principals: [arch]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Valid: 2025-11-30T13:39:52-08:00 to 2025-11-30T13:45:52-08:00 (valid, 2m17s)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Extensions:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      permit-agent-forwarding&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      permit-pty&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      permit-user-rc&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    Policy (HostUsers):&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      *: [arch]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The cert will expire in a few minutes, but the auth state should last as long as google doesn&#x27;t want you to reauthenticate and you don&#x27;t stop the agent. The auth state, certs, etc are all kept only in memory so killing the agent will wipe them out. No big deal, start the agent again and you can reauthenticate!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;run-epithet-as-a-daemon&quot;&gt;Run epithet as a daemon&lt;&#x2F;h2&gt;
&lt;p&gt;If you installed via homebrew, it also added a homebrew service. You can enable the homebrew service via:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;brew services start epithet&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You can then stop it with &lt;code&gt;stop&lt;&#x2F;code&gt; instead of start, and so on. You need to restart it when you change the config, epithet agent does not pick up on config changes automagically.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;cleaning-up-config-and-playing-around&quot;&gt;Cleaning up config and playing around&lt;&#x2F;h2&gt;
&lt;p&gt;Okay, so now you have a system, go play. The first thing I suggest doing is changing the match statement in &lt;code&gt;~&#x2F;.epithet&#x2F;config&lt;&#x2F;code&gt; to match against just your VM and restarting the agent.&lt;&#x2F;p&gt;
&lt;p&gt;My config now looks like:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ca-url      https:&#x2F;&#x2F;ir6kilkap3.execute-api.us-east-1.amazonaws.com&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;match       18.220.139.216&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;match       54.190.23.91&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;auth        epithet auth oidc --issuer https:&#x2F;&#x2F;accounts.google.com --client-id &amp;lt;abc123,etc&amp;gt;.apps.googleusercontent.com&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Notice I have two &lt;code&gt;match&lt;&#x2F;code&gt; lines, that is because I spun up a second VM. The match line is used to short circuit what epithet tries to get a certificate for. In my actual day-to-day config it looks like:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;ca-url      https:&#x2F;&#x2F;&amp;lt;REDACTED&amp;gt;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;match       *.home&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;match       *.barn&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;match       *.brianm.dev&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;auth        &#x2F;opt&#x2F;homebrew&#x2F;bin&#x2F;epithet auth oidc --issuer https:&#x2F;&#x2F;accounts.google.com --client-id &amp;lt;REDACTED&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Which relies on wildcard matching. Our fooling around VMs don&#x27;t have useful names to match on, so there we go. Alternately, you could have the policy server reject them, which will also cause matches to fail, but that is a different (and not yet written) article.&lt;&#x2F;p&gt;
&lt;p&gt;To change the policy server config you can edit the &lt;code&gt;config&#x2F;policy.yaml&lt;&#x2F;code&gt; file and redeploy it via &lt;code&gt;make apply&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;When you are done playing, if you want to tear down what we set up, you can run &lt;code&gt;make destroy&lt;&#x2F;code&gt; to tear down the resources tofu made. &lt;strong&gt;You&#x27;ll need to stop your VMs yourself&lt;&#x2F;strong&gt; (&lt;code&gt;aws --region us-east-2 ec2 terminate-instances --instance-ids $instance_id&lt;&#x2F;code&gt;)-- tofu didn&#x27;t make them for you, it doesn&#x27;t know about them.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;debugging&quot;&gt;Debugging&lt;&#x2F;h3&gt;
&lt;p&gt;I can &lt;em&gt;hope&lt;&#x2F;em&gt; everything went smoothly for you, but it may not have. If it didn&#x27;t go smoothly, I have found Claude Code to be &lt;em&gt;extremely&lt;&#x2F;em&gt; good at helping debug terraform&#x2F;aws stuff. In the repo with the the terraform resources there is a &lt;code&gt;CLAUDE.md&lt;&#x2F;code&gt; and MCP server specs (&lt;code&gt;.mcp.json&lt;&#x2F;code&gt;) for AWS and Terraform respectively. While I used Tofu, not Terraform, the MCP server knows both. If you don&#x27;t have &lt;code&gt;uv&lt;&#x2F;code&gt; installed, install it (&lt;code&gt;brew install uv&lt;&#x2F;code&gt;) and the MCP servers will work. Fire up claude and ask it to help you debug your issues.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Epithet, v2, Briefly - Part 1</title>
          <pubDate>Fri, 28 Nov 2025 00:00:00 +0000</pubDate>
          <author>Brian McCallister</author>
          <link>https://skife.org/b4/epithet-p1/</link>
          <guid>https://skife.org/b4/epithet-p1/</guid>
          <description>&lt;p&gt;I last talked about &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;epithet-ssh&#x2F;epithet&#x2F;&quot;&gt;epithet&lt;&#x2F;a&gt; two years ago, &lt;a href=&quot;&#x2F;b4&#x2F;epithet-briefly&#x2F;&quot;&gt;in 2023&lt;&#x2F;a&gt;, when I started kicking around ideas to make it general purpose. I&#x27;m pleased with how it&#x27;s turned out, so I want to start talking about some of the design decisions. I dare not &lt;em&gt;document&lt;&#x2F;em&gt; it yet, for fear someone may use it, but that&#x27;s probably coming.&lt;&#x2F;p&gt;
&lt;p&gt;Even though it never had a proper 1.0, I am calling this v2 in my head as it is not at all compatible with the previous iteration, and is so much better. The big pieces are the same shape: an Agent, a CA, and a Policy service:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;skife.org&#x2F;b4&#x2F;epithet-p1&#x2F;epithet-boxes-arrows.svg&quot; alt=&quot;Epithet Boxes and Arrows&quot; title=&quot;Epithet&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;agent&quot;&gt;Agent&lt;&#x2F;h2&gt;
&lt;p&gt;The agent handles authenticating you, obtaining certificates, and deciding which certificate to use for a given connection. It uses a small policy protocol to match certificates against connections based on target host and remote username. Authentication is handled by a plugin system, so you can have arbitrary authenticators. There&#x27;s a built-in OIDC authenticator, and I imagine I&#x27;ll add SAML at some point.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;policy-service&quot;&gt;Policy Service&lt;&#x2F;h2&gt;
&lt;p&gt;The Policy Service is the brains of the system. It receives authentication and connection information and decides (1) whether to issue a cert, (2) which principals to put on the cert, (3) the cert parameters (expiration, extensions, etc), and (4) a matching policy telling the agent which connections this cert can be used for.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ca&quot;&gt;CA&lt;&#x2F;h2&gt;
&lt;p&gt;The CA has access to the CA private key, so I want it to be as simple as possible. As such it delegates decision making about certificates to the policy service and merely issues certs as instructed.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;so-what-s-changed-and-why&quot;&gt;So what&#x27;s changed and why?&lt;&#x2F;h1&gt;
&lt;p&gt;That looks pretty similar to two years ago, but there are a few key changes I&#x27;ll dive into over a few posts. Today, the big reasons for the changes:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;I want to be able to issue a certificate which allows someone (or some process) to ssh into a specific host as a specific user only, rather than a more wide-open certificate with all of the user&#x27;s principals. Imagine, if you will, a piece of deployment automation. We want it to be able to ssh as &lt;code&gt;deploy@prod-*.example.com&lt;&#x2F;code&gt; for the next ten minutes. Allowing this should be triggered by a workflow -- either automatically by inspecting if it &lt;em&gt;should&lt;&#x2F;em&gt; be deploying right now, or via a human workflow such as a response in Slack, and so on.&lt;&#x2F;li&gt;
&lt;li&gt;I don&#x27;t want to bake in assumptions about how identities, principals, and hosts map to each other. The unix-group style set of principals from the previous version is reasonable for many use cases, but not all. It&#x27;s overkill for simple cases (my personal stuff) and not powerful enough for the hard cases (see above).&lt;&#x2F;li&gt;
&lt;li&gt;I want to coexist better with non-epithet ssh scenarios. Previously users had to carefully craft &lt;code&gt;Match&lt;&#x2F;code&gt; blocks in their ssh config to only use epithet when appropriate. Worse, if the CA or policy server was &lt;em&gt;down&lt;&#x2F;em&gt;, users would need to edit their ssh config to use a fallback. The &lt;em&gt;last&lt;&#x2F;em&gt; thing you want to do in a SEV is edit ssh config.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
</description>
      </item>
      <item>
          <title>Notes on setting up vm-bhyve</title>
          <pubDate>Sat, 04 May 2024 00:00:00 +0000</pubDate>
          <author>Brian McCallister</author>
          <link>https://skife.org/b4/setting-up-vm-bhyve/</link>
          <guid>https://skife.org/b4/setting-up-vm-bhyve/</guid>
          <description>&lt;p&gt;I&#x27;m replacing my general&#x2F;util server at home and want to manage VMs on the new host with &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;churchers&#x2F;vm-bhyve&quot;&gt;&lt;code&gt;vm-bhyve&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;. Setting it up is well documented, but running linux VMs still steers folks towards grub, which is not really great. These are just my notes (for later, after I forget) on using cloud images and uefi. This is a supplement to the docs, not a replacement!&lt;&#x2F;p&gt;
&lt;p&gt;I created a template, creatively named &lt;code&gt;brian-linux.conf&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;loader=&amp;quot;uefi&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;cpu=2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;memory=4G&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;network0_type=&amp;quot;virtio-net&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;network0_switch=&amp;quot;public&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;disk0_type=&amp;quot;nvme&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;disk0_dev=&amp;quot;sparse-zvol&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;disk0_name=&amp;quot;disk0&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I&#x27;m running on ZFS, so can use &lt;code&gt;sparse-zvol&lt;&#x2F;code&gt; for storage to make disk space only soft allocated, letting me overcommit.&lt;&#x2F;p&gt;
&lt;p&gt;Now, the commands to make things happy and run some cloud images:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# Additional dependencies for vm-bhyve&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;pkg&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; install cdrkit-genisoimage&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    # to use cloud-init&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;pkg&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; install qemu-tools&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;            # to use cloud images&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;pkg&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; install bhyve-firmware&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;        # to use uefi&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# Download some cloud images to use for our VMs&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;vm&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; img https:&#x2F;&#x2F;dl-cdn.alpinelinux.org&#x2F;alpine&#x2F;v3.19&#x2F;releases&#x2F;cloud&#x2F;nocloud_alpine-3.19.1-x86_64-uefi-cloudinit-r0.qcow2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;vm&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; img http:&#x2F;&#x2F;cloud-images.ubuntu.com&#x2F;noble&#x2F;current&#x2F;noble-server-cloudimg-amd64.img&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;vm&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; create&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;                               \ &lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;# C-k off comments if you copy this&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;    -t&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; brian-linux&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;                      \ &lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;# our template, from above&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;    -i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; noble-server-cloudimg-amd64.img&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;  \ &lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;# the cloud image&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;    -s&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; 50G&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;                              \ &lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;# disk size override&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;    -c&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 4                                \ &lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;# cpu count override&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;    -m&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; 32G&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;                              \ &lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;# memory override&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;    -C&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; -k&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; ~brianm&#x2F;.ssh&#x2F;id_rsa.pub&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;       \ &lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;# cloud-init ssh pubkey&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;    v0001&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;                                 # vm name&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Docs cover these, but reminding myself of how I configured it beyond defaults:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;vm&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; switch vlan public&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 4&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;     # use vlan 4 for VMs&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;vm&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; set console=tmux&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;         # tmux for console access&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Nice side effect of using &lt;code&gt;cloud-init&lt;&#x2F;code&gt; is that it logs the DHCP assigned IP address as well, so I can fire up the console and go search for it (&lt;code&gt;C-b C-[ C-s Address&lt;&#x2F;code&gt;)! Probably should write a script to extract it, to be honest.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Alfred Alias Hack</title>
          <pubDate>Thu, 08 Feb 2024 00:00:00 +0000</pubDate>
          <author>Brian McCallister</author>
          <link>https://skife.org/b4/alfred-alias-hack/</link>
          <guid>https://skife.org/b4/alfred-alias-hack/</guid>
          <description>&lt;p&gt;I live by &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.alfredapp.com&quot;&gt;Alfred&lt;&#x2F;a&gt; as a launcher and all around insta-utility thing. I also live by whatever terminal I am currently using. Switching from &lt;code&gt;Terminal.app&lt;&#x2F;code&gt; to &lt;code&gt;iTerm.app&lt;&#x2F;code&gt; was easy enough in Alfred, just need to hit the down arrow after trying to launch it 2-3 times and it picks up that &lt;code&gt;⌘-spc t e r&lt;&#x2F;code&gt; means &lt;code&gt;iTerm.app&lt;&#x2F;code&gt; not &lt;code&gt;Terminal.app&lt;&#x2F;code&gt; and we&#x27;re off to the races. This works as iTerm nicely has &lt;code&gt;t e r&lt;&#x2F;code&gt; in it, so Alfred picks it up as I type &lt;code&gt;t e r&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;Ghostty.app&lt;&#x2F;code&gt; does &lt;em&gt;not&lt;&#x2F;em&gt; include &lt;code&gt;t e r&lt;&#x2F;code&gt; so I have been launching iTerm, quitting iTerm, launching Ghostty, on repeat, all the time. So, the hack to convince Alfred that &lt;code&gt;⌘-spc t e r&lt;&#x2F;code&gt; means &quot;launch ghostty&quot; (as I have given up on reprogramming my fingers) is as follows:&lt;&#x2F;p&gt;
&lt;p&gt;Create an app alias (in the Finder, right click on Ghostty.app -&amp;gt; Make Alias) and name it &quot;Ter Ghostty.app&quot;. This makes a special &quot;MacOS Alias file&quot;, not a symlink. Fire up Alfred Preferences and tell it to also search &lt;code&gt;com.apple.alias-file&lt;&#x2F;code&gt; files. This is buried in &lt;code&gt;Default Results -&amp;gt; Advanced&lt;&#x2F;code&gt;. You can drag and drop the newly created alias file into the list to do it, or type carefully.&lt;&#x2F;p&gt;
&lt;p&gt;At that point, Alfred will see the alias and start showing it for default results, and with a few iterations will pick up on th efact that &lt;code&gt;⌘-spc t e r&lt;&#x2F;code&gt; means &lt;code&gt;Ter Ghostty.app&lt;&#x2F;code&gt; not &lt;code&gt;iTerm.app&lt;&#x2F;code&gt; or so forth:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;skife.org&#x2F;b4&#x2F;alfred-alias-hack&#x2F;ter-ghostty-2.png&quot; alt=&quot;Alfred Selector&quot; title=&quot;Ter Ghostty.app&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This is also useful for any other case where you want to change a default your fingers have memorized (Chrome -&amp;gt; Safari, Vim -&amp;gt; Emacs, Notes -&amp;gt; Obsidian, and maybe someday, Emacs -&amp;gt; Zed).&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Ghostty, emacsclient, and terminfo</title>
          <pubDate>Sun, 04 Feb 2024 00:00:00 +0000</pubDate>
          <author>Brian McCallister</author>
          <link>https://skife.org/b4/ghostty-emacsclient/</link>
          <guid>https://skife.org/b4/ghostty-emacsclient/</guid>
          <description>&lt;p&gt;I&#x27;ve been lucky enough to get into the &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;mitchellh.com&#x2F;ghostty&quot;&gt;Ghostty&lt;&#x2F;a&gt; beta, and am very happy with it o far. I ran into one real hiccup, and I &lt;em&gt;know&lt;&#x2F;em&gt; I&#x27;ll forget how I fixed it when I set up my next laptop, so leaving a note for myself (and anyone else who hits it).&lt;&#x2F;p&gt;
&lt;p&gt;Ghostty uses a custom terminfo, &lt;code&gt;xterm-ghostty&lt;&#x2F;code&gt;, and does &lt;em&gt;not&lt;&#x2F;em&gt; install it to the system or user, but specifies a &lt;code&gt;TERMINFO=&#x2F;Applications&#x2F;Ghostty.app&#x2F;Contents&#x2F;Resources&#x2F;terminfo&lt;&#x2F;code&gt; environment variable in the process for Ghostty itself. This &lt;em&gt;mostly&lt;&#x2F;em&gt; works great, but not in cases where something started outside Ghostty cares about the terminal capabilities -- such as an emacs daemon :-(&lt;&#x2F;p&gt;
&lt;p&gt;When I started, I was using an emacs daemon controlled by &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.brew.sh&#x2F;Manpage#services-subcommand&quot;&gt;homebrew services&lt;&#x2F;a&gt; as I more or less forget everything about launchd shortly after doing anything with it. Homebrew is &lt;em&gt;clever&lt;&#x2F;em&gt; and replaces the launchd plist every time you start the service, so you cannot edit, boo. I get it, keeps people from breaking things, but boo. So, Step 1, stop using homebrew services to interface to launchd to start my emacas daemon.&lt;&#x2F;p&gt;
&lt;p&gt;Step 2: add my own launchd plist thingie at &lt;code&gt;~&#x2F;Library&#x2F;LaunchAgents&#x2F;ghostty.emacs.plist&lt;&#x2F;code&gt;. It is derived from the one homebrew uses, which is derived form the venerable mxcl version:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;xml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;?&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;xml&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; version&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;&amp;quot;1.0&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; encoding&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;&amp;quot;UTF-8&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;?&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;!&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;DOCTYPE&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; plist&lt;&#x2F;span&gt;&lt;span&gt; PUBLIC &amp;quot;-&#x2F;&#x2F;Apple&#x2F;&#x2F;DTD PLIST 1.0&#x2F;&#x2F;EN&amp;quot; &amp;quot;http:&#x2F;&#x2F;www.apple.com&#x2F;DTDs&#x2F;PropertyList-1.0.dtd&amp;quot;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;plist&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; version&lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;&amp;quot;1.0&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;dict&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;EnvironmentVariables&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;dict&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;TERMINFO&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&#x2F;Applications&#x2F;Ghostty.app&#x2F;Contents&#x2F;Resources&#x2F;terminfo&#x2F;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;dict&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;KeepAlive&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;true&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;Label&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;ghostty.emacs&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;LimitLoadToSessionType&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;array&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;Aqua&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;Background&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;LoginWindow&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;StandardIO&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;System&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;array&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;ProgramArguments&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;array&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&#x2F;opt&#x2F;homebrew&#x2F;opt&#x2F;emacs&#x2F;bin&#x2F;emacs&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;--fg-daemon&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;string&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;array&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;RunAtLoad&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;key&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    &amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;true&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;dict&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;plist&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I changed the label and added the &lt;code&gt;EnvironmentVariables&lt;&#x2F;code&gt; section setting &lt;code&gt;TERMINFO&lt;&#x2F;code&gt;. I stopped the brew service for emacs, started this one, and voila, emacsclient works again: &lt;code&gt;launchctl load -w ~&#x2F;Library&#x2F;LaunchAgents&#x2F;ghostty.emacs.plist&lt;&#x2F;code&gt; :-)&lt;&#x2F;p&gt;
&lt;p&gt;This feels like a somewhat hacky fix, and I think the better way is probably to install the terminfo file for the local user, the way &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;sw.kovidgoyal.net&#x2F;kitty&#x2F;&quot;&gt;kitty&lt;&#x2F;a&gt; does -- but I want to grok why Mitchell is &lt;em&gt;not&lt;&#x2F;em&gt; doing that before I muck with that!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Epithet, Briefly</title>
          <pubDate>Thu, 30 Nov 2023 00:00:00 +0000</pubDate>
          <author>Brian McCallister</author>
          <link>https://skife.org/b4/epithet-briefly/</link>
          <guid>https://skife.org/b4/epithet-briefly/</guid>
          <description>&lt;p&gt;Since &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;hachyderm.io&#x2F;@bobmcwhirter&#x2F;111496958192961901#.&quot;&gt;Bob asked&lt;&#x2F;a&gt;, I&#x27;m cleaning up a system to make using short-lived ssh certificates easy, simple, and secure.&lt;&#x2F;p&gt;
&lt;p&gt;The gist is to use a custom ssh-agent which, in turn, uses some modern authn service (OIDC, Keycloak, Okta, Google Sign-In, etc) to authenticate you. It then passes that auth token up to a CA which relies on a policy service to verify the auth token and respond with cert params (time, principals, extensions, whatever is needed). The CA uses the policy provided attributes to generate a short lived cert, which it gives back to the agent. Voila, you can access things for a couple minutes.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;skife.org&#x2F;b4&#x2F;epithet-briefly&#x2F;epithet-boxes-arrows.svg&quot; alt=&quot;Epithet Boxes and Arrows&quot; title=&quot;Epithet&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;There is a real sequence diagram of what is going on &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;epithet-ssh&#x2F;epithet&#x2F;blob&#x2F;master&#x2F;README.md&quot;&gt;in the repo&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ca&quot;&gt;CA&lt;&#x2F;h2&gt;
&lt;p&gt;The CA is dirt simple and is the only thing that will ever see the CA private key. The same CA service should be usable by more or less anyone, maybe with some variety in terms of where the private key is stored, but that is it.&lt;&#x2F;p&gt;
&lt;p&gt;The CA receives an opaque auth token + pubkey from the agent and responds with a cert. The existing implementation uses a policy service to decide whether to issue the cert, and what attributes to give it.&lt;&#x2F;p&gt;
&lt;p&gt;The actual CA was kept as simple as possible, because it is a CA. You don&#x27;t want to muck with it much.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;policy&quot;&gt;Policy&lt;&#x2F;h2&gt;
&lt;p&gt;The policy service consumes the opaque auth tokens from the CA and reponds with basic info, assuming the auth is valid:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;json&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;    &amp;quot;identity&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;&amp;quot;brianm@skife.org&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;    &amp;quot;principals&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:[&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;&amp;quot;oncall_fluffy&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;oncall_wibble&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;wheel&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;],&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;    &amp;quot;expiration&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;&amp;quot;2m&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;    &amp;quot;extensions&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;: {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;        &amp;quot;permit-pty&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;true&lt;&#x2F;span&gt;&lt;span&gt;,&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;        &amp;quot;permit-port-forwarding&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;true&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The policy server (single endpoint, takes a post with the context for the request) is nice as it can be dynamic and make use of risk models and various other signals to decide what to do: eg, &quot;is it a deploy server asking for a key? Is there actually a deploy in progress matching the identity requesting the cert? Okay, give it a 5 minute cert&quot; on the sophisticated side; simply checking an OIDC token for validity on the simpler side.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;agent&quot;&gt;Agent&lt;&#x2F;h2&gt;
&lt;p&gt;The ssh agent generates a new keypair on startup and never lets the private key leave memory. It has a small GRPC API over a domain socket for interfacing with authentication mechanisms, such as a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;epithet-ssh&#x2F;epithet&#x2F;blob&#x2F;master&#x2F;cmd&#x2F;epithet-auth&#x2F;epithet-auth.go&quot;&gt;helper to simply feed it tokens over stdin&lt;&#x2F;a&gt; for simple cases. We popped a browser (or used a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;brianm&#x2F;oktadance&quot;&gt;cli tool&lt;&#x2F;a&gt;) to do the Okta dance.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;micbase&quot;&gt;Michael&lt;&#x2F;a&gt; and I made this at a previous company. We spun up the CA in one lambda function, the policy server in another, and authed against Okta. I want it for my own stuff now and it doesn&#x27;t look like anyone there is maintaining it anymore, so I have been bringing it back to life.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>SSH Certificate Notes</title>
          <pubDate>Wed, 29 Nov 2023 00:00:00 +0000</pubDate>
          <author>Brian McCallister</author>
          <link>https://skife.org/b4/ssh-certificates-notes/</link>
          <guid>https://skife.org/b4/ssh-certificates-notes/</guid>
          <description>&lt;p&gt;There are lots of tutorials out there, but I want to compile my notes so if I walk away from playing with them again for a few years, I can pick it up again!&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ssh-keys-for-different-things-are-all-the-same-modulo-chosen-algo-type&quot;&gt;SSH Keys for different things are all the same (modulo chosen algo type)&lt;&#x2F;h2&gt;
&lt;p&gt;User keys, host keys, CA keys -- they are all the same. Don&#x27;t let &lt;code&gt;ssh-keygen&lt;&#x2F;code&gt; docs, with all of its options and whatnot, convince you otherwise.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;use-authorizedprincipalsfile-to-manage-access&quot;&gt;Use &lt;code&gt;AuthorizedPrincipalsFile&lt;&#x2F;code&gt; to manage access&lt;&#x2F;h2&gt;
&lt;p&gt;Don&#x27;t go and create a local user account for everyone who may be accessing the host, just have a shared local user, probably even &lt;code&gt;root&lt;&#x2F;code&gt;, where you differentiate who did what using the identity on the cert they authed with.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;AuthorizedPrincipalsFile&lt;&#x2F;code&gt; is a file that lists the allowed principals for a given local user. Use &lt;em&gt;groups&lt;&#x2F;em&gt; as principals, not individual users, and list the groups which may use the local user in the file for the given local user. The groups an actual user is in are then added to that user&#x27;s cert.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;AuthorizedPrincipalsFile &#x2F;etc&#x2F;ssh&#x2F;auth_principals&#x2F;%u&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;%u&lt;&#x2F;code&gt; is the normal user placeholder, so &lt;code&gt;&#x2F;etc&#x2F;ssh&#x2F;auth_principals&#x2F;root&lt;&#x2F;code&gt; would control the principals that may log in as &lt;code&gt;root&lt;&#x2F;code&gt;. The file takes one principal per line. Copilot &lt;em&gt;really&lt;&#x2F;em&gt; wants me to add that you can use wildcards in the file, but that idea is making me queasy. Principals as groups sounds better to me.&lt;&#x2F;p&gt;
&lt;p&gt;The overall &lt;code&gt;sshd_config&lt;&#x2F;code&gt; looks something like:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;plain&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Port {{ port }}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;Protocol 2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;AcceptEnv LANG LC_*&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;LoginGraceTime 120&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;UsePAM no &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;PasswordAuthentication no&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;IgnoreRhosts yes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;PubkeyAuthentication yes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;HostKey {{ path }}&#x2F;ssh_host_ed25519_key&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;TrustedUserCAKeys {{ path }}&#x2F;ca.pub&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;AuthorizedPrincipalsFile {{ path }}&#x2F;auth_principals&#x2F;%u&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h2 id=&quot;testing-with-sshd&quot;&gt;Testing with sshd&lt;&#x2F;h2&gt;
&lt;p&gt;You can run sshd as a normal user, as long as you only try to log in as that same user.&lt;&#x2F;p&gt;
&lt;p&gt;Invocation looks like, &lt;code&gt;&#x2F;opt&#x2F;homebrew&#x2F;sbin&#x2F;sshd -d -D -f &#x2F;tmp&#x2F;my_ssh&#x2F;sshd_config&lt;&#x2F;code&gt;. This will start it without forking behavior, process a single login, and give us debug output.&lt;&#x2F;p&gt;
&lt;p&gt;The sshd_config may need &lt;code&gt;StrictModes no&lt;&#x2F;code&gt; depending on the directories in play and the permissions on them.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;various-invocations&quot;&gt;Various invocations&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;ssh-with-weird-agent-socket&quot;&gt;ssh with weird agent socket&lt;&#x2F;h3&gt;
&lt;p&gt;Given I am doing this to test agent + cert muckery, invoking ssh to talk to the sshd mentioned above looks like:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;ssh&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; -v \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;    -o&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; UserKnownHostsFile=&#x2F;dev&#x2F;null&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;    -o&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; StrictHostKeyChecking=no&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;    -F&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &#x2F;dev&#x2F;null&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;    -l&lt;&#x2F;span&gt;&lt;span&gt; $USER&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;    -o&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; IdentityAgent=&#x2F;path&#x2F;to&#x2F;agent.sock&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;    -p 2222 \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;    localhost&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Same, but for a local cert instead of agent (assuming &lt;code&gt;user_key&lt;&#x2F;code&gt; is the private key, and has &lt;code&gt;user_key.pub&lt;&#x2F;code&gt; and &lt;code&gt;user_key-cert.pub&lt;&#x2F;code&gt; alongside it):&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;ssh&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; -v \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;    -o&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; UserKnownHostsFile=&#x2F;dev&#x2F;null&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;    -o&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; StrictHostKeyChecking=no&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;    -F&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &#x2F;dev&#x2F;null&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;    -l&lt;&#x2F;span&gt;&lt;span&gt; $USER&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;    -i&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; path&#x2F;to&#x2F;user_key&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;    -p 2222 \&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;    localhost&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;&lt;h3 id=&quot;given-a-ca-key-generate-a-user-cert-locally&quot;&gt;Given a CA key, generate a user cert locally&lt;&#x2F;h3&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;shellscript&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;ssh-keygen&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; -s&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt;  path&#x2F;to&#x2F;ca&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; -I&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; wobble&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; -n&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; {{ principals }} .path&#x2F;to&#x2F;user_key&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Add a comma seperated (no spaces) list of the principals (groups) for &lt;code&gt;{{ principals }}&lt;&#x2F;code&gt; in that invocation (needs to align with the auth principals on sshd). The &lt;code&gt;-I&lt;&#x2F;code&gt; is basically irrevalent for testing purposes, is just a cert identifier.&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Using More FreeBSD</title>
          <pubDate>Fri, 17 Nov 2023 00:00:00 +0000</pubDate>
          <author>Brian McCallister</author>
          <link>https://skife.org/b4/more-freebsd/</link>
          <guid>https://skife.org/b4/more-freebsd/</guid>
          <description>&lt;p&gt;I do some volunteer sysadmining for a local non-profit, mostly network management and related things. Between there, and my home setup, I have gradually moved from defaulting to Ubuntu back towards FreeBSD over the last year. Nothing wrong with Ubuntu, but I kept finding FreeBSD to simply be a little bit easier and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.freebsd.org&#x2F;en&#x2F;books&#x2F;handbook&#x2F;&quot;&gt;a lot better documented&lt;&#x2F;a&gt;. For a dev box, I&#x27;ll stick with linux—all the tooling optimizes there first, in particular the container ecosystem is too useful to walk away from. For sysadmin&#x27;y stuff though, FreeBSD is where it&#x27;s at. At a minimum, it&#x27;s &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.freebsd.org&#x2F;en&#x2F;books&#x2F;handbook&#x2F;config&#x2F;#configtuning-rcd&quot;&gt;rc system&lt;&#x2F;a&gt; is much easier to work with than systemd :-)&lt;&#x2F;p&gt;
&lt;p&gt;My general toolkit for running anything is a pair of &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.amazon.com&#x2F;HP-Prodesk-600-G3-Computer&#x2F;dp&#x2F;B07RLW1QB8&#x2F;&quot;&gt;refurbished HP Prodesks&lt;&#x2F;a&gt; sharing a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.freebsd.org&#x2F;en&#x2F;books&#x2F;handbook&#x2F;advanced-networking&#x2F;#carp&quot;&gt;CARP virtual interface&lt;&#x2F;a&gt; using &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;man.freebsd.org&#x2F;cgi&#x2F;man.cgi?query=devd&amp;amp;sektion=8&amp;amp;format=html&quot;&gt;devd&lt;&#x2F;a&gt; events if I need to do anything on failover. It&#x27;s shockingly easy and just works.&lt;&#x2F;p&gt;
&lt;p&gt;(side note: I tested 14.0-RELEASE on a pair of them and discovered that if a server is hosting a VIP and needs to to &lt;em&gt;use&lt;&#x2F;em&gt; that virtual IP when it is the backup, say to &lt;a href=&quot;https:&#x2F;&#x2F;skife.org&#x2F;b4&#x2F;unbound-and-nsd&#x2F;&quot;&gt;find DNS&lt;&#x2F;a&gt;, you need to disable &lt;code&gt;net.inet.ip.source_address_validation&lt;&#x2F;code&gt; in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;docs.freebsd.org&#x2F;en&#x2F;books&#x2F;handbook&#x2F;config&#x2F;index.html#configtuning-sysctl&quot;&gt;sysctl&lt;&#x2F;a&gt;, but then it works fine. I probably shouldn&#x27;t do this, but I&#x27;ll sort that out later.)&lt;&#x2F;p&gt;
&lt;p&gt;(side note 2: With those old HP Prodesks, you need to go into the BIOS and disable UEFI (switch to use &quot;legacy boot&quot; for the FreeBSD install to work). You don&#x27;t need to for Ubuntu.)&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Home DNS with Unbound and NSD</title>
          <pubDate>Sun, 12 Nov 2023 00:00:00 +0000</pubDate>
          <author>Brian McCallister</author>
          <link>https://skife.org/b4/unbound-and-nsd/</link>
          <guid>https://skife.org/b4/unbound-and-nsd/</guid>
          <description>&lt;p&gt;I recently redid the DNS on my home network, moving from &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;dnsmasq.org&quot;&gt;dnsmasq&lt;&#x2F;a&gt; to &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;nlnetlabs.nl&#x2F;projects&#x2F;unbound&#x2F;about&#x2F;&quot;&gt;Unbound&lt;&#x2F;a&gt;
and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;nlnetlabs.nl&#x2F;projects&#x2F;nsd&#x2F;about&#x2F;&quot;&gt;NSD&lt;&#x2F;a&gt;. Unbound acts as the DNS server the network uses, and Unbound hosts losts local zones for my search domains. For reasons, this is really across two sites: our home, and a barn we own a few miles away. Because I am a dork, I have things at both sites :-)&lt;&#x2F;p&gt;
&lt;p&gt;My network controller has a built in DNS server which assigns a local domain, a la &lt;code&gt;brians-laptop.local&lt;&#x2F;code&gt; to anything which gets a DHCP lease. I wanted to be able to respect these, but also assign a diferent domain to statically assigned things on the network, such as printers and our NAS. For these I set up a &lt;code&gt;.home&lt;&#x2F;code&gt; and &lt;code&gt;.barn&lt;&#x2F;code&gt; respectively, for things in the home and in the barn. Those zones are hosted on NSD, with a config like:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# &#x2F;usr&#x2F;local&#x2F;etc&#x2F;nsd&#x2F;nsd.conf&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;server&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    ip-address&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 127.0.0.1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    port&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 53530&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;zone&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; home&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    zonefile&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;home.zone&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;zone&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; barn&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    zonefile&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;barn.zone&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note that NSD is only listening on &lt;code&gt;127.0.0.1&lt;&#x2F;code&gt; and on port 53530. It should only ever be queried from the unbound instance on the same host (which is using port 53).&lt;&#x2F;p&gt;
&lt;p&gt;The zone files referenced are just stubs, not really correct, but they don&#x27;t need to be :-)&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;common-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;; &#x2F;usr&#x2F;local&#x2F;etc&#x2F;nsd&#x2F;home.zone&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ORIGIN home. &lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;; &amp;#39;default&amp;#39; domain as FQDN for this zone&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$TTL &lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;600&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt; ; default time-to-live for this zone&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;home.   IN  SOA     ns.home. noc.dns.icann.org. (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;        16&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;          ;Serial&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;        7200&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;        ;Refresh&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;        3600&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;        ;Retry&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;        1209600&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;     ;Expire&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;        3600&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;        ;Negative response caching TTL&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;; The nameserver that are authoritative for this zone.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                        NS      ns.home.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;nas.home.       A       192.168.2.114&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;printer.home.   A       192.168.2.140&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;m0001.home.     A       192.168.2.101&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;m0002.home.     A       192.168.2.102&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;and the barn:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;common-lisp&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$ORIGIN barn. &lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;; &amp;#39;default&amp;#39; domain as FQDN for this zone&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;$TTL &lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;600&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt; ; default time-to-live for this zone&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;barn.   IN  SOA     ns.barn. noc.dns.icann.org. (&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;        17&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;          ;Serial&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;        7200&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;        ;Refresh&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;        3600&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;        ;Retry&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;        1209600&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;     ;Expire&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt;        3600&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;        ;Negative response caching TTL&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;; The nameserver that are authoritative for this zone.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;                NS      ns.barn.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;m0003.barn.     A       192.168.81.101&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;m0004.barn.     A       192.168.81.102&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;dvr.barn.       A       192.168.81.110&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;With NSD up and running, I set up Unbound to make use of those local zones. It runs on the same instance as NSD, and is configured ot use it for those domains:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;# &#x2F;usr&#x2F;local&#x2F;etc&#x2F;unbound&#x2F;unbound.conf&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;server&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    # This is a CARP interface, which apparently &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6A737D;&quot;&gt;    # requires explicitely listing&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    interface&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 192.168.2.100&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    interface&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 0.0.0.0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    do-not-query-localhost&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; no&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    access-control&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; 192.168.0.0&#x2F;16 allow&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    local-zone&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;home&amp;quot; nodefault&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    domain-insecure&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;home&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    local-zone&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;barn&amp;quot; nodefault&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    domain-insecure&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;barn&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;stub-zone&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;home.&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    stub-addr&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; 127.0.0.1@53530&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;stub-zone&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;barn.&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    stub-addr&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; 127.0.0.1@53530&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;forward-zone&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    name&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; &amp;quot;local.&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    forward-addr&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 192.168.1.1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Unbound is set up to listen on all interfaces, but because I am using a &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;man.freebsd.org&#x2F;cgi&#x2F;man.cgi?query=carp&amp;amp;sektion=4&quot;&gt;CARP&lt;&#x2F;a&gt; interface as well, it seems to require seperately listing that one to avoid confusions. The two local zones are configured to be insecure (no DNSSEC) and to forward to the NSD instance as a &lt;code&gt;stub-zone&lt;&#x2F;code&gt; for each domain, respectively.&lt;&#x2F;p&gt;
&lt;p&gt;The dynamically assigned &lt;code&gt;.local&lt;&#x2F;code&gt; domain is forwarded to the router to pick up the names of things which get DHCP leases via the &lt;code&gt;forward-zone&lt;&#x2F;code&gt; block.&lt;&#x2F;p&gt;
&lt;p&gt;Finally, to make it all work, I hand out three search domains on DHCP, &lt;code&gt;.local&lt;&#x2F;code&gt;, &lt;code&gt;.home&lt;&#x2F;code&gt;, and &lt;code&gt;.barn&lt;&#x2F;code&gt;. This is DHCP code 119 with a value &lt;code&gt;local,home,barn&lt;&#x2F;code&gt;, for future reference.&lt;&#x2F;p&gt;
&lt;p&gt;Deployment wise, this is running on two servers in each location, with each server running both NSD and Unbound. The CARP interface is used to provide a single IP address for the DNS servers, and the DHCP server is configured to hand out that IP address as the DNS server for the network.&lt;&#x2F;p&gt;
&lt;p&gt;Fun fact along the way: I learned emacs has a mode for zone files which automatically increments &lt;code&gt;serial&lt;&#x2F;code&gt; when you save. Handy!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>20 Years of Wasting Time!</title>
          <pubDate>Sat, 11 Nov 2023 00:00:00 +0000</pubDate>
          <author>Brian McCallister</author>
          <link>https://skife.org/b4/now-with-more-zola/</link>
          <guid>https://skife.org/b4/now-with-more-zola/</guid>
          <description>&lt;p&gt;I cannot let 2023 go past without writing &lt;em&gt;anything&lt;&#x2F;em&gt; given it is 20 years of this blog, but an update to Hugo broke things, and getting it to work was just annoying. So I got frustrated enough making Hugo do what I wanted that I ported over to &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;getzola.org&#x2F;&quot;&gt;Zola&lt;&#x2F;a&gt; and here we go, I am able to update again!&lt;&#x2F;p&gt;
&lt;p&gt;Along the way I took a moment to add all the posts from my original blosxom based blog to the &lt;a href=&quot;&#x2F;archive&#x2F;&quot;&gt;archive&lt;&#x2F;a&gt;, which makes me happy. Really, I want to post about the home DNS setup I did, but that will have to wait until tomorrow :-) For now, I want to push this and see if the deploy works!&lt;&#x2F;p&gt;
&lt;p&gt;Happy 2023!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Blog CD Pipeline with AWS CodePipeline</title>
          <pubDate>Wed, 22 Nov 2017 17:09:31 -0800</pubDate>
          <author>Brian McCallister</author>
          <link>https://skife.org/b3/aws-code-services-for-blog/</link>
          <guid>https://skife.org/b3/aws-code-services-for-blog/</guid>
          <description>&lt;p&gt;Jumped out of order from &lt;a href=&quot;https:&#x2F;&#x2F;skife.org&#x2F;b3&#x2F;knock-knock-still-on&#x2F;&quot;&gt;my earlier checklist&lt;&#x2F;a&gt; and set up some automagic build and deploy. I&#x27;d wanted an excuse to try out &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;aws.amazon.com&#x2F;codepipeline&#x2F;&quot;&gt;CodePipeline&lt;&#x2F;a&gt;, so this was it!&lt;&#x2F;p&gt;
&lt;p&gt;So, how does this blog work? It is deployed to an S3 bucket (&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;skife.org&#x2F;&quot;&gt;skife.org&lt;&#x2F;a&gt;) with CloudFront in front of it. CloudFront is set up to use the free SNI certs to provide TLS. Previously, I pushed manually via &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;s3tools&#x2F;s3cmd&quot;&gt;s3cmd&lt;&#x2F;a&gt;, which worked well with some incantation fiddling.&lt;&#x2F;p&gt;
&lt;p&gt;I won&#x27;t write a full CodeBuild and CodeDeploy tutorial, Amazon has that well covered, but a couple bits were funny to work out, so will talk about those.&lt;&#x2F;p&gt;
&lt;p&gt;First, CodePipeline needs to trigger things. This is important as CodeBuild has no mechanism (which I could find) to only care about particular branches. CodeBuild really just does builds (kind of). Conceptually, do everything through CodePipeline and other stuff is just steps which react to the pipeline.&lt;&#x2F;p&gt;
&lt;p&gt;Given this is a static site, the build step just builds a tarball:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;version&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 0.2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;phases&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;  install&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    commands&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; wget https:&#x2F;&#x2F;github.com&#x2F;gohugoio&#x2F;hugo&#x2F;releases&#x2F;download&#x2F;v0.31&#x2F;hugo_0.31_Linux-64bit.deb&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; dpkg -i .&#x2F;hugo_0.31_Linux-64bit.deb&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;  build&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    commands&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; hugo&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; tar -C public -cvzf skife.org.tgz .&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;artifacts&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;  files&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; skife.org.tgz&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Nothing fancy here, but having the artifact for CodePipeline to pass around is important.&lt;&#x2F;p&gt;
&lt;p&gt;My first pass just had the deploy at the end of the build, but I want to be able to insert some basic tests before I deploy new versions. Just things like link verification, HTML5 validation, and maybe running stuff like &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;developers.google.com&#x2F;web&#x2F;tools&#x2F;lighthouse&#x2F;&quot;&gt;Lighthouse&lt;&#x2F;a&gt; against a test instance before letting it out. The test part :-) Because of this, I wanted to seperate build from deploy. It turns out &quot;deploy by copying into an S3 bucket&quot; is not a thing &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;aws.amazon.com&#x2F;codedeploy&#x2F;&quot;&gt;CodeDeploy&lt;&#x2F;a&gt; has any concept for.&lt;&#x2F;p&gt;
&lt;p&gt;So my &quot;deploy&quot; is just another CodeBuild build:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;version&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #005CC5;&quot;&gt; 0.2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;phases&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;  build&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #22863A;&quot;&gt;    commands&lt;&#x2F;span&gt;&lt;span&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; tar -xf skife.org.tgz&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; rm skife.org.tgz&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;      -&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; aws s3 sync . s3:&#x2F;&#x2F;skife.org --acl public-read --cache-control public,max-age=600&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;You can feed one build to another, it doesn&#x27;t mind. When I configured the second build I had to set it up with an output artifact or CodePipeline wouldn&#x27;t let me add it. After I saved the CodePipeline changes, I could go back and remove that output. The other decent path is probably to set up a Lambda function that takes apart the tarball and copies things over... but this build approach seems simpler.&lt;&#x2F;p&gt;
&lt;p&gt;I tried to put a cloudfront invalidation into the last step as well, but the version of the aws cli on the build image is old and it is not supported. I&#x27;ll sort that out later. Once I do, will change max-age to max-age=31536000 or so and add something like:&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;-&lt;&#x2F;span&gt;&lt;span style=&quot;color: #032F62;&quot;&gt; aws cloudfront create-invalidation --distribution-id E1DTTO3T6ZPN9M --paths &#x2F; &#x2F;index.html &#x2F;404.html &#x2F;archive.html &#x2F;index.xml&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;to the build commands, and voila!&lt;&#x2F;p&gt;
</description>
      </item>
      <item>
          <title>Knock, knock. This thing still on?</title>
          <pubDate>Sat, 18 Nov 2017 17:09:31 -0800</pubDate>
          <author>Brian McCallister</author>
          <link>https://skife.org/b3/knock-knock-still-on/</link>
          <guid>https://skife.org/b3/knock-knock-still-on/</guid>
          <description>&lt;p&gt;I&#x27;ve attempted to wake this blog up a couple times, but between &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;jekyllrb.com&#x2F;&quot;&gt;Jekyll&lt;&#x2F;a&gt; changing, &lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;pygments.org&#x2F;&quot;&gt;Pygments&lt;&#x2F;a&gt; changing, and whatnot, it has been more pain than any given post seemed worth. I&#x27;ve recently had three folks independently chastise me for no longer writing, however, and three is a magic number.&lt;&#x2F;p&gt;
&lt;p&gt;So, this is basically just a test post as I try converting over to &lt;a rel=&quot;external&quot; href=&quot;http:&#x2F;&#x2F;gohugo.io&#x2F;&quot;&gt;Hugo&lt;&#x2F;a&gt;. Jekyll resisted hard enough that I declared bankruptcy. We&#x27;ll try this one. If I get annoyed at it, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;www.getgutenberg.io&#x2F;&quot;&gt;Gutenberg&lt;&#x2F;a&gt; is next on the list. &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;twitter.com&#x2F;tcurdt&quot;&gt;Torsten&lt;&#x2F;a&gt; says nice things about it :-)&lt;&#x2F;p&gt;
&lt;p&gt;All my old posts should be &lt;a href=&quot;https:&#x2F;&#x2F;skife.org&#x2F;b3&#x2F;knock-knock-still-on&#x2F;archive.html&quot;&gt;exactly where I left them&lt;&#x2F;a&gt;, no sense breaking URLs.&lt;&#x2F;p&gt;
&lt;p&gt;For a test post I need to include some code, that being most of the point of this thig, so some Rust from &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;brianm&#x2F;wsf&quot;&gt;wsf&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;pre class=&quot;giallo&quot; style=&quot;color: #24292E; background-color: #FFFFFF;&quot;&gt;&lt;code data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;impl&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; From&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;hyper&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span&gt;error&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;Error&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; for&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; CliError&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;    fn&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; from&lt;&#x2F;span&gt;&lt;span&gt;(err&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;:&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; hyper&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;error&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;Error&lt;&#x2F;span&gt;&lt;span&gt;)&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt; -&amp;gt;&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt; CliError&lt;&#x2F;span&gt;&lt;span&gt; {&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;        CliError&lt;&#x2F;span&gt;&lt;span style=&quot;color: #D73A49;&quot;&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color: #6F42C1;&quot;&gt;Http&lt;&#x2F;span&gt;&lt;span&gt;(err)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So far, so good.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot; checked=&quot;&quot;&#x2F;&gt;
Build with Hugo&lt;&#x2F;li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot; checked=&quot;&quot;&#x2F;&gt;
Code is pretty&lt;&#x2F;li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot; checked=&quot;&quot;&#x2F;&gt;
Ensure Atom&#x2F;RSS is sane&lt;&#x2F;li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&#x2F;&gt;
Add some kind of commenting (&lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;staticman.net&#x2F;&quot;&gt;staticman&lt;&#x2F;a&gt;?)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot;&#x2F;&gt;
Better favicon&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Off to deploy and see if this thing works!&lt;&#x2F;p&gt;
</description>
      </item>
    </channel>
</rss>