<?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>http://localhost:1313/</link>
    <description>Alex Boden - Computer Science Student at University of Waterloo</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Fri, 24 Oct 2025 00:00:00 +0000</lastBuildDate>
    <atom:link href="http://localhost:1313/" rel="self" type="application/rss+xml" />
    <item>
      <title>Toronto Bike Share Status</title>
      <link>http://localhost:1313/toronto-bike-share-status/</link>
      <pubDate>Fri, 24 Oct 2025 00:00:00 +0000</pubDate>
      
      <guid>http://localhost:1313/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[&lt;p&gt;I love Toronto’s Bike Share network, but often times the bike station closest to my place has no bikes or the place I&amp;rsquo;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 a multiple hours to make a simple dashboard and iOS widget that surfaces the info I need at a glance.&lt;/p&gt;
&lt;p&gt;Check out the project on &lt;a href=&#34;https://github.com/alexboden/toronto-bike-share-status&#34;&gt;GitHub&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here are some screenshots of the dashboard and widget in action:






&lt;figure class=&#34;blog-figure&#34;&gt;
    &lt;img src=&#34;http://localhost:1313/images/toronto-bike-share/website.png&#34; alt=&#34;Toronto Bike Share — dashboard overview.&#34; class=&#34;img-fluid&#34; style=&#34;max-width: 70%; width: 100%; &#34;&gt;
    
    &lt;figcaption&gt;Toronto Bike Share — dashboard overview.&lt;/figcaption&gt;
    
&lt;/figure&gt; 
&lt;/p&gt;






&lt;figure class=&#34;blog-figure&#34;&gt;
    &lt;img src=&#34;http://localhost:1313/images/toronto-bike-share/widget.jpg&#34; alt=&#34;iOS Scriptable widget showing favorite stations.&#34; class=&#34;img-fluid&#34; style=&#34;max-width: 70%; width: 100%; &#34;&gt;
    
    &lt;figcaption&gt;iOS Scriptable widget showing favorite stations.&lt;/figcaption&gt;
    
