{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "Alex's Blog",
  "home_page_url": "https://blog.alexboden.ca/",
  "feed_url": "https://blog.alexboden.ca/",
  "description": "Alex Boden - Computer Science Student at University of Waterloo",
  "language": "en-us",
  "items": [
    {
      "id": "https://blog.alexboden.ca/occam-claw/",
      "url": "https://blog.alexboden.ca/occam-claw/",
      "title": "You should make your own OpenClaw",
      "summary": "My opinion on why developers should make their own tools",
      "content_html": "\u003cp\u003ePeter Steinberger\u0026rsquo;s \u0026lsquo;Clawdbot\u0026rsquo; was a rough weekend hack designed for power-user \u0026lsquo;God Mode.\u0026rsquo; As it evolved into \u003ca href=\"https://openclaw.ai/\"\u003eOpenClaw\u003c/a\u003e, 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.\u003c/p\u003e\n\u003cp\u003eWe\u0026rsquo;ve seen developers and cloud providers alike flock to this project as it has skyrocketed in popularity. We have spinoffs like \u003ca href=\"https://github.com/HKUDS/nanobot\"\u003enanoclaw\u003c/a\u003e, \u003ca href=\"https://github.com/sipeed/picoclaw\"\u003epicoclaw\u003c/a\u003e and \u003ca href=\"https://github.com/HKUDS/nanobot\"\u003enanobot\u003c/a\u003e. We\u0026rsquo;ve also seen cloud providers offering to host this type of service such as \u003ca href=\"https://www.digitalocean.com/community/tutorials/how-to-run-openclaw\"\u003eDigital Ocean\u003c/a\u003e, \u003ca href=\"https://blog.cloudflare.com/moltworker-self-hosted-ai-agent/\"\u003eCloudflare\u003c/a\u003e and \u003ca href=\"https://www.hostinger.com/support/how-to-install-openclaw-on-hostinger-vps-template-installation/\"\u003eHostinger\u003c/a\u003e. There are even companies like \u003ca href=\"https://ai.com/\"\u003eai.com\u003c/a\u003e that are focused on this new \u0026ldquo;AI Assistant\u0026rdquo; paradigm.\u003c/p\u003e\n\u003cp\u003eThis 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\u0026rsquo;s ability to truly have it be their own. You can fork the repo to make it more \u0026ldquo;custom\u0026rdquo; but with over 10,000 commits and 500,000 lines of code it\u0026rsquo;s not exactly maintainable. Even the creator was \u003ca href=\"https://x.com/steipete/status/2023057089346580828?s=20\"\u003estruggling\u003c/a\u003e, merging over 600 commits and losing ground on open PRs. As I was writing this, Steinberger \u003ca href=\"https://steipete.me/posts/2026/openclaw\"\u003eannounced\u003c/a\u003e he\u0026rsquo;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.\u003c/p\u003e\n\u003cp\u003eWhile you will see OpenClaw marketed as secure due to self-hosting, the reality is that many \u0026lsquo;out of the box\u0026rsquo; setups leave administrative interfaces exposed. The \u003ca href=\"https://www.wiz.io/blog/exposed-moltbook-database-reveals-millions-of-api-keys\"\u003eMoltbook breach\u003c/a\u003e of 1.5 million API keys and the \u003ca href=\"https://www.reddit.com/r/hacking/comments/1r30t25/i_scanned_popular_openclaw_skills_heres_what_i/\"\u003eskills marketplace\u003c/a\u003e illustrate this point exactly. Although they do care about \u003ca href=\"https://openclaw.ai/blog/virustotal-partnership\"\u003esecurity\u003c/a\u003e, this project\u0026rsquo;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.\u003c/p\u003e\n\u003cp\u003eAll of this made me question if we really need all of these features. In philosophy, \u003ca href=\"https://en.wikipedia.org/wiki/Occam%27s_razor\"\u003eOccam\u0026rsquo;s razor\u003c/a\u003e is the theory that \u0026ldquo;recommends searching for explanations constructed with the smallest possible set of elements.\u0026rdquo; 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\u0026rsquo;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 \u003ca href=\"https://github.com/alexboden/occam-claw\"\u003eoccam-claw\u003c/a\u003e, an AI assistant that has only the features I need and none of the bloat. After building this, I realized the following benefits:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eIt\u0026rsquo;s easy to build (took a few hours) and it\u0026rsquo;s fun\u003c/li\u003e\n\u003cli\u003eYou can customize everything, you get everything you need and nothing you don\u0026rsquo;t\u003c/li\u003e\n\u003cli\u003eSmaller footprint on your host, both in resource usage and attack surface\u003c/li\u003e\n\u003cli\u003eYou can understand every line of code, which means you can actually reason about its security\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eI\u0026rsquo;m not saying you shouldn\u0026rsquo;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\u0026rsquo;s defaults.\u003c/p\u003e\n",
      "date_published": "2026-02-15T00:00:00+00:00",
      "tags": [
        "llm",
        "self-hosting"
      ]
    },
    {
      "id": "https://blog.alexboden.ca/chess-stats/",
      "url": "https://blog.alexboden.ca/chess-stats/",
      "title": "Tracking Your Chess Addiction",
      "summary": "Using the chess.com API to track time spent playing chess",
      "content_html": "\u003cp\u003e\u003ca href=\"https://www.chess.com\"\u003echess.com\u003c/a\u003e has a great \u003ca href=\"https://support.chess.com/en/articles/9650547-published-data-api\"\u003epublic API\u003c/a\u003e 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.\u003c/p\u003e\n\u003cp\u003eI created a simple website that aggregates this data by month so you can see how much time you\u0026rsquo;ve spent playing chess. Check it out at \u003ca href=\"https://chess-stats.alexboden.ca\"\u003echess-stats.alexboden.ca\u003c/a\u003e.\u003c/p\u003e\n",
      "date_published": "2025-12-28T00:00:00+00:00",
      "tags": [
        "chess"
      ]
    },
    {
      "id": "https://blog.alexboden.ca/toronto-bike-share-status/",
      "url": "https://blog.alexboden.ca/toronto-bike-share-status/",
      "title": "Toronto Bike Share Status",
      "summary": "Improving my daily commute with a custom dashboard and iOS widget for Toronto's Bike Share network",
      "content_html": "\u003cp\u003eI love Toronto’s Bike Share network, but often times the bike station closest to my place has no bikes or the place I\u0026rsquo;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.\u003c/p\u003e\n\u003cp\u003eCheck out the project on \u003ca href=\"https://github.com/alexboden/toronto-bike-share-status\"\u003eGitHub\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eCheck out the website \u003ca href=\"https://toronto-bike.alexboden.ca\"\u003ehere\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eWidget on iOS home screen:\n\n\n\n\n\n\n\u003cfigure class=\"blog-figure\"\u003e\n    \u003cimg src=\"/images/toronto-bike-share/widget.jpg\" alt=\"iOS Scriptable widget showing favorite stations.\" class=\"img-fluid\" style=\"max-width: 70%; width: 100%; \"\u003e\n    \n    \u003cfigcaption\u003eiOS Scriptable widget showing favorite stations.\u003c/figcaption\u003e\n    \n\u003c/figure\u003e \n\u003c/p\u003e\n",
      "date_published": "2025-10-24T00:00:00+00:00",
      "tags": [
        "just-for-fun"
      ]
    },
    {
      "id": "https://blog.alexboden.ca/self-hosting-securely/",
      "url": "https://blog.alexboden.ca/self-hosting-securely/",
      "title": "Self-hosting services on a custom domain securely",
      "summary": "How I securely self-host Gitea, Jellyfin, and Proxmox with Tailscale, Caddy, and Cloudflare",
      "content_html": "\u003ch2 id=\"my-setup\"\u003eMy Setup\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eProxmox\u003c/strong\u003e: Running on a bare metal server.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eBasic Linux VM\u003c/strong\u003e: Running services I would like to access securely.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eCustom domain\u003c/strong\u003e: I have the domain \u003ccode\u003ealexboden.ca\u003c/code\u003e with DNS records managed by Cloudflare.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"the-goal\"\u003eThe Goal\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003ePrivate by default:\u003c/strong\u003e Services should only be reachable from my devices.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eSingle URL per service:\u003c/strong\u003e e.g. \u003ccode\u003egit.alexboden.ca\u003c/code\u003e, \u003ccode\u003ejellyfin.alexboden.ca\u003c/code\u003e, \u003ccode\u003eproxmox.alexboden.ca\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eClean HTTPS:\u003c/strong\u003e No browser warnings, even on phones.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eNo public exposure:\u003c/strong\u003e No NAT forwards, no dangling ports.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"the-solution\"\u003eThe Solution\u003c/h2\u003e\n\u003cp\u003eI use \u003cstrong\u003eCaddy running inside a VM\u003c/strong\u003e as a single TLS termination point, with \u003cstrong\u003eTailscale\u003c/strong\u003e providing the private network:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eTailscale\u003c/strong\u003e: Connects all devices to a private network. DNS CNAMEs point to the VM\u0026rsquo;s Tailscale hostname, so only tailnet devices can reach the services.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eCaddy in a VM\u003c/strong\u003e: Terminates TLS using a private CA, then reverse-proxies each service.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eProxmox host\u003c/strong\u003e: Accessed over its own Tailscale IP.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"the-setup\"\u003eThe Setup\u003c/h2\u003e\n\u003ch3 id=\"tailscale\"\u003eTailscale\u003c/h3\u003e\n\u003cp\u003eYou can download Tailscale \u003ca href=\"https://tailscale.com/download\"\u003ehere\u003c/a\u003e. Install it on the VM and any devices you want to access your services from.\u003c/p\u003e\n\u003ch3 id=\"caddy--cloudflare\"\u003eCaddy + Cloudflare\u003c/h3\u003e\n\u003cp\u003eYou can download Caddy \u003ca href=\"https://caddyserver.com/download\"\u003ehere\u003c/a\u003e. Since I manage my DNS with Cloudflare, I also installed the \u003ca href=\"https://caddyserver.com/docs/modules/dns.providers.cloudflare\"\u003eCloudflare DNS module\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eI installed Caddy as a systemd service on my self hosted VM. I added the following to my Caddyfile (\u003ccode\u003e/etc/caddy/Caddyfile\u003c/code\u003e):\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-caddy\" data-lang=\"caddy\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e(cloudflare) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003etls\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003edns\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003ecloudflare\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003eCLOUDFLARE_API_TOKEN\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\u003cspan style=\"color:#75715e\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# Example of a reverse proxy for a service running on the same VM as Caddy\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003egit.alexboden.ca {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereverse_proxy\u003c/span\u003e http://localhost:\u003cspan style=\"color:#ae81ff\"\u003e3000\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e cloudflare\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\u003cspan style=\"color:#75715e\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# Example of a reverse proxy for a service in the same tailnet (with a different )\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003eproxmox.alexboden.ca {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereverse_proxy\u003c/span\u003e https://100.109.111.101:\u003cspan style=\"color:#ae81ff\"\u003e8006\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003etransport\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003ehttp\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003etls_insecure_skip_verify\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eheader_up\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003eHost\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e{host}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eheader_up\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003eX-Forwarded-Proto\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003ehttps\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eheader_up\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003eX-Forwarded-For\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e{remote}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eimport\u003c/span\u003e cloudflare\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThen reload Caddy:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esystemctl restart caddy\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAdd the following DNS records to Cloudflare:\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003eType\u003c/th\u003e\n          \u003cth\u003eName\u003c/th\u003e\n          \u003cth\u003eContent\u003c/th\u003e\n          \u003cth\u003eProxy Status\u003c/th\u003e\n          \u003cth\u003eTTL\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eCNAME\u003c/td\u003e\n          \u003ctd\u003egit.alexboden.ca\u003c/td\u003e\n          \u003ctd\u003evm-name.tailnet-name.ts.net\u003c/td\u003e\n          \u003ctd\u003eDNS only\u003c/td\u003e\n          \u003ctd\u003eAuto\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003eCNAME\u003c/td\u003e\n          \u003ctd\u003eproxmox.alexboden.ca\u003c/td\u003e\n          \u003ctd\u003evm-name.tailnet-name.ts.net\u003c/td\u003e\n          \u003ctd\u003eDNS only\u003c/td\u003e\n          \u003ctd\u003eAuto\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eYou can find the VM name and tailnet name in the Tailscale dashboard.\u003c/p\u003e\n\u003ch2 id=\"diagram-of-the-setup\"\u003eDiagram of the setup\u003c/h2\u003e\n\u003cdiv class=\"mermaid\" id=\"mermaid-0\"\u003e\n\nflowchart TD\nsubgraph Cloudflare[Cloudflare DNS]\nDNS1[\"git.alexboden.ca\"]\nDNS2[\"proxmox.alexboden.ca\"]\nend\n\n    subgraph Tailnet[Tailscale Network]\n        U[\"User Devices\\n(Laptop, Phone)\"]\n        VM[\"Caddy (TLS Termination + Reverse Proxy)\"]\n        PVE[\"Proxmox Host (Bare Metal, Tailscale IP)\"]\n        Gitea[\"Gitea Service localhost:3000\"]\n    end\n\n    %% Flows\n    U --\u003e|HTTPS Request git.alexboden.ca| DNS1\n    U --\u003e|HTTPS Request proxmox.alexboden.ca| DNS2\n\n    DNS1 --\u003e VM\n    DNS2 --\u003e VM\n\n    VM --\u003e|Local Reverse Proxy| Gitea\n    VM --\u003e|Reverse Proxy via Tailscale IP| PVE\n\n\n\u003c/div\u003e\n\n",
      "date_published": "2025-09-23T00:00:00+00:00",
      "tags": [
        "self-hosting",
        "networking"
      ]
    },
    {
      "id": "https://cloud.watonomous.ca/blog/using-slurm-to-run-github-actions",
      "url": "https://cloud.watonomous.ca/blog/using-slurm-to-run-github-actions",
      "title": "Using Slurm to Run GitHub Actions",
      "summary": "How we run GitHub Actions on Slurm at WATcloud",
      "content_html": "\u003cp\u003eHow WATcloud runs GitHub Actions on Slurm the workload manager.\u003c/p\u003e\n",
      "date_published": "2025-04-06T00:00:00+00:00",
      "tags": [
        "watcloud",
        "github-actions"
      ]
    }
  ]
}
