<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Alex&#39;s Blog</title>
    <link>https://blog.alexboden.ca</link>
    <description>Alex Boden - Computer Science Student at University of Waterloo</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Sun, 15 Feb 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://blog.alexboden.ca/rss.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>You should make your own OpenClaw</title>
      <link>https://blog.alexboden.ca/occam-claw/</link>
      <pubDate>Sun, 15 Feb 2026 00:00:00 +0000</pubDate>
      
      <guid>https://blog.alexboden.ca/occam-claw/</guid>
      <description>My opinion on why developers should make their own tools</description>
      <content:encoded><![CDATA[<p>Peter Steinberger&#8217;s &#8216;Clawdbot&#8217; was a rough weekend hack designed for power-user &#8216;God Mode.&#8217; As it evolved into <a href="https://openclaw.ai/">OpenClaw</a>, it brought in a laundry list of features. By trying to be everything for everyone, it became a massive security nightmare with a surface area too large for most individuals to truly secure. Instead of adopting a bloated general-purpose platform, developers should build their own minimal AI assistant tailored to what they actually need.</p>
<p>We&#8217;ve seen developers and cloud providers alike flock to this project as it has skyrocketed in popularity. We have spinoffs like <a href="https://github.com/HKUDS/nanobot">nanoclaw</a>, <a href="https://github.com/sipeed/picoclaw">picoclaw</a> and <a href="https://github.com/HKUDS/nanobot">nanobot</a>. We&#8217;ve also seen cloud providers offering to host this type of service such as <a href="https://www.digitalocean.com/community/tutorials/how-to-run-openclaw">Digital Ocean</a>, <a href="https://blog.cloudflare.com/moltworker-self-hosted-ai-agent/">Cloudflare</a> and <a href="https://www.hostinger.com/support/how-to-install-openclaw-on-hostinger-vps-template-installation/">Hostinger</a>. There are even companies like <a href="https://ai.com/">ai.com</a> that are focused on this new &#8220;AI Assistant&#8221; paradigm.</p>
<p>This is all very exciting but seems to go against the original philosophy of OpenClaw. The sheer amount of features that OpenClaw has does provide flexibility for every user, but it also takes away a user&#8217;s ability to truly have it be their own. You can fork the repo to make it more &#8220;custom&#8221; but with over 10,000 commits and 500,000 lines of code it&#8217;s not exactly maintainable. Even the creator was <a href="https://x.com/steipete/status/2023057089346580828?s=20">struggling</a>, merging over 600 commits and losing ground on open PRs. As I was writing this, Steinberger <a href="https://steipete.me/posts/2026/openclaw">announced</a> he&#8217;s leaving OpenClaw to join OpenAI, transitioning the project to an independent foundation. When even the creator decides a project has outgrown what one person can manage, it reinforces the case for building something smaller and purpose-built.</p>
<p>While you will see OpenClaw marketed as secure due to self-hosting, the reality is that many &#8216;out of the box&#8217; setups leave administrative interfaces exposed. The <a href="https://www.wiz.io/blog/exposed-moltbook-database-reveals-millions-of-api-keys">Moltbook breach</a> of 1.5 million API keys and the <a href="https://www.reddit.com/r/hacking/comments/1r30t25/i_scanned_popular_openclaw_skills_heres_what_i/">skills marketplace</a> illustrate this point exactly. Although they do care about <a href="https://openclaw.ai/blog/virustotal-partnership">security</a>, this project&#8217;s nature leaves itself open to so many attacks. A smaller, purpose-built tool has a fundamentally smaller attack surface, making it far easier to audit and secure.</p>
<p>All of this made me question if we really need all of these features. In philosophy, <a href="https://en.wikipedia.org/wiki/Occam%27s_razor">Occam&#8217;s razor</a> is the theory that &#8220;recommends searching for explanations constructed with the smallest possible set of elements.&#8221; The same principle applies to software: the simplest system that meets your needs is the one with the fewest vulnerabilities, the least maintenance burden, and the most clarity. Like others, I saw the benefits of OpenClaw but I didn&#8217;t need many features, just the ability to message the AI from my phone and have it manage my calendar. This led me to build <a href="https://github.com/alexboden/occam-claw">occam-claw</a>, an AI assistant that has only the features I need and none of the bloat. After building this, I realized the following benefits:</p>
<ul>
<li>It&#8217;s easy to build (took a few hours) and it&#8217;s fun</li>
<li>You can customize everything, you get everything you need and nothing you don&#8217;t</li>
<li>Smaller footprint on your host, both in resource usage and attack surface</li>
<li>You can understand every line of code, which means you can actually reason about its security</li>
</ul>
<p>I&#8217;m not saying you shouldn&#8217;t use OpenClaw or any of the other spinoffs. I think all developers should really consider where they want to integrate AI into their life and what value these products are actually providing. Obviously this is dependent on the person, but I prefer controlling what is fed into my AI model and heavily restrict what it can do on my behalf. When you build it yourself, you make those decisions deliberately rather than inheriting someone else&#8217;s defaults.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Tracking Your Chess Addiction</title>
      <link>https://blog.alexboden.ca/chess-stats/</link>
      <pubDate>Sun, 28 Dec 2025 00:00:00 +0000</pubDate>
      
      <guid>https://blog.alexboden.ca/chess-stats/</guid>
      <description>Using the chess.com API to track time spent playing chess</description>
      <content:encoded><![CDATA[<p><a href="https://www.chess.com">chess.com</a> has a great <a href="https://support.chess.com/en/articles/9650547-published-data-api">public API</a> that allows you to access your chess games and stats. You can calculate your time spent playing using the start and end times of your games.</p>
<p>I created a simple website that aggregates this data by month so you can see how much time you&#8217;ve spent playing chess. Check it out at <a href="https://chess-stats.alexboden.ca">chess-stats.alexboden.ca</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Toronto Bike Share Status</title>
      <link>https://blog.alexboden.ca/toronto-bike-share-status/</link>
      <pubDate>Fri, 24 Oct 2025 00:00:00 +0000</pubDate>
      
      <guid>https://blog.alexboden.ca/toronto-bike-share-status/</guid>
      <description>Improving my daily commute with a custom dashboard and iOS widget for Toronto&#39;s Bike Share network</description>
      <content:encoded><![CDATA[<p>I love Toronto’s Bike Share network, but often times the bike station closest to my place has no bikes or the place I&#8217;m heading to has no docks. The Bike Share app is a bit clunky, requiring multiple taps and scrolling to check all of this info. In order to save those few extra seconds in the morning, I did the logical thing and spent multiple hours to make a simple dashboard and iOS widget that surfaces the info I need at a glance.</p>
<p>Check out the project on <a href="https://github.com/alexboden/toronto-bike-share-status">GitHub</a></p>
<p>Check out the website <a href="https://toronto-bike.alexboden.ca">here</a>.</p>
<p>Widget on iOS home screen:






<figure class="blog-figure">
    <img src="/images/toronto-bike-share/widget.jpg" alt="iOS Scriptable widget showing favorite stations." class="img-fluid" style="max-width: 70%; width: 100%; ">
    
    <figcaption>iOS Scriptable widget showing favorite stations.</figcaption>
    
</figure> 
</p>
]]></content:encoded>
    </item>
    <item>
      <title>Self-hosting services on a custom domain securely</title>
      <link>https://blog.alexboden.ca/self-hosting-securely/</link>
      <pubDate>Tue, 23 Sep 2025 00:00:00 +0000</pubDate>
      
      <guid>https://blog.alexboden.ca/self-hosting-securely/</guid>
      <description>How I securely self-host Gitea, Jellyfin, and Proxmox with Tailscale, Caddy, and Cloudflare</description>
      <content:encoded><![CDATA[<h2 id="my-setup">My Setup</h2>
<ul>
<li><strong>Proxmox</strong>: Running on a bare metal server.</li>
<li><strong>Basic Linux VM</strong>: Running services I would like to access securely.</li>
<li><strong>Custom domain</strong>: I have the domain <code>alexboden.ca</code> with DNS records managed by Cloudflare.</li>
</ul>
<h2 id="the-goal">The Goal</h2>
<ul>
<li><strong>Private by default:</strong> Services should only be reachable from my devices.</li>
<li><strong>Single URL per service:</strong> e.g. <code>git.alexboden.ca</code>, <code>jellyfin.alexboden.ca</code>, <code>proxmox.alexboden.ca</code>.</li>
<li><strong>Clean HTTPS:</strong> No browser warnings, even on phones.</li>
<li><strong>No public exposure:</strong> No NAT forwards, no dangling ports.</li>
</ul>
<h2 id="the-solution">The Solution</h2>
<p>I use <strong>Caddy running inside a VM</strong> as a single TLS termination point, with <strong>Tailscale</strong> providing the private network:</p>
<ul>
<li><strong>Tailscale</strong>: Connects all devices to a private network. DNS CNAMEs point to the VM&#8217;s Tailscale hostname, so only tailnet devices can reach the services.</li>
<li><strong>Caddy in a VM</strong>: Terminates TLS using a private CA, then reverse-proxies each service.</li>
<li><strong>Proxmox host</strong>: Accessed over its own Tailscale IP.</li>
</ul>
<h2 id="the-setup">The Setup</h2>
<h3 id="tailscale">Tailscale</h3>
<p>You can download Tailscale <a href="https://tailscale.com/download">here</a>. Install it on the VM and any devices you want to access your services from.</p>
<h3 id="caddy--cloudflare">Caddy + Cloudflare</h3>
<p>You can download Caddy <a href="https://caddyserver.com/download">here</a>. Since I manage my DNS with Cloudflare, I also installed the <a href="https://caddyserver.com/docs/modules/dns.providers.cloudflare">Cloudflare DNS module</a>.</p>
<p>I installed Caddy as a systemd service on my self hosted VM. I added the following to my Caddyfile (<code>/etc/caddy/Caddyfile</code>):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-caddy" data-lang="caddy"><span style="display:flex;"><span>(cloudflare) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">tls</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">dns</span> <span style="color:#e6db74">cloudflare</span> <span style="color:#e6db74">CLOUDFLARE_API_TOKEN</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}<span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># Example of a reverse proxy for a service running on the same VM as Caddy
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>git.alexboden.ca {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">reverse_proxy</span> http://localhost:<span style="color:#ae81ff">3000</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">import</span> cloudflare
</span></span><span style="display:flex;"><span>}<span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># Example of a reverse proxy for a service in the same tailnet (with a different )
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>proxmox.alexboden.ca {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">reverse_proxy</span> https://100.109.111.101:<span style="color:#ae81ff">8006</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">transport</span> <span style="color:#e6db74">http</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">tls_insecure_skip_verify</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">header_up</span> <span style="color:#e6db74">Host</span> <span style="color:#ae81ff">{host}</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">header_up</span> <span style="color:#e6db74">X-Forwarded-Proto</span> <span style="color:#e6db74">https</span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">header_up</span> <span style="color:#e6db74">X-Forwarded-For</span> <span style="color:#ae81ff">{remote}</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">import</span> cloudflare
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Then reload Caddy:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>systemctl restart caddy
</span></span></code></pre></div><p>Add the following DNS records to Cloudflare:</p>
<table>
  <thead>
      <tr>
          <th>Type</th>
          <th>Name</th>
          <th>Content</th>
          <th>Proxy Status</th>
          <th>TTL</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>CNAME</td>
          <td>git.alexboden.ca</td>
          <td>vm-name.tailnet-name.ts.net</td>
          <td>DNS only</td>
          <td>Auto</td>
      </tr>
      <tr>
          <td>CNAME</td>
          <td>proxmox.alexboden.ca</td>
          <td>vm-name.tailnet-name.ts.net</td>
          <td>DNS only</td>
          <td>Auto</td>
      </tr>
  </tbody>
</table>
<p>You can find the VM name and tailnet name in the Tailscale dashboard.</p>
<h2 id="diagram-of-the-setup">Diagram of the setup</h2>
<div class="mermaid" id="mermaid-0">

flowchart TD
subgraph Cloudflare[Cloudflare DNS]
DNS1["git.alexboden.ca"]
DNS2["proxmox.alexboden.ca"]
end

    subgraph Tailnet[Tailscale Network]
        U["User Devices\n(Laptop, Phone)"]
        VM["Caddy (TLS Termination + Reverse Proxy)"]
        PVE["Proxmox Host (Bare Metal, Tailscale IP)"]
        Gitea["Gitea Service localhost:3000"]
    end

    %% Flows
    U -->|HTTPS Request git.alexboden.ca| DNS1
    U -->|HTTPS Request proxmox.alexboden.ca| DNS2

    DNS1 --> VM
    DNS2 --> VM

    VM -->|Local Reverse Proxy| Gitea
    VM -->|Reverse Proxy via Tailscale IP| PVE


</div>

]]></content:encoded>
    </item>
    <item>
      <title>Using Slurm to Run GitHub Actions</title>
      <link>https://cloud.watonomous.ca/blog/using-slurm-to-run-github-actions</link>
      <pubDate>Sun, 06 Apr 2025 00:00:00 +0000</pubDate>
      
      <guid>https://cloud.watonomous.ca/blog/using-slurm-to-run-github-actions</guid>
      <description>How we run GitHub Actions on Slurm at WATcloud</description>
      <content:encoded><![CDATA[<p>How WATcloud runs GitHub Actions on Slurm the workload manager.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