&lt;/figure&gt; 

]]></content:encoded>
    </item>
    <item>
      <title>Self-hosting services on a custom domain securely</title>
      <link>http://localhost:1313/self-hosting-securely/</link>
      <pubDate>Tue, 23 Sep 2025 00:00:00 +0000</pubDate>
      
      <guid>http://localhost:1313/self-hosting-securely/</guid>
      <description>How I securely self-host Gitea, Jellyfin, and Proxmox with Tailscale, Cloudflare, and Caddy</description>
      <content:encoded><![CDATA[&lt;h2 id=&#34;my-setup&#34;&gt;My Setup&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Proxmox&lt;/strong&gt;: Running on a bare metal server.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Basic Linux VM&lt;/strong&gt;: Running services I would like to access securely.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom domain&lt;/strong&gt;: I have the domain &lt;code&gt;alexboden.ca&lt;/code&gt; with DNS records managed by Cloudflare.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;the-goal&#34;&gt;The Goal&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Private by default:&lt;/strong&gt; Services should only be reachable from my devices.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Single URL per service:&lt;/strong&gt; e.g. &lt;code&gt;git.alexboden.ca&lt;/code&gt;, &lt;code&gt;jellyfin.alexboden.ca&lt;/code&gt;, &lt;code&gt;proxmox.alexboden.ca&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clean HTTPS:&lt;/strong&gt; No browser warnings, even on phones.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No public exposure:&lt;/strong&gt; No NAT forwards, no dangling ports.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;the-solution&#34;&gt;The Solution&lt;/h2&gt;
&lt;p&gt;I combined &lt;strong&gt;Tailscale Serve&lt;/strong&gt; with &lt;strong&gt;Caddy running inside a VM&lt;/strong&gt; as a single TLS termination point:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tailscale Serve&lt;/strong&gt;: Accepts incoming HTTPS traffic on port 443, does raw TCP passthrough.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Caddy in a VM&lt;/strong&gt;: Terminates TLS using a private CA, then reverse-proxies each service.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Proxmox host&lt;/strong&gt;: Accessed over its own Tailscale IP.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;the-setup&#34;&gt;The Setup&lt;/h2&gt;
&lt;h3 id=&#34;tailscale&#34;&gt;Tailscale:&lt;/h3&gt;
&lt;p&gt;You can download tailscale &lt;a href=&#34;https://tailscale.com/download&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo tailscale serve reset
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo tailscale serve --bg --tcp&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;443&lt;/span&gt; tcp://localhost:443
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This allows incoming traffic on port 443, the standard port for HTTPS.&lt;/p&gt;
&lt;h3 id=&#34;caddy--cloudflare&#34;&gt;Caddy + Cloudflare&lt;/h3&gt;
&lt;p&gt;You can download Caddy &lt;a href=&#34;https://caddyserver.com/download&#34;&gt;here&lt;/a&gt;. Since I manage my DNS with Cloudflare, I also installed the &lt;a href=&#34;https://caddyserver.com/docs/modules/dns.providers.cloudflare&#34;&gt;Cloudflare DNS module&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I installed Caddy as a systemd service on my self hosted VM. I added the following to my Caddyfile (&lt;code&gt;/etc/caddy/Caddyfile&lt;/code&gt;):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-caddy&#34; data-lang=&#34;caddy&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;(cloudflare) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;tls&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;dns&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;cloudflare&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;CLOUDFLARE_API_TOKEN&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#75715e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Example of a reverse proxy for a service running on the same VM as Caddy
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;git.alexboden.ca {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;reverse_proxy&lt;/span&gt; http://localhost:&lt;span style=&#34;color:#ae81ff&#34;&gt;3000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;import&lt;/span&gt; cloudflare
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;span style=&#34;color:#75715e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Example of a reverse proxy for a service in the same tailnet (with a different )
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;proxmox.alexboden.ca {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;reverse_proxy&lt;/span&gt; https://100.109.111.101:&lt;span style=&#34;color:#ae81ff&#34;&gt;8006&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;transport&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;http&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;tls_insecure_skip_verify&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;header_up&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;Host&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;{host}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;header_up&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;https&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;header_up&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;X-Forwarded-For&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;{remote}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;import&lt;/span&gt; cloudflare
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then reload Caddy:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;systemctl restart caddy
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Add the following DNS records to Cloudflare:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Type&lt;/th&gt;
          &lt;th&gt;Name&lt;/th&gt;
          &lt;th&gt;Content&lt;/th&gt;
          &lt;th&gt;Proxy Status&lt;/th&gt;
          &lt;th&gt;TTL&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;CNAME&lt;/td&gt;
          &lt;td&gt;git.alexboden.ca&lt;/td&gt;
          &lt;td&gt;vm-name.tailnet-name.ts.net&lt;/td&gt;
          &lt;td&gt;DNS only&lt;/td&gt;
          &lt;td&gt;Auto&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;CNAME&lt;/td&gt;
          &lt;td&gt;proxmox.alexboden.ca&lt;/td&gt;
          &lt;td&gt;vm-name.tailnet-name.ts.net&lt;/td&gt;
          &lt;td&gt;DNS only&lt;/td&gt;
          &lt;td&gt;Auto&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;You can find the VM name and tailnet name in the Tailscale dashboard.&lt;/p&gt;
&lt;h2 id=&#34;diagram-of-the-setup&#34;&gt;Diagram of the setup&lt;/h2&gt;
&lt;div class=&#34;mermaid&#34; id=&#34;mermaid-0&#34;&gt;

flowchart TD
subgraph Cloudflare[Cloudflare DNS]
DNS1[&#34;git.alexboden.ca&#34;]
DNS2[&#34;proxmox.alexboden.ca&#34;]
end

    subgraph Tailnet[Tailscale Network]
        U[&#34;User Devices\n(Laptop, Phone)&#34;]
        TS[&#34;Tailscale Serve (TCP:443 passthrough)&#34;]
        VM[&#34;Caddy (TLS Termination + Reverse Proxy)&#34;]
        PVE[&#34;Proxmox Host (Bare Metal, Tailscale IP)&#34;]
        Gitea[&#34;Gitea Service localhost:3000&#34;]
    end

    %% Flows
    U --&gt;|HTTPS Request git.alexboden.ca| DNS1
    U --&gt;|HTTPS Request proxmox.alexboden.ca| DNS2

    DNS1 --&gt; TS
    DNS2 --&gt; TS

    TS --&gt; VM

    VM --&gt;|Local Reverse Proxy| Gitea
    VM --&gt;|Reverse Proxy via Tailscale IP| PVE


&lt;/div&gt;

]]></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[&lt;p&gt;How WATcloud runs GitHub Actions on Slurm the workload manager.&lt;/p&gt;
]]></content:encoded>
    </item>
    <item>
      <title>Chess Stats: Tracking Your Chess.com Playing Time</title>
      <link>http://localhost:1313/chess-stats/</link>
      <pubDate>Mon, 27 Jan 2025 00:00:00 +0000</pubDate>
      
      <guid>http://localhost:1313/chess-stats/</guid>
      <description>A simple web tool to see how much time you&#39;ve spent playing chess on Chess.com</description>
      <content:encoded><![CDATA[&lt;p&gt;I&amp;rsquo;ve been playing chess on Chess.com for a while, and I&amp;rsquo;ve always been curious about how much time I&amp;rsquo;ve actually spent playing. The platform shows you game counts, ratings, and win/loss records, but it doesn&amp;rsquo;t surface the total time investment in a clear way.&lt;/p&gt;
&lt;p&gt;To satisfy this curiosity, I built &lt;a href=&#34;https://chess-stats.alexboden.ca/&#34;&gt;Chess Stats&lt;/a&gt; — a simple web tool that fetches your Chess.com game history and calculates how much time you&amp;rsquo;ve spent playing.&lt;/p&gt;
&lt;h2 id=&#34;how-it-works&#34;&gt;How It Works&lt;/h2&gt;
&lt;p&gt;Enter your Chess.com username, and the tool will:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fetch your complete game history from the Chess.com API&lt;/li&gt;
&lt;li&gt;Calculate the total time spent based on game time controls&lt;/li&gt;
&lt;li&gt;Display your playing statistics in an easy-to-read format&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&amp;rsquo;s a straightforward way to see your chess time investment at a glance. Whether you&amp;rsquo;re curious about your total hours played or want to track your activity over time, Chess Stats makes it easy to get those insights.&lt;/p&gt;
&lt;p&gt;Check it out at &lt;a href=&#34;https://chess-stats.alexboden.ca/&#34;&gt;chess-stats.alexboden.ca&lt;/a&gt;&lt;/p&gt;
]]></content:encoded>
    </item>
  </channel>
</rss>
