<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>caleb's website</title><link>https://calebsharp.dev/</link><description>Recent content on caleb's website</description><generator>Hugo</generator><language>en</language><managingEditor>me@calebsharp.dev (Caleb Sharp)</managingEditor><webMaster>me@calebsharp.dev (Caleb Sharp)</webMaster><copyright>© Caleb Sharp 2026</copyright><lastBuildDate>Fri, 24 Apr 2026 16:45:30 -0700</lastBuildDate><atom:link href="https://calebsharp.dev/index.xml" rel="self" type="application/rss+xml"/><item><title>Deployment Updates: Nix</title><link>https://calebsharp.dev/posts/deployment-updates-nix/</link><pubDate>Fri, 24 Apr 2026 16:45:30 -0700</pubDate><author>me@calebsharp.dev (Caleb Sharp)</author><guid>https://calebsharp.dev/posts/deployment-updates-nix/</guid><description>&lt;p&gt;I&amp;rsquo;ve updated the build process for my site to use Nix.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;If I stop working on it for a year, I want it to work still when I come back
to it.&lt;/li&gt;
&lt;li&gt;Faster build times because of caching&lt;/li&gt;
&lt;li&gt;I wanted to learn more about Nix&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I originally did this the &amp;ldquo;regular&amp;rdquo; Nix way, but it turns out this is kind of
the &amp;ldquo;old&amp;rdquo; way? Apparently the more popular thing to use recently is &amp;ldquo;flakes&amp;rdquo;
(which I have seen mentioned in passing a couple times prior). From what I&amp;rsquo;ve
picked up on, this takes away some of the common impure footguns that usually
come with Nix, the biggest of which is adding a lockfile for every dependency.&lt;/p&gt;
&lt;p&gt;So of course I immediately reimplemented it as a flake.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I found &lt;a href="https://prodlog.xyz/posts/hugo-nix/"&gt;this blog post&lt;/a&gt; which helped me
get most of the way there, and I only had to learn a little bit more about how
flakes work to finish (because I also need to install PostCSS via pnpm). I think
I do like the flake approach a little bit better. For one, I can just stick
everything in one file instead of having separate &lt;code&gt;build.nix&lt;/code&gt;, &lt;code&gt;shell.nix&lt;/code&gt;,
&lt;code&gt;default.nix&lt;/code&gt; and whatever else files. I think it&amp;rsquo;s also provides a bit more
standardization, which felt like one of the most confusing things about Nix when
I first started learning about it.&lt;/p&gt;
&lt;p&gt;I found this documentation helpful for setting up pnpm:
&lt;a href="https://nixos.org/manual/nixpkgs/stable/#javascript-pnpm"&gt;https://nixos.org/manual/nixpkgs/stable/#javascript-pnpm&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s my &lt;code&gt;flake.nix&lt;/code&gt; as of writing this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-nix" data-lang="nix"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;caleb&amp;#39;s website&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;github:nixos/nixpkgs?ref=10e7ad5bbcb421fe07e3a4ad53a634b0cd57ffac&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;github:numtide/flake-utils&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;utils&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eachDefaultSystem&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;nixpkgs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;inherit&lt;/span&gt; &lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;packages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;rec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;calebsharp-dev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdenv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mkDerivation&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;finalAttrs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;calebsharp.dev&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;nativeBuildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;git&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;hugo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pnpmConfigHook&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pnpm_10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;nodejs_24&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pnpmDeps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;fetchPnpmDeps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;finalAttrs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;finalAttrs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;fetcherVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;pnpm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pnpm_10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;sha256-k/2/Yjcs5h6P2Z6UMzkhMMDHXhTUPlMvMWtorhLLM94=&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;buildPhase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; hugo --minify
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;installPhase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cp -r public $out&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calebsharp-dev&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;apps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;rec&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;build&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mkApp&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;drv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hugo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;serve&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mkApp&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;drv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;writeShellScriptBin&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;hugo-serve&amp;#34;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hugo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;/bin/hugo server -D
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;newcontent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;utils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mkApp&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;drv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;writeShellScriptBin&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;new-post&amp;#34;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hugo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;/bin/hugo new content &amp;#34;$1&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;devShells&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pkgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;mkShellNoCC&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;nativeBuildInputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;hugo&lt;/span&gt; &lt;span class="n"&gt;go&lt;/span&gt; &lt;span class="n"&gt;rsync&lt;/span&gt; &lt;span class="n"&gt;openssh&lt;/span&gt; &lt;span class="n"&gt;pnpm_10&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I&amp;rsquo;m sure there&amp;rsquo;s improvements to make and I know there&amp;rsquo;s still lots to learn.
I&amp;rsquo;m also starting to pick up on the idea that there&amp;rsquo;s probably many different
ways of accomplishing the same thing, which tends to make learning a tad bit
slower.&lt;/p&gt;
&lt;p&gt;Local dev is super easy, since I can just run &lt;code&gt;nix run&lt;/code&gt; (which is an alias of
&lt;code&gt;nix run .#serve&lt;/code&gt;) and automatically bring along the dependencies I need (just
hugo in this case).&lt;/p&gt;
&lt;p&gt;I also have noticed some improvements in CI performance. I have my self-hosted
Forgejo runner, which is great, but the runner image I use doesn&amp;rsquo;t include all
the tools I need, so I&amp;rsquo;ve been running &lt;code&gt;apt&lt;/code&gt; from within the runner to add
dependencies I need. I never love doing this because it&amp;rsquo;s both slower and less
reproducible. I considered building my own custom runner image with the tools I
needed, but that&amp;rsquo;s an extra maintenance burden that I don&amp;rsquo;t want, and it doesn&amp;rsquo;t
help me if I need different versions of tools between projects. Nix has
basically solved all these problems, because it&amp;rsquo;s fully deterministic, and I can
cache the entire Nix store folder to speed up subsequent builds if dependencies
don&amp;rsquo;t change. I don&amp;rsquo;t know if this is &lt;em&gt;totally&lt;/em&gt; the right solution (I think a
big problem is the size of the folder can grow indefinitely, so I&amp;rsquo;ll need some
kind of cron job to periodically prune it), but it seems to be working.&lt;/p&gt;
&lt;p&gt;My new deployment workflow:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Build and Deploy Website&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;main&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&lt;/span&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# A custom runner image that includes basically just Nix and node&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nix-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;nix-store:/nix&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;nix-cache:/root/.cache/nix&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;defaults&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Run all commands in the same dev shell environment I run locally. Tools have the _exact_ same version in CI, which is pretty cool&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;shell&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nix develop --command bash -c &amp;#34;{0}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v6&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;submodules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;recursive&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Add SSH key&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;SSH_AUTH_SOCK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/tmp/ssh_agent.sock&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; mkdir -p /root/.ssh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; which ssh-keyscan
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; ssh-keyscan x.x.x.x &amp;gt;&amp;gt; /root/.ssh/known_hosts
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;${{ secrets.SSH_KEY }}&amp;#34; &amp;gt; /root/.ssh/github_actions
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; chmod 600 /root/.ssh/github_actions
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; ssh-agent -a $SSH_AUTH_SOCK &amp;gt; /dev/null	
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; ssh-add /root/.ssh/github_actions&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nix build&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nix build&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deploy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;SSH_AUTH_SOCK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/tmp/ssh_agent.sock&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # Just need to copy hugo build artifacts to deploy
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; rsync -avz --delete result/ caleb@x.x.x.x:/srv/calebsharp.dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>On Rental Scooters</title><link>https://calebsharp.dev/posts/on-rental-scooters/</link><pubDate>Mon, 20 Apr 2026 09:56:57 -0700</pubDate><author>me@calebsharp.dev (Caleb Sharp)</author><guid>https://calebsharp.dev/posts/on-rental-scooters/</guid><description>&lt;p&gt;We took the new Bird e-things they&amp;rsquo;ve deployed to Kamloops yesterday. We&amp;rsquo;d
ridden them once before, for a shorter one-way trip. This time, we wanted to
head across the river to grab some ice cream, and I also wanted to try the new
e-bikes they&amp;rsquo;ve started putting out. I was able to find one, but my partner had
to take a scooter, because only I have a helmet and only the scooters come with
one you can rent.&lt;/p&gt;
&lt;h2 id="unlocking"&gt;Unlocking&lt;/h2&gt;
&lt;p&gt;To actually rent one of these things is &lt;em&gt;relatively&lt;/em&gt; straightforward. All of the
&amp;ldquo;vehicles&amp;rdquo; have a little QR code that you&amp;rsquo;re meant to scan in the app to unlock.
After some waiting around for what I assume is some bluetooth negotiation, an
apple pay prompt will appear, to charge you with an &amp;ldquo;amount pending&amp;rdquo;. They take
a $3 hold while you are renting, so I assume that&amp;rsquo;s related. Then, once you&amp;rsquo;ve
unlocked it, you&amp;rsquo;re basically ready to go.&lt;/p&gt;
&lt;p&gt;As with anything wireless and probably shitty-react-native, don&amp;rsquo;t expect this to
go very smoothly. The first time I attempted to rent a scooter, I got stuck in
the helmet rental screen, which presented me with instructions that were clearly
for a completely different type of scooter. I also made the mistake of reserving
that scooter (since it was a 10 minute walk away). Turns out you get charged at
the same rate as riding it, so I paid like $7 for nothing.&lt;/p&gt;
&lt;h2 id="the-ride"&gt;The ride&lt;/h2&gt;
&lt;p&gt;The ride sucks. I&amp;rsquo;ve not tried many e-bikes before and this was actually my
first time trying an e-scooter, but they both had incredible rigid suspension.
Actually, I think the bike has no suspension. They&amp;rsquo;re also super heavy. I don&amp;rsquo;t
think this is necessarily a surprise to people who are more familiar with this
type of thing (after all, they have to put up with a lot of abuse. More on that
in a sec), but it was definitely a different experience than the &amp;ldquo;regular&amp;rdquo;
e-bikes I&amp;rsquo;ve ridden before.&lt;/p&gt;
&lt;p&gt;They&amp;rsquo;ve also been totally trashed. The bikes have only been out for a week and
they&amp;rsquo;re already covered in graffiti and scuffed to hell. That said, after
mentioning it to a friend, he said it&amp;rsquo;s probably just because they&amp;rsquo;ve come from
another city and we&amp;rsquo;re not getting brand new stuff. Fair enough, my faith in
people is slightly restored.&lt;/p&gt;
&lt;p&gt;Anyway, we were able to ride across the bridge and grab some ice cream (I got
spumoni; very good) and hang out for a bit. We decided to book it back after we
saw the huge rain cloud that had been looming in the distance shift over to us.
We both took scooters on the way back.&lt;/p&gt;
&lt;h2 id="parking"&gt;Parking&lt;/h2&gt;
&lt;p&gt;You have to park the scooters in designated areas. This is pretty annoying,
especially because they can be relatively spread out, depending on the part of
town. This means you have to basically plan where you&amp;rsquo;re going to park before
you start the ride. If you &lt;em&gt;don&amp;rsquo;t&lt;/em&gt; check ahead of time, you have to stop and
pull out your phone, since the parking spots are only indicated by map icons in
the app. If you&amp;rsquo;re lucky, there&amp;rsquo;s another scooter already there you can use as a
landmark. Once we ran into an issue where the app just wouldn&amp;rsquo;t recognize that
we were in a parking spot, which is pretty lame especially when the ride time
continues to tick up.&lt;/p&gt;
&lt;h2 id="pricing"&gt;Pricing&lt;/h2&gt;
&lt;p&gt;We took about 15 minutes each way (about 4km). The total cost for both of use
was probably around $25-$30 (one of us had to rent a helmet). The pricing is
pretty clear before you unlock the scooter ($1.15 fixed unlock fee + $0.35/min),
but I was still pretty surprised at how expensive it was to take such a short
trip. They have some monthly pass options that I had initially scoffed at for
how expensive they seemed, but in comparison they&amp;rsquo;re a pretty good deal. Not
sure if that&amp;rsquo;s a good thing.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Update: I&amp;rsquo;ve taken out a scooter a couple more times since writing this. I
ended up getting the $4/mo &amp;ldquo;unlimited rides&amp;rdquo; pass, which basically caps ride
fares to $3 (+ tax!) for 20 minute-and-under rides (I think you get charged at
the regular fare for time after). Not sure of the exact math, but I only had
to do two 15 minute rides to make that worth it. $3 rides makes it
significantly more competitive with other modes of transport IMO. If you need
to do lots of trips in a day though, it&amp;rsquo;s still hard to beat the $4.50 day
pass for the bus.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The larger problem was just the lack of infrastructure for most of the way
there. Unfortunately, when we don&amp;rsquo;t have the ability to use a dedicated bike
lane (which is more often than not), we&amp;rsquo;re required by law to ride on the road.
I don&amp;rsquo;t &lt;em&gt;mind&lt;/em&gt; this on a bike (not to say I like it; it&amp;rsquo;s still a horrible
experience cycling here most of the time), but the scooter was a new level of
fear. Compared to a bike, I feel quite a bit more vulnerable and unstable.
Riding down a busy road with trucks passing by within an arms-length at 60km/h
is not a pleasant experience (not to mention by law they&amp;rsquo;re supposed to give us
1.5m. Oh well). I can&amp;rsquo;t recall who said this, but I saw somewhere scooters
described as being in the &amp;ldquo;uncanny valley&amp;rdquo; of transportation options. That feels
apt. If we had great cycling infrastructure, I wouldn&amp;rsquo;t foresee much adoption of
these, since most people would already have a bike, or at least would prefer the
experience of renting a bike/e-bike. With how spread out everything is here,
it&amp;rsquo;s hard to think of a trip where taking a scooter makes much sense. If I just
need to get around downtown, I would prefer to walk, especially because I would
have to ride on the road. On the other hand, if I want to take it on a longer
trip, it also doesn&amp;rsquo;t make sense, primarily due to the steep price. At that
point, it&amp;rsquo;s more competitive to take rideshare, which I think is crazy. Even the
bus is a better deal, and depending on the trip, also more convenient.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Update: With the monthly pass, I don&amp;rsquo;t think the scooters are &lt;em&gt;quite&lt;/em&gt; as bad a
deal as I made it out to be here. It&amp;rsquo;s still almost always more expensive than
the bus, but especially on weekends when bus frequencies are lower, it can be
a lot more convenient. I don&amp;rsquo;t think this changes any of my thoughts on the
ride experience and lack of infrastructure though.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Overall, I don&amp;rsquo;t think we&amp;rsquo;ll see much uptake, and unfortunately I think most
people will interpret this as a lack of wanting alternatives to driving. It&amp;rsquo;s
nothing new when it comes to cycling infrastructure, and more generally to any
transportation options other than the private automobile, but it&amp;rsquo;s a bit
frustrating that people can&amp;rsquo;t understand that it doesn&amp;rsquo;t work because we aren&amp;rsquo;t
making the systemic changes to our city that would &lt;em&gt;let&lt;/em&gt; them work.&lt;/p&gt;
&lt;p&gt;My most optimistic take is that people will try the e-bikes once, and realize
that it &lt;em&gt;sucks&lt;/em&gt; to ride in Kamloops in most places, and they&amp;rsquo;ll be radicalized
to advocate for change. I think the more realistic outcome is that people will
try it, they won&amp;rsquo;t like it, and it&amp;rsquo;ll just reinforce their belief that driving
is the only way.&lt;/p&gt;</description></item><item><title>How My Site Is Deployed</title><link>https://calebsharp.dev/posts/how-my-site-is-deployed/</link><pubDate>Wed, 15 Apr 2026 09:19:03 -0700</pubDate><author>me@calebsharp.dev (Caleb Sharp)</author><guid>https://calebsharp.dev/posts/how-my-site-is-deployed/</guid><description>&lt;h2 id="tldr"&gt;tl;dr&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Changes are pushed to a self-hosted forgejo instances&lt;/li&gt;
&lt;li&gt;Pushes to &lt;code&gt;main&lt;/code&gt; trigger an action that builds the site with &lt;code&gt;hugo&lt;/code&gt; and
copies it with rsync to another server.&lt;/li&gt;
&lt;li&gt;Caddy serves the static files at &lt;code&gt;/srv/calebsharp.dev&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="hugo"&gt;Hugo&lt;/h2&gt;
&lt;p&gt;This is pretty self-explanatory, but this site is built with
&lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt;. This is actually my first time trying it, and I&amp;rsquo;m
still getting used to it. For that reason, I decided to start with a theme and
ended up choosing this neat
&lt;a href="https://github.com/panr/hugo-theme-terminal"&gt;terminal theme by panr&lt;/a&gt;. I&amp;rsquo;ve
started to rip it apart and customize it to my liking and I expect to eventually
change most everything. At this point, I&amp;rsquo;m still trying to decide if it makes
more sense to treat my custom stuff as a &amp;ldquo;theme&amp;rdquo;, or just inline everything into
the main project. I get the feeling it only matters if I want to re-use the
theme on another site, and/or share it. Guess we&amp;rsquo;ll see!&lt;/p&gt;
&lt;p&gt;Other than that, I&amp;rsquo;ve been appreciating the authorship experience of Hugo. I
feel like I can focus on writing when I want to write, and focus on everything
but the posts when I want to tinker. It&amp;rsquo;s a nice separation of concerns that I
think stops me from getting too distracted.&lt;/p&gt;
&lt;h2 id="forgejo"&gt;Forgejo&lt;/h2&gt;
&lt;p&gt;I host my own Forgejo instance on an old PC I have at home. I use Tailscale to
connect to it (and holy crap what a useful tool that is). I have a cheap Hetzner
instance somewhere in Germany that I use as an actions runner (I&amp;rsquo;ll have to
write about that soon, it&amp;rsquo;s a bit finicky to set up, especially in my case where
I wanted to run the runner in podman &lt;em&gt;and&lt;/em&gt; run workflows in &amp;ldquo;nested&amp;rdquo;
containers). Other than that, it&amp;rsquo;s pretty stock.&lt;/p&gt;
&lt;p&gt;To have a nice domain/SSL, I don&amp;rsquo;t use Tailscale&amp;rsquo;s MagicDNS feature (I think the
domains are ugly!). Instead, I just have a public DNS record that resolves to
the internal Tailscale IP of the machine (&lt;code&gt;dig forgejo.ts.calebsharp.dev&lt;/code&gt;). If
you&amp;rsquo;re not on the tailnet, you can&amp;rsquo;t connect. I proxy Forgejo (+ all the other
things I run) through Caddy for automatic HTTPS.&lt;/p&gt;
&lt;p&gt;When I push new changes to the upstream repo, the runner kicks off a build job
that basically just installs some tools (TODO for myself: add tools to the
runner image) and runs &lt;code&gt;hugo build --minify&lt;/code&gt;. I&amp;rsquo;m using
&lt;a href="https://mise.jdx.dev/"&gt;mise&lt;/a&gt; to manage the versions of the tools I need, both
for local dev and in CI/CD (although I can&amp;rsquo;t get it to install hugo; there seems
to be some problem with the URL it tries to access for macOS builds). It then
copies the built static files with &lt;code&gt;rsync&lt;/code&gt; to my Hetzner VPS that&amp;rsquo;s actually
hosting the site. Since both the runner and server are both in my tailnet, it&amp;rsquo;s
trivial to let them connect via SSH without having to worry about exposing SSH
to the world. That&amp;rsquo;s it for deployment! It&amp;rsquo;s a little refreshing to not have a
stupidly complex build step with containers and linters and all that crap.&lt;/p&gt;
&lt;h2 id="caddy"&gt;Caddy&lt;/h2&gt;
&lt;p&gt;The static files deployed to the server are ultimately served by Caddy. Pretty
basic config, along with some automatic HTTPS and basic cache-control headers:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-caddyfile" data-lang="caddyfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;calebsharp.dev&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;root&lt;/span&gt; &lt;span class="nd"&gt;/srv/calebsharp.dev&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;encode&lt;/span&gt; &lt;span class="s"&gt;zstd&lt;/span&gt; &lt;span class="s"&gt;gzip&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;file_server&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;header&lt;/span&gt; &lt;span class="nd"&gt;/css/*&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;Cache-Control&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;public, max-age=3600, must-revalidate&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;header&lt;/span&gt; &lt;span class="nd"&gt;/fonts/*&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;Cache-Control&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;public, max-age=604800, must-revalidate&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt; # API credentials for ACME DNS challenges
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; porkbun
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And that&amp;rsquo;s it! I mentioned this in my first post, but it&amp;rsquo;s nice to have
something architecturally simple to let myself focus on writing when I want to.
On the other hand, I feel like I have quite a bit of flexibility to do whatever
cool stuff I want to try out. I think it strikes a nice balance.&lt;/p&gt;
&lt;p&gt;Thanks for reading! If you&amp;rsquo;re trying to do something similar and want to chat
about it, feel free to send me an email (info on the &lt;a href="https://calebsharp.dev/about/"&gt;about page&lt;/a&gt;).&lt;/p&gt;</description></item><item><title>Hello World</title><link>https://calebsharp.dev/posts/hello-world/</link><pubDate>Mon, 13 Apr 2026 12:26:25 -0700</pubDate><author>me@calebsharp.dev (Caleb Sharp)</author><guid>https://calebsharp.dev/posts/hello-world/</guid><description>&lt;h1 id="hello-world"&gt;Hello world!&lt;/h1&gt;
&lt;p&gt;This is my first post, finally. After a long time (easily years at this point), I&amp;rsquo;ve
finally moved past the stage of &amp;ldquo;building a blog&amp;rdquo;, to &amp;ldquo;writing a blog&amp;rdquo;. Who knew that
it could take so long to do myself what others have done bajillions of times.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve decided to just use Hugo. Hopefully it&amp;rsquo;s going to protect me from my
own urge to endlessly tinker with the tech &lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;. Ironically, I feel much less of a desire
to iterate on my words. I think I will be mostly happy to just publish whatever stream-of-consciousness
garbage comes out.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s to at least one more post!&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;IIRC my last attempt was a from-scratch HTTP server in Rust, along with &lt;a href="https://htmx.org/"&gt;htmx&lt;/a&gt;. Why I added frontend
interactivity before building any frontend: for fun. Did I ever finish: no.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description></item><item><title>About</title><link>https://calebsharp.dev/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>me@calebsharp.dev (Caleb Sharp)</author><guid>https://calebsharp.dev/about/</guid><description>&lt;h1 id="calebs-website"&gt;caleb&amp;rsquo;s website&lt;/h1&gt;
&lt;p&gt;Hi, my name is Caleb. I&amp;rsquo;m a profesional software developer. Mostly, I find myself
these days building wildfire modelling tools.&lt;/p&gt;
&lt;p&gt;I enjoy working with Rust, and recently have had a lot of fun building tools with
Elixir/Phoenix LiveView.&lt;/p&gt;
&lt;p&gt;Other hobbies include: urbanism, trains, biking, cooking&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/calebissharp"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;me &lt;code&gt;at&lt;/code&gt; this domain&lt;/p&gt;</description></item></channel></rss>