<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Clinton Boys</title><link>https://clintonboys.com/project-types/version-control/</link><description>Recent content in Version Control on Clinton Boys</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Sun, 15 Feb 2026 03:52:30 -0500</lastBuildDate><atom:link href="https://clintonboys.com/project-types/version-control/index.xml" rel="self" type="application/rss+xml"/><item><title>Building an OPNsense router</title><link>https://clintonboys.com/projects/homelab/04-router/</link><pubDate>Sun, 19 Apr 2026 00:00:00 +0000</pubDate><guid>https://clintonboys.com/projects/homelab/04-router/</guid><description>&lt;h1 id="building-an-opnsense-router">&lt;/h>Building an OPNsense router&lt;a href="#building-an-opnsense-router">
&lt;/a>
&lt;/h1>&lt;p>&lt;em>April 19, 2026&lt;/em>&lt;/p>
&lt;p>The first thing to understand when building your own router is what is usually meant by the word “router”. Your ISP will give you a (usually white) box with blinking lights which they call a router but which usually has several functions in the same package, all typically with a very low level of configurability:&lt;/p>
&lt;ul>
&lt;li>A &amp;ldquo;gateway&amp;rdquo;, which sits between the wider internet (&lt;a href="https://en.wikipedia.org/wiki/Wide_area_network" target="_blank" rel="noopener">WAN&lt;/a>) and your LAN, hands out IP addresses to your local devices via DHCP, and handles network address translation (&lt;a href="https://en.wikipedia.org/wiki/Network_address_translation" target="_blank" rel="noopener">NAT&lt;/a>), the process that brokers between external IPs in the public internet and local ones in your LAN. More advanced gateways will also let you do things like tunnel all traffic through a VPN.&lt;/li>
&lt;li>Basic &amp;ldquo;firewall-like&amp;rdquo; functions: blocking potentially harmful traffic and basic content filtering.&lt;/li>
&lt;li>A Wi-Fi access point&lt;/li>
&lt;li>A switch, allowing other devices to connect to it directly by Ethernet&lt;/li>
&lt;/ul>
&lt;p>In previous posts I wrote about how I decoupled the &lt;a href="https://clintonboys.com/projects/homelab/02-wifi/">Wi-Fi functionality&lt;/a> and split the &lt;a href="https://clintonboys.com/projects/homelab/03-network/">network topology&lt;/a> up with a bunch of switches and a trunk line between my house and office. My goal was to also have a router which would allow me to properly do all these more advanced networking things I had started to hear about, and to properly support the 10Gbps and VLAN functionality I had built into the network.&lt;/p>
&lt;h2 id="router-on-a-stick">&lt;/h>Router on a stick&lt;a href="#router-on-a-stick">
&lt;/a>
&lt;/h2>&lt;p>In the previous &lt;a href="https://clintonboys.com/projects/homelab/03-network/">post&lt;/a> I wrote about the network topology I decided on, which called for a &amp;ldquo;router on a stick&amp;rdquo; in my home office. This means the router is connected to a switch somewhere in the network like any other device, not necessarily at the &amp;ldquo;bottle neck&amp;rdquo; point (i.e. directly connected to the fibre modem) where it would be in most consumer setups. The network is then segmented using VLANs in a way that traffic flows through this router in both directions on its way to and from the WAN or between LAN devices.&lt;/p>
&lt;p>I wrote a lot more detail about VLANs in the previous &lt;a href="https://clintonboys.com/projects/homelab/03-network/">post&lt;/a>, so you can read about my decisions there. From the router&amp;rsquo;s perspective, it just had to support VLAN tagging to be able to support the network topology I had built out. Most consumer routers don&amp;rsquo;t, but from what I read it seemed like a very basic feature of any router&amp;rsquo;s operating system once you go one step beyond consumer.&lt;/p>
&lt;h2 id="opnsense">&lt;/h>OPNsense&lt;a href="#opnsense">
&lt;/a>
&lt;/h2>&lt;p>So I spent some time researching what it would mean to &amp;ldquo;level up&amp;rdquo; from my ISP&amp;rsquo;s router to something better. There&amp;rsquo;s a few options, and the simplest is to buy a dedicated &amp;ldquo;routing appliance&amp;rdquo; or &amp;ldquo;gateway&amp;rdquo; like the UniFi Cloud Gateway series from &lt;a href="https://ui.com/cloud-gateways" target="_blank" rel="noopener">Ubiquiti&lt;/a>. These are solid options, they are very plug and play and give an Apple-style &amp;ldquo;it just works&amp;rdquo; experience. They are painfully expensive however.&lt;/p>
&lt;p>I read a lot about &lt;a href="https://en.wikipedia.org/wiki/OPNsense" target="_blank" rel="noopener">OPNsense&lt;/a> in various places. It&amp;rsquo;s open source, based on the venerable &lt;a href="https://en.wikipedia.org/wiki/FreeBSD" target="_blank" rel="noopener">FreeBSD&lt;/a> Unix-like OS, and has basically every feature you could possibly ask for when configuring your network. It is itself a fork of an older project, pfSense, which is also still in active development and it seems like there is quite a split in the homelab community regarding which is better. I went with OPNsense in the end because I read a few tutorials from folks I trusted who seemed to think it was the way to go.&lt;/p>
&lt;p>OPNsense will give us a very powerful setup allowing us full control over NAT, DNS, DHCP, VLANs, firewall rules, VPNs and a lot of other things, including extremely detailed monitoring of all traffic on the network. If those things are all meaningless to you, they were mostly meaningless to me as well before I started on this journey, so hopefully I&amp;rsquo;ll be able to explain it all below!&lt;/p>
&lt;h2 id="hardware">&lt;/h>Hardware&lt;a href="#hardware">
&lt;/a>
&lt;/h2>&lt;p>First, I had to decide what hardware I would use for my router. Ultimately, a router is just a computer with a very specific use case, so you can really use any computer as a router. Most ISP-provided routers are very low-powered computers with some whitelabelled, detoothed Linux operating system on them.&lt;/p>
&lt;p>You can buy a small form-factor computer like a &lt;a href="https://en.wikipedia.org/wiki/Next_Unit_of_Computing" target="_blank" rel="noopener">NUC&lt;/a>, or a dedicated &amp;ldquo;routing appliance&amp;rdquo; like the ones from &lt;a href="https://eu.protectli.com" target="_blank" rel="noopener">Protectli&lt;/a>, but the easiest way is just to buy an old, refurbished enterprise desktop computer, preferably in a small form factor, and then install OPNsense on it.&lt;/p>
&lt;p>The hardware I ultimately settled on was a &lt;a href="https://dl.dell.com/topicspdf/optiplex-3060-desktop_owners-manual4_en-us.pdf" target="_blank" rel="noopener">Dell OptiPlex 3060&lt;/a> in the &amp;ldquo;small form factor&amp;rdquo; (SFF). This is significantly smaller than a &amp;ldquo;full-size&amp;rdquo; tower desktop, but bigger than the &amp;ldquo;micro form factor&amp;rdquo; computers, which are too small to take PCIe cards, and I wanted to install a 10GbE network interface card (NIC).&lt;/p>
&lt;p>&lt;strong>Specs&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>Intel Core i5-8400 (6-core, 8th gen)&lt;/li>
&lt;li>16GB DDR4 RAM (overkill)&lt;/li>
&lt;li>256GB NVMe&lt;/li>
&lt;li>Intel X540-T2 dual 10GbE NIC&lt;/li>
&lt;/ul>
&lt;p>The first NIC I bought didn&amp;rsquo;t work. I bought a new one from Amazon but it turned out to be some cheap Chinese imitation one and not a genuine Intel one (I was buying a lot of parts at the same time so I was a bit distracted), so the Dell didn&amp;rsquo;t recognise it when I installed it. I just returned it and ended up buying a used genuine Intel one for less.&lt;/p>
&lt;p>Putting the card in was very straightforward, and once it was in it was just a matter of turning the machine on and installing OPNsense.&lt;/p>
&lt;p>Using refurbished consumer hardware feels a bit uncomfortable: how long is it going to last? Thankfully there&amp;rsquo;s an easy way around this: you just change your mindset and treat your router like it&amp;rsquo;s a commodity which could die at any moment and plan for that contingency. OPNsense lets you easily download the full configuration as an XML file, so if the machine does die you can just swap it out for a new one, install OPNsense, upload the XML file and you are ready to go. So the bottleneck really becomes the time it would take to order and receive a new machine and NIC, meaning one day I might be happy I kept my ISP’s router in the cupboard instead of ceremonially smashing it to pieces&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>.&lt;/p>
&lt;h2 id="installation">&lt;/h>Installation&lt;a href="#installation">
&lt;/a>
&lt;/h2>&lt;p>The actual installation of the OPNsense operating system is straightforward:&lt;/p>
&lt;ol>
&lt;li>Download the installer ISO from the &lt;a href="https://opnsense.org/download/" target="_blank" rel="noopener">OPNsense&lt;/a> page.&lt;/li>
&lt;li>Flash to a USB drive on MacOS (I used &lt;a href="https://en.wikipedia.org/wiki/Dd_%28Unix%29" target="_blank" rel="noopener">dd&lt;/a> which seems to be the standard way to do it if you&amp;rsquo;re comfortable with the terminal)&lt;/li>
&lt;li>Boot (hold F12 while booting to boot from the USB drive) and follow prompts&lt;/li>
&lt;li>Assign interfaces (WAN/LAN)&lt;/li>
&lt;/ol>
&lt;p>The only gotcha I encountered was needing to disable &lt;a href="https://en.wikipedia.org/wiki/UEFI#Secure_Boot" target="_blank" rel="noopener">Secure Boot&lt;/a> in the BIOS before installing. Secure Boot only allows bootloaders signed by a &amp;ldquo;recognised authority&amp;rdquo; to run, and FreeBSD&amp;rsquo;s, and by extension OPNsense&amp;rsquo;s, bootloader isn&amp;rsquo;t signed. Some major Linux distros (Fedora, Debian, Ubuntu, etc) get their bootloaders signed, but FreeBSD didn&amp;rsquo;t (a combination of open source resources and philosophical resistance to Microsoft&amp;rsquo;s control of a critical piece of security gatekeeping), so the machine will just refuse to boot the installer if Secure Boot is toggled on in the BIOS. Essentially you&amp;rsquo;re trading one security mechanism for another: firmware-level boot verification for an OS you chose and fully control. For a homelab router I think this is a perfectly reasonable tradeoff.&lt;/p>
&lt;h2 id="vlans">&lt;/h>VLANs&lt;a href="#vlans">
&lt;/a>
&lt;/h2>&lt;p>The first thing I needed to do was configure the VLANs. Since I&amp;rsquo;d set them up on the switches, nothing would work until the router was set up to be compatible with them. The router config is quite a lot simpler than the switches, and I didn&amp;rsquo;t run into anywhere near as many issues as I did when configuring the switches. Basically you just define virtual &amp;ldquo;interfaces&amp;rdquo; for each VLAN, and give the router a dedicated IP at each interface (e.g. &lt;code>192.168.20.1&lt;/code> on the &amp;ldquo;trusted LAN&amp;rdquo; VLAN which has ID 20).&lt;/p>
&lt;h2 id="dns">&lt;/h>DNS&lt;a href="#dns">
&lt;/a>
&lt;/h2>&lt;p>The &lt;a href="https://en.wikipedia.org/wiki/Domain_Name_System" target="_blank" rel="noopener">Domain Name System&lt;/a> (DNS) is the way that IP addresses get translated back and forth to URLs. There are global DNS servers, which are what allow you type &lt;code>google.com&lt;/code> in your browser address bar and then forward you to the IP address of Google&amp;rsquo;s servers, like a giant global phone book. In your LAN, you can also use DNS to do similar things, so instead of remembering that your Plex server is located at &lt;code>192.168.20.15:32400&lt;/code> you can just type &lt;code>plex.local&lt;/code> into your address bar.&lt;/p>
&lt;p>Setting this up requires three things:&lt;/p>
&lt;ul>
&lt;li>Decide what your domain will be. If you’re just going to access it from home, it can be pretty much anything so long as it doesn’t conflict with global DNS. If you want to be able to access your stuff at the same address locally and remotely, like I did, you’ll need to register a domain and then use that here. Later I will explain how to set up the external access to the same domain.&lt;/li>
&lt;li>Setting up your DNS overrides in OPNsense. This is a list of things you would like the DNS to handle, and you tell the router to send them all to your reverse proxy (see next point).&lt;/li>
&lt;/ul>
&lt;div class="diagram-container" style="text-align:center;margin:1.5rem 0;">
&lt;img src="https://clintonboys.com/projects/homelab/04-router/dns.png" alt="Home network topology" class="diagram" loading="lazy">
&lt;/div>
&lt;ul>
&lt;li>A reverse proxy, like NPM, which stores the explicit mapping between domain and IP:port, as well as some other technical stuff like SSL certificates.&lt;/li>
&lt;/ul>
&lt;div class="diagram-container" style="text-align:center;margin:1.5rem 0;">
&lt;img src="https://clintonboys.com/projects/homelab/04-router/npm.png" alt="Home network topology" class="diagram" loading="lazy">
&lt;/div>
&lt;h2 id="dhcp">&lt;/h>DHCP&lt;a href="#dhcp">
&lt;/a>
&lt;/h2>&lt;p>Dynamic Host Configuration Protocol (&lt;a href="https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol" target="_blank" rel="noopener">DHCP&lt;/a>) is a server hosted by your router that hands out IP address to devices that connect to it. The same sort of server sits on your ISP&amp;rsquo;s equipment in one of their networking centres and hands out a public IP to your modem.&lt;/p>
&lt;p>When connecting your most commonly used devices, like always-on servers, entertainment devices, you may want to configure them with static IPs, meaning they will always be found at the same address whenever they connect. But you don&amp;rsquo;t want to do this for every single device that needs to connect to the internet - phones, laptops, internet-connected fridges. So DHCP will just give them one dynamically, and in a configurable router OS like OPNsense, you can configure the range of IPs that different interfaces can hand out over DHCP. This lets you set up a very nice correspondence between addresses on a specific subnet, e.g. the range &lt;code>192.168.20.100&lt;/code> to &lt;code>192.168.20.200&lt;/code>, and the VLAN with ID &lt;code>20&lt;/code>. You can then use &lt;code>192.168.20.1&lt;/code> to &lt;code>192.168.20.99&lt;/code> for the static IPs in that VLAN.&lt;/p>
&lt;h2 id="firewall">&lt;/h>Firewall&lt;a href="#firewall">
&lt;/a>
&lt;/h2>&lt;p>Once you have the VLANs and DHCP set up, you&amp;rsquo;ll want to set up firewall rules that actually &amp;ldquo;implement&amp;rdquo; what you had in mind for the VLANs. For me, my &lt;code>20&lt;/code> VLAN was a &amp;ldquo;trusted LAN&amp;rdquo;, so I wanted to exclude access to it from all my other VLANs (guest, IoT). The IoT VLAN (&lt;code>30&lt;/code>) can only access the internet and not any of the other VLANs. And the &amp;ldquo;DMZ&amp;rdquo; VLAN (&lt;code>50&lt;/code>) is also restricted to only access the internet. The AI DMZ (&lt;code>60&lt;/code>) has a few additional restrictions: I want to be able to choose the specific websites on the internet it can access, to really make sure any agent I &amp;ldquo;set loose&amp;rdquo; there is tightly controlled by me. I also plan to use &lt;code>80&lt;/code> and &lt;code>81&lt;/code> (and maybe other &lt;code>8x&lt;/code> VLANs) in the future for VPNs.&lt;/p>
&lt;div class="diagram-container" style="text-align:center;margin:1.5rem 0;">
&lt;img src="https://clintonboys.com/projects/homelab/04-router/dmz.png" alt="Home network topology" class="diagram" loading="lazy">
&lt;/div>
&lt;h2 id="vpn">&lt;/h>VPN&lt;a href="#vpn">
&lt;/a>
&lt;/h2>&lt;p>One of the things I was most excited about with OPNsense was the ability to tunnel traffic through a VPN at the router level. I have a PrivateVPN subscription with endpoints in countries I used to live in, and I wanted to be able to route my Apple TV through one of them on demand so I could access that sweet geo-restricted homesickness-inspiring content.&lt;/p>
&lt;p>OPNsense has &lt;a href="https://en.wikipedia.org/wiki/WireGuard" target="_blank" rel="noopener">WireGuard&lt;/a> built in (it used to be a plugin but it&amp;rsquo;s been part of the base system since 25.x). Setting up a tunnel is straightforward: you create a &amp;ldquo;local&amp;rdquo; WireGuard instance with your client private key and tunnel address, then add an &amp;ldquo;endpoint&amp;rdquo; with the server&amp;rsquo;s public key, address, and port. Do this for each country you want to connect to, enable WireGuard, and you have your tunnels.&lt;/p>
&lt;p>My first attempt immediately broke the internet for every device on the network. The problem was AllowedIPs = 0.0.0.0/0 on the endpoint, which tells OPNsense to route all traffic through the VPN tunnel, not just the Apple TV&amp;rsquo;s. The fix is to tick &amp;ldquo;Disable Routes&amp;rdquo; on the local WireGuard instance so it doesn&amp;rsquo;t override the default route, and then use policy-based routing to selectively send traffic through it.&lt;/p>
&lt;p>I initially set this up with a firewall rule that matched the Apple TV&amp;rsquo;s source IP and routed it through the VPN gateway. This worked, but toggling between VPN countries — or turning VPN off entirely — meant logging into OPNsense and fiddling with firewall rules, which is not something I want to be doing every time I sit down to watch something.&lt;/p>
&lt;p>The solution I eventually want to implement is to give each VPN its own VLAN: for example in my case 80 for Sydney and 81 for Tel Aviv. Each VLAN will get its own interface, DHCP range, and firewall rules that force all traffic through the corresponding WireGuard gateway. On the physical switch then, I will be able to set up three ports as access ports: one on VLAN 20 (my regular LAN with no VPN), one on 80, one on 81, and patch them to labelled ports on my patch panel. Then toggling VPN is a physical action: I move one patch cable on the front of the panel. No logging in, no firewall rules to toggle, just move the cable and the Apple TV picks up a new IP on the right VLAN.&lt;/p>
&lt;h2 id="removing-my-isp-router">&lt;/h>Removing my ISP router&lt;a href="#removing-my-isp-router">
&lt;/a>
&lt;/h2>&lt;p>I had done all this with my ISP router still plugged in and sitting in its original place between the ONT and one of my new switches.
But with OPNsense handling routing, NAT, DHCP, DNS, and firewall, the ISP router is now redundant. With it still in the path, you end up with &amp;ldquo;double NAT&amp;rdquo;: the ISP router translates your traffic once, then OPNsense translates it again.&lt;/p>
&lt;div class="diagram-container" style="text-align:center;margin:1.5rem 0;">
&lt;img src="https://clintonboys.com/projects/homelab/04-router/nat.png" alt="Home network topology" class="diagram" loading="lazy">
&lt;/div>
&lt;p>In theory once everything is set up, you should be able to just remove your ISP router and plug the ONT directly into the switch. In practice, I encountered two problems. First, the ONT caches the MAC address of whatever&amp;rsquo;s plugged into it, so after swapping cables you need to power cycle the ONT and wait a minute or two for it to come back up and recognise the new device. Second, OPNsense itself can hold stale DHCP leases from the old connection — if you&amp;rsquo;re still seeing a &lt;code>192.168.x.x&lt;/code> address on the WAN interface after the swap (instead of the public IP from your ISP), releasing and renewing the lease (or flushing the lease file from the shell) should sort it out.&lt;/p>
&lt;p>Once it&amp;rsquo;s done, you should see a public IP on OPNsense&amp;rsquo;s WAN interface, single NAT, and full control over your network edge.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>Don’t do this of course, you are contractually obliged to return your ISP’s equipment at the end of your contract.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>lit: Version control with good vibes</title><link>https://clintonboys.com/projects/lit/</link><pubDate>Sun, 15 Feb 2026 03:52:30 -0500</pubDate><guid>https://clintonboys.com/projects/lit/</guid><description>&lt;h1 id="lit">&lt;/h>&lt;code>lit&lt;/code>&lt;a href="#lit">
&lt;/a>
&lt;/h1>&lt;p>&lt;em>February 15, 2026&lt;/em>&lt;/p>
&lt;p>Like everyone else with a keyboard and an anxious interest in their future as a programmer, I&amp;rsquo;ve been using AI agents to write a lot of code recently. You talk to Claude or Cursor and code appears and it mostly works, you fiddle with it a bit to get it right, and you push it. I have mixed feelings about the results, about what it means for the future, and most importantly for how it makes me &lt;em>feel&lt;/em>, but this post isn&amp;rsquo;t yet another one in that vein.&lt;/p>
&lt;p>Using agents means things are getting faster and crazier on the code &lt;em>generation&lt;/em> side, but on the code &lt;em>review&lt;/em> side, the bottlenecks preventing this generated code from actually making it to production, seem to be largely intact. Even though everyone (including me &lt;a href="https://clintonboys.com/posts/how-will-llms-take-our-jobs/">last year&lt;/a>) seems to like the analogy of LLMs being to compiled programming languages what compiled languages were to assembly code, there isn&amp;rsquo;t any tooling in place to actually make this happen.&lt;/p>
&lt;p>People are not &lt;em>accountable&lt;/em> for the code they generate. Their &lt;em>intent&lt;/em> when generating the code is not recorded, and is lost in the vibes. It&amp;rsquo;s a particular shame because the natural language prompts encode this intent quite well: well enough for the LLM to combine it with its training set and produce the code for you.&lt;/p>
&lt;p>So I had the idea for a tool, or maybe it&amp;rsquo;s better to think of it as a &amp;ldquo;thought experiment&amp;rdquo; because the workflows are still not entirely clear to me yet. It&amp;rsquo;s heavily inspired by git, and it tries to make this &amp;ldquo;the prompts are the source of truth, the code is just an artifact&amp;rdquo; thing a reality. Whether I actually &lt;em>want&lt;/em> this reality, or whether it just bringing us closer to an apocalypse of meaning, is a different question altogether. But it was fun to build.&lt;/p>
&lt;h2 id="lit-1">&lt;/h>lit&lt;a href="#lit-1">
&lt;/a>
&lt;/h2>&lt;p>&lt;a href="https://github.com/clintonboys/lit" target="_blank" rel="noopener">lit&lt;/a> is a version control system that treats LLM agent prompts as the source of truth for software projects. Generated code lives in a &lt;code>code.lock/&lt;/code> directory (&amp;ldquo;lockdir&amp;rdquo;) and is committed alongside prompts in git. The code generation itself is also handled by &lt;code>lit&lt;/code>.&lt;/p>
&lt;p>The name is a working title. It&amp;rsquo;s meant to simultaneously evoke &lt;code>git&lt;/code> (its spiritual predecessor and storage layer), the word &amp;ldquo;literature&amp;rdquo; (natural language as the source of truth), and just generally &amp;ldquo;vibes&amp;rdquo;, because&amp;hellip; &lt;a href="https://www.youtube.com/watch?v=97IiPli_uXw" target="_blank" rel="noopener">you know&lt;/a>. Unfortunately it&amp;rsquo;s also the name of a well-known &lt;a href="https://github.com/lit/lit" target="_blank" rel="noopener">web framework&lt;/a> with 21k GitHub stars, so it&amp;rsquo;ll probably have to change.&lt;/p>
&lt;p>&lt;em>&lt;a href="https://github.com/clintonboys/lit" target="_blank" rel="noopener">GitHub&lt;/a> | &lt;a href="SPEC.md">Spec&lt;/a> | &lt;a href="https://github.com/clintonboys/lit-demo-crud" target="_blank" rel="noopener">Demo&lt;/a>&lt;/em>&lt;/p>
&lt;p>Here&amp;rsquo;s what a prompt looks like in &lt;code>lit&lt;/code>:&lt;/p>
&lt;span class="code-language">markdown&lt;/span>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-markdown" data-lang="markdown">&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1&lt;/span>&lt;span>---
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2&lt;/span>&lt;span>outputs:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3&lt;/span>&lt;span> &lt;span style="color:#66d9ef">-&lt;/span> src/models/user.py
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4&lt;/span>&lt;span>imports:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5&lt;/span>&lt;span> &lt;span style="color:#66d9ef">-&lt;/span> prompts/models/base.prompt.md
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6&lt;/span>&lt;span>---
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7&lt;/span>&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8&lt;/span>&lt;span># User Model
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9&lt;/span>&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10&lt;/span>&lt;span>Create a SQLAlchemy model for a User with fields:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11&lt;/span>&lt;span>&lt;span style="color:#66d9ef">-&lt;/span> &lt;span style="color:#e6db74">`email`&lt;/span>: String(255), unique, indexed, not null
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12&lt;/span>&lt;span>&lt;span style="color:#66d9ef">-&lt;/span> &lt;span style="color:#e6db74">`hashed_password`&lt;/span>: String(255), not null
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13&lt;/span>&lt;span>&lt;span style="color:#66d9ef">-&lt;/span> &lt;span style="color:#e6db74">`full_name`&lt;/span>: String(255), nullable
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14&lt;/span>&lt;span>&lt;span style="color:#66d9ef">-&lt;/span> &lt;span style="color:#e6db74">`is_active`&lt;/span>: Boolean, default True
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15&lt;/span>&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16&lt;/span>&lt;span>Use the Base class from @import(prompts/models/base.prompt.md).
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17&lt;/span>&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18&lt;/span>&lt;span>Include proper &lt;span style="color:#e6db74">`__repr__`&lt;/span> and a relationship to items.&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>The frontmatter at the top declares what files the prompt generates and what other prompts it depends on. That &lt;code>@import()&lt;/code> is the key idea: &lt;code>lit&lt;/code> builds a dependency &lt;a href="https://en.wikipedia.org/wiki/Directed_acyclic_graph" target="_blank" rel="noopener">DAG&lt;/a> from the imports and generates code according to this graph: when prompt B imports prompt A, &lt;code>lit&lt;/code> generates A first, then feeds A&amp;rsquo;s generated code as context when generating B.&lt;/p>
&lt;p>Here&amp;rsquo;s what the structure of a &lt;code>lit&lt;/code> repo looks like:&lt;/p>
&lt;pre>&lt;code>my-project/
lit.toml # Config: language, framework, model
prompts/
models/user.prompt.md # Source of truth
models/base.prompt.md
api/users.prompt.md
code.lock/
src/models/user.py # Generated artifact
src/models/base.py
src/api/users.py&lt;/code>&lt;/pre>
&lt;h2 id="why-codelock">&lt;/h>Why &lt;code>code.lock/&lt;/code>?&lt;a href="#why-codelock">
&lt;/a>
&lt;/h2>&lt;p>The key insight, if there is one, is that LLM-generated code has the same problem as dependency resolution: the output is non-deterministic and expensive to produce, so you want to pin it. So &lt;code>code.lock/&lt;/code> is generated and committed to git as an artifact. It&amp;rsquo;s a &amp;ldquo;lockdir&amp;rdquo; containing your entire codebase, and you are encouraged not to look at it, the same way you never really open your &lt;code>package-lock.json&lt;/code> file or &lt;code>uv.lock&lt;/code> file, and it&amp;rsquo;s not such a big deal if it gets deleted (except here regenerating it will cost actual money in the form of LLM tokens).&lt;/p>
&lt;h2 id="using-lit">&lt;/h>Using &lt;code>lit&lt;/code>&lt;a href="#using-lit">
&lt;/a>
&lt;/h2>&lt;p>I don&amp;rsquo;t think people are going to be editing &lt;code>.prompt.md&lt;/code> files in an orderly fashion instead of iterating live with an agent. Rather, &lt;code>lit&lt;/code> is for what comes after that, when code needs to be maintained by a team, reviewed, and understood months later.&lt;/p>
&lt;p>There are three workflows where I think this matters.&lt;/p>
&lt;p>&lt;strong>Post-hoc formalization of vibecoding&lt;/strong>. Vibecode something freely, and once it works, write the prompt that describes the intent: what this code should do, what assumptions it makes, what &amp;ldquo;contract&amp;rdquo; it fulfills. Then run &lt;code>lit regenerate&lt;/code> to verify the prompt actually reproduces the code. This command actually handles the generation of the code (integration with LLMs). Now you have a reproducible (insofar as LLMs can be) spec committed alongside the code.&lt;/p>
&lt;p>This feels to me sort of like writing tests after prototyping something.&lt;/p>
&lt;pre>&lt;code># After vibe-coding a feature that works:
vi prompts/auth/login.prompt.md # Describe the intent...
lit regenerate # Verify it reproduces.
lit diff --code # Compare generated vs hand-written
lit commit -m &amp;#34;Capture login intent&amp;#34;&lt;/code>&lt;/pre>
&lt;p>&lt;strong>Prompt-driven changes&lt;/strong>. Requirements change. Instead of asking an AI to &amp;ldquo;update this code&amp;rdquo; and hoping it gets it right, you change the prompt - the spec - and regenerate. The diff shows the change in intent, not just the change in code. Code review becomes review of requirements.&lt;/p>
&lt;p>This is where the DAG becomes really useful. I added two lines to a user model prompt in the &lt;a href="https://github.com/clintonboys/lit-demo-crud" target="_blank" rel="noopener">demo project&lt;/a> and ran &lt;code>lit diff --summary&lt;/code>:&lt;/p>
&lt;pre>&lt;code>=== Changes Summary ===
Prompts:
~ prompts/models/user.prompt.md (&amp;#43;2 -0 lines)
Impact (prompts that will regenerate):
-&amp;gt; prompts/models/user.prompt.md
-&amp;gt; prompts/models/item.prompt.md (imports user models)
-&amp;gt; prompts/schemas/user.prompt.md (imports user models)
-&amp;gt; prompts/api/users.prompt.md (imports user models, user schemas)
-&amp;gt; prompts/schemas/item.prompt.md (imports item models, user schemas)
-&amp;gt; prompts/tests/test_users.prompt.md (imports user schemas)
-&amp;gt; prompts/api/items.prompt.md (imports item models, user models, item schemas)
-&amp;gt; prompts/tests/test_items.prompt.md (imports item schemas, user schemas)
8 prompt(s) will regenerate, 4 unchanged&lt;/code>&lt;/pre>
&lt;p>&lt;strong>Prompts as documentation&lt;/strong>. A new developer reads &lt;code>prompts/&lt;/code> to understand intent, not just implementation. Each prompt file is a spec for what its generated code should do. The DAG shows how components relate.&lt;/p>
&lt;pre>&lt;code>lit debug dag # See how prompts depend on each other
cat prompts/api/users.prompt.md # Read the spec for the users endpoint&lt;/code>&lt;/pre>
&lt;h2 id="the-demo">&lt;/h>The demo&lt;a href="#the-demo">
&lt;/a>
&lt;/h2>&lt;p>I built a complete &lt;a href="https://github.com/clintonboys/lit-demo-crud" target="_blank" rel="noopener">CRUD API app&lt;/a> using &lt;code>lit&lt;/code> to demonstrate the idea. Twelve prompts generate the FastAPI application: models, schemas, API modules, test suites, database config, and package structure. Each prompt is 15–30 lines of natural language. The generated code is a working API with proper relationships, validation, pagination, soft deletes, and test coverage.&lt;/p>
&lt;p>The entire application is reproducible. Clone the repo, set your API key, run &lt;code>lit regenerate&lt;/code>, and you will get the same app.&lt;/p>
&lt;h2 id="under-the-hood">&lt;/h>Under the hood&lt;a href="#under-the-hood">
&lt;/a>
&lt;/h2>&lt;p>When you run &lt;code>lit commit -m &amp;quot;message&amp;quot;&lt;/code>, it parses all the prompt files, builds the dependency DAG, detects which prompts changed since the last commit, computes the regeneration set (changed prompts plus their downstream dependents), and then generates according to the graph. Each prompt gets assembled into an LLM request with the project config, the generated code of its imports, and the prompt body. The LLM responds with file delimiters and &lt;code>lit&lt;/code> parses the output into &lt;code>code.lock/&lt;/code>.&lt;/p>
&lt;p>There&amp;rsquo;s input-hash caching a la Bazel (SHA-256 of prompt content + imported code + config) so unchanged prompts are skipped. There is also basic manual patch support, because sometimes you need a one-line fix and regenerating and burning tokens is overkill. &lt;code>lit&lt;/code> saves your edit as a patch and reapplies it on top of future generations.&lt;/p>
&lt;p>Generation metadata is committed alongside everything else: tokens used, cost, model, a snapshot of the DAG at generation time. You can run &lt;code>lit cost --breakdown&lt;/code> and see exactly what you&amp;rsquo;re spending per prompt:&lt;/p>
&lt;pre>&lt;code>=== Cost Summary ===
Total: $0.42 across 3 commits
Per-prompt breakdown:
prompts/api/users.prompt.md $0.08 (2,431 tokens)
prompts/api/items.prompt.md $0.07 (2,198 tokens)
...&lt;/code>&lt;/pre>
&lt;h2 id="try-it">&lt;/h>Try it&lt;a href="#try-it">
&lt;/a>
&lt;/h2>
&lt;span class="code-language">bash&lt;/span>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1&lt;/span>&lt;span>git clone https://github.com/clintonboys/lit
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2&lt;/span>&lt;span>cd lit
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3&lt;/span>&lt;span>cargo install --path .
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4&lt;/span>&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5&lt;/span>&lt;span>export LIT_API_KEY&lt;span style="color:#f92672">=&lt;/span>sk-ant-...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6&lt;/span>&lt;span>mkdir my-project &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> cd my-project
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7&lt;/span>&lt;span>lit init --defaults&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;p>Or look at the demo: &lt;a href="https://github.com/clintonboys/lit-demo-crud" target="_blank" rel="noopener">lit-demo-crud&lt;/a>.&lt;/p>
&lt;p>The full spec is in &lt;a href="SPEC.md">SPEC.md&lt;/a> — nine sections covering the storage architecture, generation pipeline, DAG resolution, cost tracking, and design rationale.&lt;/p>
&lt;p>It&amp;rsquo;s about 7,600 lines of Rust with 128 tests, and was itself largely vibe-coded with Claude. I did write the README and this blog post by hand though.&lt;/p>
&lt;h2 id="limitiations-of-v1">&lt;/h>Limitiations of v1&lt;a href="#limitiations-of-v1">
&lt;/a>
&lt;/h2>&lt;p>This is a proof-of-concept that I put together in a couple of days. It works and it&amp;rsquo;s interesting but it has a few major limitations, the main one being that currently every prompt must explicitly declare the files it will generate in its YAML frontmatter:&lt;/p>
&lt;pre>&lt;code>outputs:
- src/models/user.py
- tests/test_user.py&lt;/code>&lt;/pre>
&lt;p>This means the prompt author needs to know the output file paths before the LLM generates anything. It works well when you&amp;rsquo;re formalizing existing code or when you have a clear project structure in mind and are OK with prompts and the files they generate being in 1-1 correspondence. But it is a lot more rigid than how most people actually use agents to write software.&lt;/p>
&lt;p>This is a deliberate trade-off to get a prototype working. Declaring outputs up front is what makes the rest of &lt;code>lit&lt;/code> work cleanly: the DAG can be resolved before generation, caching can be content-addressed, and no two prompts can accidentally claim the same file.&lt;/p>
&lt;p>An obvious way to address this is &amp;ldquo;two-shot generation&amp;rdquo;: a cheap first pass asks the LLM &amp;ldquo;what files would you produce for this prompt?&amp;rdquo;, and &lt;code>lit&lt;/code> records the answer as a &amp;ldquo;manifest&amp;rdquo;, which is also pinned in the repo. The second pass does the actual generation against that manifest. This preserves all the benefits of knowing outputs ahead of time (DAG resolution, caching, conflict detection) while removing the burden from the prompt author.&lt;/p>
&lt;p>For a tool like this to be actually used in production for large-scale code bases, maybe it would need to be aware of the &lt;a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree" target="_blank" rel="noopener">AST&lt;/a> of the code base in some way? I think that could be an interesting, but obviously much more involved, direction.&lt;/p>
&lt;p>Please don&amp;rsquo;t hesitate to &lt;a href="mailto:me@clintonboys.com">get in touch&lt;/a> if you are interested in &lt;code>lit&lt;/code> or you want to discuss the ideas here further: I&amp;rsquo;d love to hear from you!&lt;/p></description></item><item><title>Upgrading my home network to support VLANs and 10Gbps</title><link>https://clintonboys.com/projects/homelab/03-network/</link><pubDate>Sat, 07 Feb 2026 00:00:00 +0000</pubDate><guid>https://clintonboys.com/projects/homelab/03-network/</guid><description>&lt;h1 id="upgrading-my-home-network-to-support-vlans-and-10gbps">&lt;/h>Upgrading my home network to support VLANs and 10Gbps&lt;a href="#upgrading-my-home-network-to-support-vlans-and-10gbps">
&lt;/a>
&lt;/h1>&lt;p>&lt;em>February 7, 2026&lt;/em>&lt;/p>
&lt;p>After &lt;a href="https://clintonboys.com/projects/homelab/02-wifi/">improving my Wi-Fi setup&lt;/a>, I started to focus on a wider rehaul of my whole home network. I knew I wanted to accomplish a few things:&lt;/p>
&lt;ul>
&lt;li>remove my ISP&amp;rsquo;s router and replace it with a custom router, &lt;strong>located in my home office&lt;/strong> instead of the networking cupboard in my living room&lt;/li>
&lt;li>support &lt;a href="https://en.wikipedia.org/wiki/VLAN" target="_blank" rel="noopener">VLANs&lt;/a> for better security as I slowly incorporate more home automation&lt;/li>
&lt;li>support &lt;a href="https://en.wikipedia.org/wiki/10_Gigabit_Ethernet" target="_blank" rel="noopener">10Gbps networking&lt;/a> for speed and future proofing&lt;/li>
&lt;li>separate my NAS storage from the compute layer by building a new server and migrating all hosted services there&lt;/li>
&lt;/ul>
&lt;p>The first item on the list was actually the biggest constraint for me, and it led me down the rabbit hole of discovering VLANs in the first place: I had no idea what they were and that I needed them before I started researching. Don&amp;rsquo;t worry, if you don&amp;rsquo;t know what they are either I will do my best to explain it very carefully below.&lt;/p>
&lt;p>My first thought was: the fibre enters my house in my living room, so how can I have my router in the office? I figured it meant the connection would need to first go into the office, through the router, then back out to the living room across a second cable. I spent a bit of time looking into how to lay additional cables, and then realised it was going to be extremely expensive and annoying, so started trying to understand whether it was possible to use a single cable to accomplish what I wanted.&lt;/p>
&lt;p>It was in this state that I turned to the &lt;a href="https://www.reddit.com/r/homelab/" target="_blank" rel="noopener">r/homelab&lt;/a> subreddit, which is a really great source of information (and photos of peoples&amp;rsquo; gear that will make you jealous). I asked a question about how to accomplish what I wanted, and received an answer suggesting that I check out &amp;ldquo;VLANs&amp;rdquo; and &amp;ldquo;router on a stick&amp;rdquo; topology. Apparently this would give me a way to tag packets (or more accurately, &lt;a href="https://en.wikipedia.org/wiki/Frame_%28networking%29" target="_blank" rel="noopener">frames&lt;/a>) entering my network through the living room networking cupboard as &lt;a href="https://en.wikipedia.org/wiki/Wide_area_network" target="_blank" rel="noopener">WAN&lt;/a>, have them automatically be forwarded to the router in the office, and then routed to the correct part of my network, which could be a device in the office, or back in the living room again.&lt;/p>
&lt;div class="diagram-container" style="text-align:center;margin:1.5rem 0;">
&lt;img src="https://clintonboys.com/projects/homelab/03-network/network_simplified.png" alt="Home network topology" class="diagram diagram-light" loading="lazy">
&lt;img src="https://clintonboys.com/projects/homelab/03-network/network_simplified-dark.png" alt="Home network topology" class="diagram diagram-dark" loading="lazy">
&lt;/div>
&lt;p>In order to get this to work, I discovered, I would need to upgrade my switches not just from 1Gbps to 10Gbps, but they would also need to be &lt;em>managed&lt;/em>: rather than just a simple switch that &amp;ldquo;splits&amp;rdquo; the network and provides additional ports, managed switches give you a lot more configurability, crucially in my case the ability to define VLANs, which will give the packet-tagging setup that will allow my topology to work.&lt;/p>
&lt;p>&lt;em>Note&lt;/em>&lt;/p>
&lt;p>A note before we get started about the actual build order. As you will note below, I needed a VLAN-compatible router in order to change the network, so I actually did the router build and the network changes at the same time. I&amp;rsquo;ve split the posts up into two, so take a look at the next post for lots of details on my OPNsense router build and configuration.&lt;/p>
&lt;h2 id="vlans">&lt;/h>VLANs&lt;a href="#vlans">
&lt;/a>
&lt;/h2>&lt;p>When you&amp;rsquo;re learning something complicated and (to me at least) new like networking, it can take a while to understand that there is a single term for something that you need. If you don&amp;rsquo;t know the word, you can spend ages poking in the wrong directions, but once you learn it you suddenly realise that your problem has been solved for decades and is so common that it has an industry standard name. The CAT6 cable between my home and garden office carries a &lt;a href="https://en.wikipedia.org/wiki/Trunking" target="_blank" rel="noopener">trunk&lt;/a> link: a single physical connection between two managed switches, configured to carry traffic from multiple VLANs simultaneously. Each frame travelling along the cable is tagged with its VLAN ID using the &lt;a href="https://en.wikipedia.org/wiki/IEEE_802.1Q" target="_blank" rel="noopener">802.1Q&lt;/a> standard, so the switch at either end knows which VLAN it belongs to and can forward it accordingly.&lt;/p>
&lt;div class="diagram-container" style="text-align:center;margin:1.5rem 0;">
&lt;img src="https://clintonboys.com/projects/homelab/03-network/packet_path.png" alt="Home network topology" class="diagram diagram-light" loading="lazy">
&lt;img src="https://clintonboys.com/projects/homelab/03-network/packet_path-dark.png" alt="Home network topology" class="diagram diagram-dark" loading="lazy">
&lt;/div>
&lt;p>A &amp;ldquo;VLAN&amp;rdquo; then requires several different things working in tandem:&lt;/p>
&lt;ul>
&lt;li>switches which know how to tag and untag frames and transfer them around the network&lt;/li>
&lt;li>a router/firewall which knows which IPs correspond to which VLANs, and can set rules for communication between them (e.g. VLAN $x$ cannot access devices on VLAN $y$)&lt;/li>
&lt;li>a table like the one below, which probably sits in the network designer&amp;rsquo;s head, or their documentation system of choice, which decides which devices get assigned to which VLANs&lt;/li>
&lt;/ul>
&lt;table style="border-collapse:collapse;margin:1.5rem 0;font-size:var(--font-size-base);">
&lt;tbody>
&lt;tr>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;">&lt;span style="display:inline-block;width:60px;height:8px;background:#5b9a8b;border-radius:2px">&lt;/span>&lt;/td>&lt;td colspan="2" style="border:1px solid #555;padding:0.5rem 1rem;text-align:center;">Trunk&lt;/td>&lt;/tr>
&lt;tr>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;">&lt;span style="display:inline-block;width:60px;height:8px;background:#c0c0c0;border-radius:2px">&lt;/span>&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;text-align:center;">1&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;">Management&lt;/td>&lt;/tr>
&lt;tr>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;">&lt;span style="display:inline-block;width:60px;height:8px;background:#cd853f;border-radius:2px">&lt;/span>&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;text-align:center;">10&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;">WAN&lt;/td>&lt;/tr>
&lt;tr>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;">&lt;span style="display:inline-block;width:60px;height:8px;background:#32cd32;border-radius:2px">&lt;/span>&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;text-align:center;">20&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;">Trusted LAN&lt;/td>&lt;/tr>
&lt;tr>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;">&lt;span style="display:inline-block;width:60px;height:8px;background:#ff6b6b;border-radius:2px">&lt;/span>&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;text-align:center;">30&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;">Untrusted IoT&lt;/td>&lt;/tr>
&lt;tr>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;">&lt;span style="display:inline-block;width:60px;height:8px;background:#f4a460;border-radius:2px">&lt;/span>&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;text-align:center;">40&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;">Guest LAN&lt;/td>&lt;/tr>
&lt;tr>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;">&lt;span style="display:inline-block;width:60px;height:8px;background:#6b8cff;border-radius:2px">&lt;/span>&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;text-align:center;">50&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;">DMZ&lt;/td>&lt;/tr>
&lt;tr>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;">&lt;span style="display:inline-block;width:60px;height:8px;background:#ff00ff;border-radius:2px">&lt;/span>&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;text-align:center;">60&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;">AI DMZ&lt;/td>&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>After quite a bit of research I settled on the seven VLANs in the table above. The numbers are the actual tags used when things are going around the network, but you can also use a very common and genius mnemonic of setting the devices in each VLAN to receive (static or through DHCP) IPs from the &lt;a href="https://en.wikipedia.org/wiki/Subnet" target="_blank" rel="noopener">subnet&lt;/a> &lt;code>192.168.{$ID}.0/24&lt;/code>. (Unfortunately this doesn&amp;rsquo;t hold up so well in IPv6 land where &lt;a href="https://gist.github.com/timothyham/dd003dbad5614b425a8325ec820fd785?ck%5Fsubscriber%5Fid=512831968" target="_blank" rel="noopener">IP addresses are like sand&lt;/a>).&lt;/p>
&lt;p>&lt;strong>Management VLAN (1)&lt;/strong>&lt;/p>
&lt;p>The management VLAN will just have my router, and my three core managed switches, which all have a nice web UI for management. After a lot of research I ended up buying the following switches.&lt;/p>
&lt;table style="border-collapse:collapse;margin:1.5rem 0;font-size:var(--font-size-base);">
&lt;tbody>
&lt;tr>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;font-weight:bold;background:var(--color-contrast-medium);">Type&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;font-weight:bold;background:var(--color-contrast-medium);">Switch&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;font-weight:bold;background:var(--color-contrast-medium);">1Gbps&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;font-weight:bold;background:var(--color-contrast-medium);">Multigig RJ45&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;font-weight:bold;background:var(--color-contrast-medium);">10Gbps SFP+&lt;/td>&lt;/tr>
&lt;tr>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;">Managed&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;">2x &lt;a href="https://www.zyxel.com/global/en/products/switch/12-port-web-managed-multi-gigabit-switch-includes-3-port-10g-and-1-port-10g-sfp-xgs1250-12">Zyxel XGS 1250-12&lt;/a>&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;text-align:center;">8&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;text-align:center;">3&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;text-align:center;">1&lt;/td>&lt;/tr>
&lt;tr>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;">Managed&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;">&lt;a href="https://mikrotik.com/product/crs305_1g_4s_in">MikroTik CRS305-1G-4S+IN&lt;/a>&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;text-align:center;">1&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;text-align:center;">—&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;text-align:center;">4&lt;/td>&lt;/tr>
&lt;tr>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;">Unmanaged&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;">&lt;a href="https://www.tp-link.com/uk/business-networking/soho-switch-unmanaged/tl-sg1005p/">TP-Link TL-SG1005P&lt;/a> (PoE)&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;text-align:center;">5&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;text-align:center;">—&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;text-align:center;">—&lt;/td>&lt;/tr>
&lt;tr>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;">Unmanaged&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;">&lt;a href="https://www.amazon.co.uk/dp/B07Q5B51P7">TP-Link TL-SG1008MP&lt;/a> (PoE)&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;text-align:center;">8&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;text-align:center;">—&lt;/td>&lt;td style="border:1px solid #555;padding:0.5rem 1rem;text-align:center;">—&lt;/td>&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>This setup gives me a combined total of 30 x 1Gbps &lt;a href="https://en.wikipedia.org/wiki/Registered_jack#RJ45" target="_blank" rel="noopener">RJ45&lt;/a> Ethernet ports (of which 13 are PoE), 6 x &amp;ldquo;multigig&amp;rdquo; RJ45 Ethernet ports, which support 1/2.5/5/10Gbps, and 6 x 10Gbps &lt;a href="https://en.wikipedia.org/wiki/Small_Form-factor_Pluggable" target="_blank" rel="noopener">SFP+&lt;/a> ports. So that&amp;rsquo;s 42 ports in total, and I have an additional 3 dumb 1Gbps switches with 5 ports each from my old setup if I ever need them. So I don’t think I’ll be buying a switch again in the near future.&lt;/p>
&lt;p>The Zyxels were great buys. They’re sturdy, good quality, with a simple management interface that gives you exactly what you need and nothing more: it seems like if you are a &amp;ldquo;prosumer&amp;rdquo; and you want a managed switch, you pretty much need two features: VLANs and link aggregation (LAGG), which lets you combine multiple physical Ethernet links into a single virtual link for additional bandwidth and redundancy.&lt;/p>
&lt;div class="diagram-container" style="text-align:center;margin:1.5rem 0;">
&lt;img src="https://clintonboys.com/projects/homelab/03-network/reception_switch.png" alt="Home network topology" class="diagram" loading="lazy">
&lt;/div>
&lt;p>You can see above my VLAN configuration for the Zyxel switch in my living room. The first port has my Apple TV connected (VLAN 20) and the second my Hive hub (VLAN 30). Port 10 has the trunk line connected, and Port 9 is connected to the ISP&amp;rsquo;s ONT for the WAN link. If you look at the switch configuration screenshot above, green means untagged and orange means tagged, which I was mentally thinking about the wrong way around for a while at the start, leading to some problems (see below for some more details).&lt;/p>
&lt;p>The MikroTik, which I used to add more 10Gbps ports in my office where I have all my 10Gbps capable devices, is a lot more capable and consequently a lot harder to figure out how to use. It even has a CLI!&lt;/p>
&lt;p>SFP+ was new to me. It&amp;rsquo;s a different interface to RJ45 (which is used synonymously with &amp;ldquo;Ethernet&amp;rdquo; by most folks) and it&amp;rsquo;s a bit more flexible but trickier to use so hasn&amp;rsquo;t gained as much exposure and usage, except in 10Gbps and beyond where its benefits start to outweigh its drawbacks. I&amp;rsquo;ll write some more in a future post about how I set up the SFP+ ports with the right modules and fibre optics.&lt;/p>
&lt;p>&lt;strong>WAN VLAN (10)&lt;/strong>&lt;/p>
&lt;p>I defined a VLAN for my WAN traffic. Like I showed in the diagrams above, this lets WAN traffic travel along the same trunk as LAN traffic in either direction, and be handled correctly by OPNsense (more on how to set this up in OPNsense in a future post).&lt;/p>
&lt;p>&lt;strong>Trusted VLAN (20)&lt;/strong>&lt;/p>
&lt;p>All the main devices in my house that I want to be able access the main LAN, and that I trust, are assigned IPs in this VLAN. This includes my and my wife&amp;rsquo;s laptops, the NAS, our Apple TV and in the future the Proxmox server I want to build. The main Wi-Fi network used by all our phones and computers is set up through the UniFi controller to be assigned to this VLAN as well.&lt;/p>
&lt;p>&lt;strong>IoT VLAN (30)&lt;/strong>&lt;/p>
&lt;p>Our Hive Hub, which controls the thermostat in our house, as well as some cheap AliExpress IoT stuff (another thermostat to control my office heater, some smart switches and plugs) is no longer on the same network as all our trusted devices. These sit in their own VLAN, which has access to the internet but not &amp;ldquo;sideways&amp;rdquo; to the trusted LAN. I had to set up a second WiFi network with a different SSID on this VLAN, and make sure the connection to the PoE switch that connects to the APs (port 8 in the image above) is also a trunk line carrying the 20, 30 and 40 VLANs (see below).&lt;/p>
&lt;p>&lt;strong>Guest VLAN (40)&lt;/strong>&lt;/p>
&lt;p>Similarly, there&amp;rsquo;s no reason guests need to access the files and media we store on our LAN devices. So I set up an additional Wi-Fi network in this VLAN that guests can access. Like the IoT VLAN, it can access the Internet but has no access to our trusted devices. It also has a bandwidth limit.&lt;/p>
&lt;p>&lt;strong>DMZ and AI DMZ VLANs (50 and 60)&lt;/strong>&lt;/p>
&lt;p>The last two VLANs are even more &amp;ldquo;tightly secured&amp;rdquo;. Here I have very tight firewall rules defined because services here can be accessed from the Internet: it&amp;rsquo;s where this site is served from. I also wanted very fine, port-level control over a second VLAN for AI agents, and to have these separated from my regular DMZ. Again, more about this in the next post.&lt;/p>
&lt;h2 id="lessons-learned">&lt;/h>Lessons learned&lt;a href="#lessons-learned">
&lt;/a>
&lt;/h2>&lt;p>Setting up VLANs is a notoriously frustrating process. There were several times in the process where I locked myself out of the network by misconfiguring the Wi-Fi network that my laptop was using to access the switches. If I had known what I was doing upfront, there are ways to avoid this, but most important is to work through a physical connection to the switch wherever possible, and to design your network over multiple iterations on paper (or &lt;a href="https://draw.io" target="_blank" rel="noopener">draw.io&lt;/a>) before actually configuring anything.&lt;/p>
&lt;p>The main lockout incident happened while configuring the living room Zyxel switch. I changed the VLAN tags on Port 8 (which connected to the PoE switch feeding my APs), which immediately killed the Wi-Fi. I then tried accessing the switch via a different port, but that port was on VLAN 20 while switch management was still on VLAN 1, with no routing between them.&lt;/p>
&lt;p>A power cycle didn&amp;rsquo;t help because the config had already been saved. I tried accessing the living room switch from the office switch, but hit the same VLAN 1 vs VLAN 20 routing problem across the trunk. Eventually the resolution required a factory reset of the living room switch (holding the physical reset button), reconfiguring from scratch, and being careful to set up management access on VLAN 20 &lt;em>before&lt;/em> changing port assignments.&lt;/p>
&lt;p>There was also a second lockout around the trunk link between the two switches. The living room switch had VLAN 1 set to &lt;em>untagged&lt;/em> on the trunk port instead of &lt;em>tagged&lt;/em> (like I mentioned above I was thinking about VLAN tagging/untagging the wrong way around), which broke management access across switches. I had to plug into the switch and fix the trunk tagging.&lt;/p>
&lt;p>The pattern was always the same: changing a port&amp;rsquo;s VLAN assignment while connected through that port (or a dependent path), then losing the ability to reach the switch management interface to undo the change. So it&amp;rsquo;s important to always maintain a known-good management path before touching VLAN configs, and work through a direct physical connection to the switch you&amp;rsquo;re modifying.&lt;/p>
&lt;h2 id="the-finished-product">&lt;/h>The finished product&lt;a href="#the-finished-product">
&lt;/a>
&lt;/h2>&lt;p>Below is a simplified diagram of my new setup, colour-coded to show the VLAN setup. You can compare it to the diagram of my old setup in the &lt;a href="https://clintonboys.com/projects/homelab/01-planning/">introduction post&lt;/a> to see how much of an improvement this is, and that it accomplishes all the goals I set out at the start of this post. It took quite a bit of frustration and effort to get it working, but it is supremely satisfying now it&amp;rsquo;s all up and running.&lt;/p>
&lt;div class="diagram-container" style="text-align:center;margin:1.5rem 0;">
&lt;img src="https://clintonboys.com/projects/homelab/03-network/new_network.png" alt="Home network topology" class="diagram diagram-light" loading="lazy">
&lt;img src="https://clintonboys.com/projects/homelab/03-network/new_network-dark.png" alt="Home network topology" class="diagram diagram-dark" loading="lazy">
&lt;/div>
&lt;p>In future posts I will go into a lot more detail about the router setup, the 10Gbps side of the networking (the thicker lines in the diagram above represent links which are 10Gbps), and the biggest part of the setup: the new Proxmox server I am planning to build to host all my applications and services.&lt;/p></description></item><item><title>Upgrading my Wi-Fi network</title><link>https://clintonboys.com/projects/homelab/02-wifi/</link><pubDate>Sat, 31 Jan 2026 00:00:00 +0000</pubDate><guid>https://clintonboys.com/projects/homelab/02-wifi/</guid><description>&lt;h1 id="upgrading-my-wi-fi-network">&lt;/h>Upgrading my Wi-Fi network&lt;a href="#upgrading-my-wi-fi-network">
&lt;/a>
&lt;/h1>&lt;p>&lt;em>January 31, 2026&lt;/em>&lt;/p>
&lt;p>The first step in improving my home network had to be fixing my dreadful Wi-Fi setup. My ISP-provided router was nowhere near powerful enough to reach the back end of my house, let alone the garden or the home office.&lt;/p>
&lt;p>In a previous apartment, I had used a basic mesh system for a similar reason: the apartment was long and narrow, and the Wi-Fi didn&amp;rsquo;t really reach the far end, so I used a simple mesh system to boost the signal to the other end of the house. There are two types of mesh: the simplest, most &amp;ldquo;plug and play&amp;rdquo; versions are basically just signal relay systems. They don&amp;rsquo;t need ethernet connections, and so long as you place each one within the range of the next one, you can get a half-decent mesh purely through Wi-Fi, without worrying about running Ethernet cables through your house.&lt;/p>
&lt;p>I wanted a much more robust system though, and my research led me to &amp;ldquo;&lt;a href="https://en.wikipedia.org/wiki/Backhaul_%28telecommunications%29#WiFi_mesh_networks_for_wireless_backhaul" target="_blank" rel="noopener">wired Ethernet backhaul&lt;/a>&amp;rdquo; as being the best way to implement a mesh system. This is how it&amp;rsquo;s actually done in large offices or public Wi-Fi networks. Wired Ethernet backhaul means that each access point (AP) is directly wired to the network with an Ethernet cable. There is then some centralised controller software which manages the network, allowing for seamless roaming between APs.&lt;/p>
&lt;p>If you spend a bit of time reading about APs, you will definitely read about &lt;a href="https://ui.com" target="_blank" rel="noopener">Ubiquiti&lt;/a> and their &lt;a href="https://ui.com/wifi" target="_blank" rel="noopener">UniFi&lt;/a> range. It seems like they are positioned as the Apple of home networking equipment: everything is very nicely designed, with a &amp;ldquo;just works&amp;rdquo; experience on top of a &amp;ldquo;prosumer&amp;rdquo; level of configurability and features. Most people pair their UniFi APs with one of their &amp;ldquo;cloud gateways&amp;rdquo;, which are designed to replace your ISP router and provide much better firewall options and configurability, as well as hosting the controller application for the Wi-Fi mesh network. Because I wanted to build my own router (see some later posts for lots of details on that), I needed to find a way to host the controller application myself.&lt;/p>
&lt;p>After quite a bit of research I settled on three &lt;a href="https://uk.store.ui.com/uk/en/products/u6-plus" target="_blank" rel="noopener">UniFi U6+&lt;/a> APs. Three seemed like it would be enough to cover my house, garden and office (two in the house, one in the office), and I figured if I needed more I could always buy more. Crucially, these models support &lt;a href="https://en.wikipedia.org/wiki/Power_over_Ethernet" target="_blank" rel="noopener">Power over Ethernet&lt;/a> (PoE), meaning they can get their power &lt;em>and&lt;/em> network from a single ethernet cable, making it all a lot neater. They&amp;rsquo;re designed to be ceiling-mounted, but they work well enough and look decent enough just sitting around the house.&lt;/p>
&lt;div class="diagram-container" style="text-align:center;margin:1.5rem 0;">
&lt;img src="https://clintonboys.com/projects/homelab/02-wifi/ap.png" alt="Home network topology" class="diagram" loading="lazy">
&lt;/div>
&lt;p>I was very lucky that the person we bought the house from had wired Ethernet to two important locations. There&amp;rsquo;s a &amp;ldquo;drop&amp;rdquo; going from one of the upstairs bedrooms (in the picture above) down into the cupboard in the living room which has become a designated networking cupboard, with two cables terminated in nice sockets in the wall. So I could just plug an AP directly into the wall with a single ethernet cable and it works, provided the other end of that cable is connected to a PoE-capable switch, which I also had to buy. I added another AP downstairs plugged directly into the switch.&lt;/p>
&lt;p>The biggest gift of all the previous owners left us with is a CAT6 cable buried in a trench in the garden which connects the home office to the networking cupboard. Another, smaller PoE switch in the home office then allowed me to install the final AP there. I&amp;rsquo;ll say a bit more about the specific switches I chose in the &lt;a href="https://clintonboys.com/projects/homelab/03-network/">next post&lt;/a>.&lt;/p>
&lt;p>My final step was to install the UniFi controller app. As I mentioned above, it was important for me to be able to self-host it rather than get locked into a UniFi gateway. It was actually very straightforward to run through Docker on my Synology NAS, which is connected via Ethernet to the same network as the APs. Once the container was running, I just logged in, set it up using the wizard, and then &amp;ldquo;adopted&amp;rdquo; the APs and I was done!&lt;/p>
&lt;div class="diagram-container" style="text-align:center;margin:1.5rem 0;">
&lt;img src="https://clintonboys.com/projects/homelab/02-wifi/unifi.png" alt="Home network topology" class="diagram" loading="lazy">
&lt;/div>
&lt;p>The only minor snag I hit was quite educational. When I was debugging one of the APs not adopting properly, I noticed it was receiving a strange IP address that was not the default one it shipped with. It took me a good hour to figure out that my Hive hub was running its own &lt;a href="https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol" target="_blank" rel="noopener">DHCP server&lt;/a> and handing out a different IP address to the AP, making it undiscoverable at its default IP. Once I turned the Hive hub off everything worked fine, and once the AP was adopted I just turned it back on again. This was a good introduction to DHCP for me, as well as some network diagnostic tools that I ended up using much more heavily as I headed deeper down the rabbit hole.&lt;/p>
&lt;p>My next step was a complete redesign of my home network and replacing the ISP router with a custom build, you can read about that &lt;a href="https://clintonboys.com/projects/homelab/03-network/">here&lt;/a>.&lt;/p></description></item><item><title>Waratah: introduction and plan</title><link>https://clintonboys.com/projects/homelab/01-planning/</link><pubDate>Tue, 27 Jan 2026 00:00:00 +0000</pubDate><guid>https://clintonboys.com/projects/homelab/01-planning/</guid><description>&lt;h1 id="waratah">&lt;/h>Waratah&lt;a href="#waratah">
&lt;/a>
&lt;/h1>&lt;p>&lt;em>January 27, 2026&lt;/em>&lt;/p>
&lt;p>We bought our first home last summer, and after the initial moving in, setting up, redecorating and making ourselves feel at home, winter rolled around: the perfect season for spending a lot of time at home tinkering with computers. I ended up diving deep into a project I have wanted to do for years but never felt I could justify in a rented house: building a proper home networking setup myself.&lt;/p>
&lt;p>I spent quite a lot of time over the last couple of months diving into this rabbit hole, and ended up reading a lot of blogs about &lt;em>homelabs&lt;/em>: a sort of environment you set up at home to allow you to play around with interesting technologies you want to learn about, and &lt;em>self-hosting&lt;/em>: using your own hardware to serve applications and services for your personal use.&lt;/p>
&lt;p>This write-up is an attempt to give back to the community which I learnt so much from by reading detailed write-ups about people&amp;rsquo;s setups. My setup does involve a homelab, but it is also a full build of a &amp;ldquo;production&amp;rdquo; home networking setup that serves my whole house: my wife and kids and all our devices, our &amp;ldquo;smart home&amp;rdquo; gear, guests, media, backups, and in the future more and more self-hosted services to try and fight back against the encroachment of &lt;a href="https://xkcd.com/908/" target="_blank" rel="noopener">The Cloud&lt;/a> on our lives.&lt;/p>
&lt;h2 id="old-setup">&lt;/h>Old setup&lt;a href="#old-setup">
&lt;/a>
&lt;/h2>&lt;p>Here&amp;rsquo;s a schematic of our old network, before I started working on changes.&lt;/p>
&lt;div class="diagram-container" style="text-align:center;margin:1.5rem 0;">
&lt;img src="https://clintonboys.com/projects/homelab/01-planning/old_network.png" alt="Home network topology" class="diagram diagram-light" loading="lazy">
&lt;img src="https://clintonboys.com/projects/homelab/01-planning/old_network-dark.png" alt="Home network topology" class="diagram diagram-dark" loading="lazy">
&lt;/div>
&lt;p>I live in London, and my ISP is &lt;a href="http://www.communityfibre.co.uk" target="_blank" rel="noopener">Community Fibre&lt;/a>. We are currently on a 1Gbps symmetric connection with them, which has a static public IP (note: I signed up for this almost three years ago, apparently this is &lt;em>not&lt;/em> the case for new subscribers to their 1Gbps plans, you will get put behind &lt;a href="https://en.wikipedia.org/wiki/Carrier-grade_NAT" target="_blank" rel="noopener">CGNAT&lt;/a>) and has pretty much always been reliable and achieves the line rate consistently in both upload and download (1Gbps will top out at around 940Mbps, or 117MB/s, due to various overheads). They provide an &lt;a href="https://en.wikipedia.org/wiki/Network_interface_device#Optical_network_terminals" target="_blank" rel="noopener">ONT&lt;/a> into which the fibre connection terminates. This then connected to the router that they provided, which also functioned as the sole Wi-Fi access point. I connected the stuff I need in my living room directly to the router: Apple TV, Hive hub to control the heating system in my house, and then connected up a simple unmanaged switch for the three CAT6 cable runs the previous owner had installed through the walls: one out to the home office at the end of the garden, which goes through a trench, and two upstairs into nicely terminated wall connectors, which I wasn&amp;rsquo;t using (yet).&lt;/p>
&lt;p>In the office, which is about 15 metres away from the house, I plugged the other end of that cable into another unmanaged switch, and into that went my laptops and my &lt;a href="https://global.download.synology.com/download/Document/Hardware/DataSheet/DiskStation/20-year/DS220&amp;#43;/enu/Synology_DS220_Plus_Data_Sheet_enu.pdf" target="_blank" rel="noopener">Synology DS220+ NAS&lt;/a>. The NAS hosted a fairly basic media setup, including Plex, and was the target of Time Machine backups from my Mac.&lt;/p>
&lt;h2 id="shortcomings">&lt;/h>Shortcomings&lt;a href="#shortcomings">
&lt;/a>
&lt;/h2>&lt;p>This setup worked OK for some things, but there were quite a few limitations.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Very poor Wi-Fi coverage&lt;/strong>. We have a two-storey Victorian terrace house with thick brick walls. The ISP router worked fine in the living room and master bedroom directly above it, but the back of the house had very patchy coverage, the garden almost none, and the office none at all. Combined with the fact that our street is a 4G dead zone, this was often a point of frustration.&lt;/li>
&lt;li>&lt;strong>No real control of routing&lt;/strong>. I have always struggled with the limited configurability of ISP-provided routers. I&amp;rsquo;d like to be able to configure firewalls, manage DNS and DHCP myself (don&amp;rsquo;t know what that means? Neither did I until I started digging!), and have real monitoring and logs and visibility into the network.&lt;/li>
&lt;li>&lt;strong>Flat network&lt;/strong>. I would like the ability to segment my network to be confident that any &lt;a href="https://en.wikipedia.org/wiki/Internet_of_things" target="_blank" rel="noopener">IoT&lt;/a> devices I might purchase don&amp;rsquo;t have access to my main LAN. I&amp;rsquo;d also like to be able to self-host my website through a &lt;a href="https://en.wikipedia.org/wiki/DMZ_%28computing%29" target="_blank" rel="noopener">DMZ&lt;/a>.&lt;/li>
&lt;li>&lt;strong>Limited to 1Gbps&lt;/strong>. I would like to future-proof my setup and support 10Gbps speeds for LAN, and in the future WAN.&lt;/li>
&lt;li>&lt;strong>No separate NAS storage from self-hosted compute&lt;/strong>. The only way I can host services is as Docker containers inside Synology&amp;rsquo;s &lt;a href="https://www.synology.com/en-global/dsm" target="_blank" rel="noopener">DSM&lt;/a> operating system. I&amp;rsquo;d like to expand what I can host, and also separate the storage layer from the compute layer for a more robust and modular architecture.&lt;/li>
&lt;li>&lt;strong>Lack of power&lt;/strong>. The Synology NAS has been reliable for almost five years now, and I am going to keep using it for storage for the next few years at least, but it does have a dated and fairly weak CPU and a paltry 6GB of RAM, so doesn&amp;rsquo;t really give me a playground for proper virtualisation or home automation.&lt;/li>
&lt;/ul>
&lt;h2 id="plan">&lt;/h>Plan&lt;a href="#plan">
&lt;/a>
&lt;/h2>&lt;p>I spent a lot of time researching how to improve my setup, and I came up with the following plan, broadly split into four parts:&lt;/p>
&lt;ol>
&lt;li>Build a mesh Wi-Fi network with wired ethernet backhaul.&lt;/li>
&lt;li>Replace the ISP router with a self-built router running OPNsense.&lt;/li>
&lt;li>Upgrade the entire network to support 10Gbps networking and VLANs.&lt;/li>
&lt;li>Build a dedicated server to run Proxmox VE to host all my apps.&lt;/li>
&lt;/ol>
&lt;p>Some other things that were important to accomplish in the project:&lt;/p>
&lt;ul>
&lt;li>Build out a solid, trustworthy backup system (Time Machine is just not good enough)&lt;/li>
&lt;li>Use &lt;a href="https://en.wikipedia.org/wiki/Infrastructure_as_code" target="_blank" rel="noopener">infrastructure as code&lt;/a> wherever possible&lt;/li>
&lt;li>Proper monitoring of the whole system&lt;/li>
&lt;li>Gear in the home office&lt;/li>
&lt;li>Everything nicely &lt;a href="https://en.wikipedia.org/wiki/Rack_unit" target="_blank" rel="noopener">rackmounted&lt;/a>, neat and tidy cable management&lt;/li>
&lt;/ul>
&lt;p>I decided to call the project &lt;em>Waratah&lt;/em>, after one of my favourite Australian native &lt;a href="https://en.wikipedia.org/wiki/Waratah" target="_blank" rel="noopener">flowers&lt;/a> (it&amp;rsquo;s a long-standing tradition of mine to name my projects after Australian stuff). I also decided that the different components of the network (switches, servers, APs, etc) will be named after different villages in the &lt;a href="https://en.wikipedia.org/wiki/Blue_Mountains_%28New_South_Wales%29" target="_blank" rel="noopener">Blue Mountains&lt;/a> in New South Wales, Australia, where I grew up.&lt;/p>
&lt;p>You can keep reading about the first step in this project &lt;a href="https://clintonboys.com/projects/homelab/02-wifi/">here&lt;/a>.&lt;/p></description></item><item><title>Angophora</title><link>https://clintonboys.com/projects/angophora/</link><pubDate>Sun, 30 Mar 2025 02:12:30 -0500</pubDate><guid>https://clintonboys.com/projects/angophora/</guid><description>&lt;h1 id="angophora">&lt;/h>Angophora&lt;a href="#angophora">
&lt;/a>
&lt;/h1>&lt;p>&lt;em>March 30, 2025&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://angophora-blog.vercel.app" target="_blank" rel="noopener">Angophora&lt;/a> is a new blog I started this week to share some writing that doesn&amp;rsquo;t naturally fit on this technically-slanted personal website, or on my &amp;ldquo;digital garden&amp;rdquo; &lt;a href="https://mtsolitary.com/" target="_blank" rel="noopener">Mt. Solitary&lt;/a>. I&amp;rsquo;m hoping to post things at quite a regular cadence here: links to interesting things I&amp;rsquo;ve found, reviews, thoughts, basically anything that comes to mind. Feel free to take a look.&lt;/p>
&lt;h2 id="technical-notes">&lt;/h>Technical notes&lt;a href="#technical-notes">
&lt;/a>
&lt;/h2>&lt;p>There&amp;rsquo;s not a lot to share in terms of how the new site was created that I haven&amp;rsquo;t already written about on one of the &lt;a href="https://clintonboys.com/projects/hugo-migration/">other&lt;/a> &lt;a href="https://clintonboys.com/projects/nitzanboys-com/">project&lt;/a> &lt;a href="https://clintonboys.com/projects/mt-solitary/">pages&lt;/a> on this site relating to static websites. I chose a very simple &lt;a href="https://gohugo.io" target="_blank" rel="noopener">Hugo&lt;/a> theme called &lt;a href="https://github.com/ldeso/hugo-flex" target="_blank" rel="noopener">hugo-flex&lt;/a> and deployed with Vercel. I added a custom font and a simple GoatCounter script, and the whole thing took me about ten minutes. My fifteen-year-old self writing raw HTML on GeoCities would be amazed.&lt;/p></description></item><item><title>GreenChainer</title><link>https://clintonboys.com/projects/greenchainer/</link><pubDate>Wed, 19 Mar 2025 02:12:30 -0500</pubDate><guid>https://clintonboys.com/projects/greenchainer/</guid><description>&lt;h1 id="greenchainer">&lt;/h>GreenChainer&lt;a href="#greenchainer">
&lt;/a>
&lt;/h1>&lt;p>&lt;em>March 19, 2025&lt;/em>&lt;/p>
&lt;p>&lt;a href="https://greenchainer.clintonboys.com" target="_blank" rel="noopener">GreenChainer&lt;/a> is a web application that I built a proof-of-concept for recently, inspired by the &lt;a href="https://clintonboys.com/posts/green-chain-walk/">Green Chain Walk&lt;/a> in South London, where I live. I spend a lot of time walking, and one of the greatest things about this part of the city is the immense amount of green spaces, which have been famously &amp;ldquo;stitched&amp;rdquo; together in the Green Chain Walk, a fantastic 80 kilometre network of signposted routes connecting many of them together. This got me thinking: could I build an application that would automatically generate &amp;ldquo;green chain&amp;rdquo; routes between a given start and end point, of a given length?&lt;/p>
&lt;p>
&lt;img src="https://clintonboys.com/projects/greenchainer/screenshot1_hu872667b7719c7767ba138e9522be9d6c_4422681_800x0_resize_q100_h2_box_3.webp"
width="800"
height="515"
alt="GreenChainer screenshot"
class="single-post-image"
loading="lazy"
decoding="async"
>
&lt;/p>
&lt;p>To implement this idea, I needed to stitch a bunch of components together.&lt;/p>
&lt;ul>
&lt;li>A map to display the user&amp;rsquo;s chosen search location, green spaces, and the eventual route.&lt;/li>
&lt;li>A geocoding API, to convert a text-based location search string like &amp;ldquo;New York City&amp;rdquo; into coordinates to centre the map around.&lt;/li>
&lt;li>An API to find green spaces within a given radius of the searched location.&lt;/li>
&lt;li>A routing API which supports walking routes.&lt;/li>
&lt;li>A routing algorithm which creates the &amp;ldquo;chain&amp;rdquo; route through various green spaces nearby the searched location. I figured from the start that this step would require the most thought.&lt;/li>
&lt;/ul>
&lt;p>I then had to throw all of these things together in a web application and deploy it somewhere. You can read below for the details of what I used to put everything together.&lt;/p>
&lt;h2 id="the-map">&lt;/h>The map&lt;a href="#the-map">
&lt;/a>
&lt;/h2>&lt;p>I have some experience using &lt;a href="https://www.openstreetmap.org/" target="_blank" rel="noopener">OpenStreetMap&lt;/a> (OSM), one of the greatest projects of the open internet alongside Wikipedia. It&amp;rsquo;s a free, open source map of the entire world that anyone is free to contribute to. It doesn&amp;rsquo;t have the richness of proprietary mapping services like Google Maps or Apple Maps, but in many parts of the world it is absolutely competitive with them, and it&amp;rsquo;s completely free and open! So choosing it as the map layer for this project was a no-brainer. You can hook it into Java/TypeScript using &lt;a href="https://leafletjs.com" target="_blank" rel="noopener">Leaflet&lt;/a>, a nice mature library that supports many different mapping services, including OSM.&lt;/p>
&lt;h2 id="geocoding">&lt;/h>Geocoding&lt;a href="#geocoding">
&lt;/a>
&lt;/h2>&lt;p>Geocoding is an interesting problem: I wrote a bit about my first experiences with it &lt;a href="https://clintonboys.com/projects/israeli-constituencies/">here&lt;/a> when I was trying to do something a bit more complicated. The use case here is quite simple: just a search bar that the user can enter in text (which I will assume is specific enough and doesn&amp;rsquo;t ned to be &amp;ldquo;disambiguated&amp;rdquo;) and have an API return coordinates.&lt;/p>
&lt;p>I used &lt;a href="https://nominatim.org" target="_blank" rel="noopener">Nominatim&lt;/a>, a geocoding services based on OSM which offers a generous free API for simple applications like what I am building here, provided you adhere to their &lt;a href="https://operations.osmfoundation.org/policies/nominatim/" target="_blank" rel="noopener">usage policy&lt;/a>. It works very nicely provided you are clear enough with your search query.&lt;/p>
&lt;h2 id="fetching-green-spaces">&lt;/h>Fetching green spaces&lt;a href="#fetching-green-spaces">
&lt;/a>
&lt;/h2>&lt;p>Green spaces, like parks, commons, woodland etc, have special metadata within the OSM map. There is an additional API, called &lt;a href="https://dev.overpass-api.de/overpass-doc/en/preface/preface.html" target="_blank" rel="noopener">Overpass&lt;/a>, which provides search functionality for OSM. So for the use case I needed, you can provide coordinates and a radius, and have the API return a list of &amp;ldquo;points of interest&amp;rdquo; of certain types within that area. For the initial version of the application I use the API to search exclusively for places labeled &amp;ldquo;park&amp;rdquo; on the map, but this could be easily extended to the many other different types of &amp;ldquo;green spaces&amp;rdquo; that OSM allows for.&lt;/p>
&lt;h2 id="routing-api">&lt;/h>Routing API&lt;a href="#routing-api">
&lt;/a>
&lt;/h2>&lt;p>Once we have a location and a list of green spaces, we need to build an interesting route between them. For this we need a routing API - something that takes in a start and end location and returns a route between them that satisfies certain criteria. Usually this will be the &amp;ldquo;shortest route&amp;rdquo;, and in this case I want that shortest route to follow walking routes and not just roads that can be driven. I used &lt;a href="https://openrouteservice.org" target="_blank" rel="noopener">OpenRouteService&lt;/a> (ORS), which is related to the OSM &amp;ldquo;ecosystem&amp;rdquo; and provides a generous free tier, though you do need to pay if your requests exceed a certain monthly quota or you will get blocked.&lt;/p>
&lt;h2 id="routing-algorithm">&lt;/h>Routing algorithm&lt;a href="#routing-algorithm">
&lt;/a>
&lt;/h2>&lt;p>Because there is no &amp;ldquo;greenest route&amp;rdquo; option in ORS (that&amp;rsquo;s why I&amp;rsquo;m trying to make this project!) we will need a custom algorithm to build our &amp;ldquo;green chain&amp;rdquo;. I devised a pretty simple POC algorithm here, which has as inputs the desired start and end points, as well as a list of green spaces in the radius and a desired walk length, and then checks for candidate &amp;ldquo;waypoints&amp;rdquo; from the list of green spaces until it finds a chained route between them that it thinks, based on a heuristic, will be close to the desired total length.&lt;/p>
&lt;p>This is definitely the most unfinished (and of course, the most interesting and important) part of the project. It doesn&amp;rsquo;t work anywhere near as well as I would like it to, and I&amp;rsquo;m hoping to find some time to improve it in the coming months.&lt;/p>
&lt;h2 id="building-the-application">&lt;/h>Building the application&lt;a href="#building-the-application">
&lt;/a>
&lt;/h2>&lt;p>This is a single-page application so it felt like a natural fit for &lt;a href="https://react.dev" target="_blank" rel="noopener">React&lt;/a> to me. I split the code into &amp;ldquo;frontend&amp;rdquo; and &amp;ldquo;backend&amp;rdquo;, with a fairly simple React application in the frontend showing a map component, and then various boxes on the left hand side for the different phases of the UX flow:&lt;/p>
&lt;ul>
&lt;li>Enter a location and a radius, and press search to find green spaces within that radius around the location&lt;/li>
&lt;li>Choose a green space from the list as your start and end point&lt;/li>
&lt;li>Choose a desired route length&lt;/li>
&lt;li>View the route on the map and a summary of the route&lt;/li>
&lt;/ul>
&lt;h2 id="deploying-with-vercel">&lt;/h>Deploying with Vercel&lt;a href="#deploying-with-vercel">
&lt;/a>
&lt;/h2>&lt;p>I have used &lt;a href="https://vercel.com/" target="_blank" rel="noopener">Vercel&lt;/a> quite a bit now — this site is deployed there, along with a few other projects of mine. I hadn&amp;rsquo;t use its &amp;ldquo;serverless functions&amp;rdquo; feature before though: essentially it gives you a very simple way to deploy a monorepo of frontend together with backend APIs it communicates with, without having to manage a separate deployment for the backend or worry about actual servers. It works fantastically well and was really easy to set up.&lt;/p>
&lt;h2 id="a-word-about-vibe-coding">&lt;/h>A word about &amp;ldquo;vibe coding&amp;rdquo;&lt;a href="#a-word-about-vibe-coding">
&lt;/a>
&lt;/h2>&lt;p>I leaned heavily into AI coding tools for this project, mostly to see what they are capable of and understand if they really live up to the (rather incredible) amounts of hype that they are receiving at the moment. I used &lt;a href="https://www.cursor.com/en" target="_blank" rel="noopener">Cursor&lt;/a>, together with the Anthropic API running mostly Claude Sonnet 3.7, which I have found gives the best results for code at the moment. I spent about $15USD on API tokens and about four hours altogether getting this up and running, and mostly operated on the &lt;a href="https://simonwillison.net/2025/Mar/6/vibe-coding/" target="_blank" rel="noopener">&amp;ldquo;vibe coding&amp;rdquo;&lt;/a> principle of letting the LLM write pretty much all of the code, including the configurations for the Vercel deployment. It did a pretty decent job of everything except the single part of this project which could be considered new or original: the routing algorithm that should make interesting &amp;ldquo;green chain&amp;rdquo; routes through public green spaces. So it seems like writing that part of the application is on me, and rightly so.&lt;/p>
&lt;p>The routing algorithm still needs work: the routes it produces are often a bit &amp;ldquo;unnatural&amp;rdquo;: sometimes they cross over themselves, sometimes they avoid very obvious waypoints in favour of a main road. I think getting interesting, &amp;ldquo;naturally green&amp;rdquo; routes out of the application will be a proper challenge that will require adding additional things like entry and exit points for parks, and much more directionality information to the algorithm.&lt;/p>
&lt;p>It is however very nice to have something tangible that exists after such a short time, and I think that this is the best use case for LLMs right now. I would have no idea how to use them to get a routing algorithm that works like I would expect from the finished, polished version of the product I am trying to build here. But they were able to get a &amp;ldquo;minimal viable&amp;rdquo; version (depending on how you define viable) in a fraction of the time it would have taken me to create it from scratch by hand. That&amp;rsquo;s definitely valuable, but it&amp;rsquo;s still not something that can replace getting your hands dirty and &lt;a href="https://clintonboys.com/posts/how-will-llms-take-our-jobs/">doing the actual work&lt;/a>.&lt;/p></description></item><item><title>How will LLMs take our jobs?</title><link>https://clintonboys.com/posts/how-will-llms-take-our-jobs/</link><pubDate>Sun, 16 Mar 2025 21:21:46 -0500</pubDate><guid>https://clintonboys.com/posts/how-will-llms-take-our-jobs/</guid><description>&lt;h1 id="how-will-llms-take-our-jobs">&lt;/h>How will LLMs take our jobs?&lt;a href="#how-will-llms-take-our-jobs">
&lt;/a>
&lt;/h1>&lt;p>&lt;em>March 16, 2025&lt;/em>&lt;/p>
&lt;p>It&amp;rsquo;s a pretty inescapable take these days in tech circles: if you don&amp;rsquo;t want to get left behind, you need to be using &lt;a href="https://en.wikipedia.org/wiki/Large_language_model" target="_blank" rel="noopener">LLMs&lt;/a>, and become well-versed in how dramatically they can improve your productivity as a developer. Besides their utility as &amp;ldquo;next generation&amp;rdquo; search engines and replacements for online knowledge bases like StackOverflow, according to many, it is now possible, or will soon be possible, depending on how you look at it, to use these models to generate large portions of production codebases.&lt;/p>
&lt;h2 id="llms-at-work">&lt;/h>LLMs at work&lt;a href="#llms-at-work">
&lt;/a>
&lt;/h2>&lt;p>Of course, as of today at least, we are still not at the stage where large amounts of production code &lt;em>is&lt;/em> being generated by LLMs. There&amp;rsquo;s still a few legal and ethical hurdles to overcome before the cat is really out of the bag there I think: since any company that actually values its own intellectual property will not allow its employees to upload source code into third party servers, the ability for companies to train and serve their own LLMs, built around rapidly improving open source models, will have to progress significantly.&lt;/p>
&lt;p>Closer to home though, I read a lot of tech blogs and comments on &lt;a href="https://news.ycombinator.com" target="_blank" rel="noopener">HackerNews&lt;/a> and &lt;a href="https://lobste.rs" target="_blank" rel="noopener">Lobsters&lt;/a>, and am seeing a constant stream of posts and write-ups of people using LLMs to complete side projects. Since proprietary models are usually being used here (Claude, ChatGPT, etc), these sorts of projects can give us a glimpse at what may be the future of coding at work. The consensus seems to be that rather than a side project being some sort of idea you have, then spend a couple of hours on, maybe learn a few things, but quickly get distracted by life or a new side project, you can now just chuck your idea into the model and after a couple of hours of iterating you have a working project.&lt;/p>
&lt;p>To me, this all seems to point to the fact that we are currently in the middle of a significant paradigm shift, akin to the transition from writing assembly to compiled programming languages. A potential future is unfolding before our eyes in which programmers don&amp;rsquo;t write in programming languages anymore, but write in &lt;em>natural language&lt;/em>, and generative AI handles the gruntwork of actually writing the code, the same way a compiler translates your C code into machine instructions. (It’s worth noting that this workflow of “greenfield” projects is not really that similar to how most “at work” coding is done, where most tasks require changes to established “legacy” codebases rather than churning out thousands of tokens of new code).&lt;/p>
&lt;p>This begs the question: what of the future of our livelihoods as programmers? Is a future of writing prompts and wrangling with LLMs something that you could concievably continue to call &amp;ldquo;engineering&amp;rdquo;? Will there be a time that software becomes so commodified that this job so many of us love will cease to exist, at least the way we have come to know it over the past half a century?&lt;/p>
&lt;p>I think there are still an awful lot of unknowns at this point, we are probably at or close to the peak of the hype cycle so it&amp;rsquo;s very hard to see the future in between all the bullshit, invented use cases, marketing speak and VC money. But I think it&amp;rsquo;s at least possible to identify several possible scenarios, which I&amp;rsquo;ll go into in a bit of detail below, noting that any possible future from this point is probably going to be some linear combination of them and not any single definite path.&lt;/p>
&lt;h2 id="give-up">&lt;/h>Give up&lt;a href="#give-up">
&lt;/a>
&lt;/h2>&lt;p>If you don’t like the look of where these changes are taking us, you can always go and learn a new skill set which you are betting is immune to them: gardening, carpentry, plumbing, psychotherapy, construction. This has been a desirable path for many burnt out tech folks over the years, even before the latest AI hype wave, as it can be exhausting to keep up with the latest changes, keep yourself current and relevant, particularly as you get older, and as the hype waves become more and more depressing in terms of the vision they present for our collective future as a species. Presumably this path will be attractive to those who have already reached financial independence through their career in technology and have the privilege to switch off and go and build chairs or milk cows (or both).&lt;/p>
&lt;h2 id="join-em">&lt;/h>Join &amp;rsquo;em&lt;a href="#join-em">
&lt;/a>
&lt;/h2>&lt;p>If you can&amp;rsquo;t give up, like most people with families and mortgages and other obligations in the real world can&amp;rsquo;t, or you don&amp;rsquo;t want to, you can always try to be one of the folks creating the models and the value from the models. Surely this is a career path with its peak still to come. It is currently extremely lucrative but there will be more and more jobs in these kind of roles in the coming decade. Good luck sorting the real, interesting and challenging opportunities from the hype though, and note that the learning curve here is very high, the base skills required very different from your average tech job (maths, stats, ML, GPU computing) and that it’s currently an &lt;em>extremely&lt;/em> competitive market attracting the best of the best talent.&lt;/p>
&lt;h2 id="climb-the-ladder">&lt;/h>Climb the ladder&lt;a href="#climb-the-ladder">
&lt;/a>
&lt;/h2>&lt;p>Another option for folks with the right dispositions is to spend more effort trying to climb the ladder into management positions, since presumably these will be immune for longer to any disruptive changes caused by the commodification of the actual &amp;ldquo;code generation&amp;rdquo; process.&lt;/p>
&lt;p>I like the description that Nate Silver gives in &lt;a href="https://www.natesilver.net/p/sbsq-17-how-should-you-prepare-for" target="_blank" rel="noopener">this post&lt;/a>, where the &amp;ldquo;market value $V$ of [a knowledge worker is] dictated by the function&amp;rdquo; $V= G\times S \times P$, where $G$ is general intelligence, $S$ is domain knowledge and $P$ is &amp;ldquo;soft&amp;rdquo; skills — communication, management of people, and so on. They are &amp;ldquo;multiplicative &amp;hellip; because any of [them] are potentially limiting factors [on the others]&amp;rdquo;. So in a world where the first two multiplicands are devalued, the third becomes a lot more valuable. Presumably those &amp;ldquo;further up the ladder&amp;rdquo; will benefit more from this shift, since they are (at least ostensibly) there because of their high values of $P$.&lt;/p>
&lt;h2 id="cross-your-fingers">&lt;/h>Cross your fingers&lt;a href="#cross-your-fingers">
&lt;/a>
&lt;/h2>&lt;p>You could also cross your fingers and hope it pans out differently — particularly if, &lt;a href="https://clintonboys.com/posts/this-is-your-brain-on-chatgpt/">like me&lt;/a> you find the vision of the future spruiked by the most bullish LLM proponents a little ghoulish and offensive to our collective humanity. After all, there&amp;rsquo;s still only a very small chance (in my opinion at least) that the current technologies get us to the coveted &lt;a href="https://en.wikipedia.org/wiki/Artificial_general_intelligence" target="_blank" rel="noopener">AGI&lt;/a> goal, which would truly be a world-changing technological revolution surpassing the Industrial Revolution or the invention and adoption of the internet. So there are three subpaths here:&lt;/p>
&lt;h3 id="ai-winter">&lt;/h>AI winter&lt;a href="#ai-winter">
&lt;/a>
&lt;/h3>&lt;p>Hope this hype wave leads to another “AI winter”, like it has before, in which case traditional tech could remain the way it is for another boom/bust cycle or two. Each passing day that generative AI companies burn millions in cash without an actual profitable business model make this scenario incrementally more likely. On the other hand, each improvement in LLM capabilities, reduction in cost, and scale breakthrough make it less likely. There are a lot of people waiting to see how all this is going to pan out.&lt;/p>
&lt;h3 id="ai-summer">&lt;/h>AI &amp;ldquo;summer&amp;rdquo;&lt;a href="#ai-summer">
&lt;/a>
&lt;/h3>&lt;p>I&amp;rsquo;m not sure of a better name for this scenario. But it&amp;rsquo;s interesting to note that in the &amp;ldquo;LLMs are just one level of abstraction further up, like moving from assembly to compiled languages&amp;rdquo; analogy I mentioned in the introduction, there are a lot more programmers making a living today writing Python and Javascript than ever made a living writing x86 instructions. Maybe this change &amp;ldquo;democratises&amp;rdquo; programming and software, and opens up a whole host of new opportunities while keeping the &amp;ldquo;old guard&amp;rdquo; employed as the &amp;ldquo;brokerage&amp;rdquo; layer between the old and the new (see &amp;ldquo;Linux greybeards&amp;rdquo; as the example from the previous generation).&lt;/p>
&lt;h3 id="long-shot">&lt;/h>Long-shot&lt;a href="#long-shot">
&lt;/a>
&lt;/h3>&lt;p>Be part of one of the efforts trying to get to AGI with a different approach (&amp;ldquo;agents&amp;rdquo;, semantics, quantum, something else entirely). Of course you need to pick which of these approaches you think has the highest likelihood of success, and then have it succeed, which is a sort of startup-like trajectory that will not sound appealing to the risk-averse. It also requires unconventional skillsets similar to the &amp;ldquo;join &amp;rsquo;em&amp;rdquo; scenario above.&lt;/p>
&lt;h2 id="make-sourdough">&lt;/h>Make sourdough&lt;a href="#make-sourdough">
&lt;/a>
&lt;/h2>&lt;p>Finally, you could bet on the “human-made becomes artisanal” path: where software and technology untouched by generative AI develops a premium image and a path for continued employment for talented “old school” developers willing to put in the effort and resist the hype. Presumably when all our food is made by robots, rich folks with a nostalgic bent will pay top dollar for food made by actual human chefs: perhaps the same will turn out to be true for software.&lt;/p>
&lt;h2 id="conclusion">&lt;/h>Conclusion&lt;a href="#conclusion">
&lt;/a>
&lt;/h2>&lt;p>I don&amp;rsquo;t know how any of this is going to play out, so it&amp;rsquo;s hard to decide myself which of the above scenarios to invest efforts in to ensure an interesting career with the potential for growth, learning and fulfillment. For now I am trying to keep my options open, to make sure I am keeping up with this very fast-changing field and at least theoretically remaining able to “pivot” if I see things going in a certain direction I think is more definite.&lt;/p>
&lt;p>I said a few times above that thinking about the LLM future depresses me: I suppose this is mostly because I really like playing around with computers and using them to build interesting things, and these models feel like they are going to ruin a bit of that fun. I like the feeling of being creative and inventive, and these models give us the illusion that a computer can do that for us, when really all it is doing is garbling together some high-dimensional average of what other people have done before. In this environment, the premium for creativity and “truly new” ideas will presumably become even higher, as anything that can be generated by this averaging process becomes commoditised. So if I have to focus on one thing, it will be this: trying to remain creative and original, and not succumbing to the urge to use LLMs to do that kind of work for me. I think this strategy, alongside continuing to keep up with developments in the space, learning how to use whichever tools gain wide adoption and maintaining a solid understanding of the strengths and weaknesses of this current wave of AI, leaves as many of the potential paths above open as possible during this uncertain and turbulent time.&lt;/p></description></item><item><title>My setup (2025)</title><link>https://clintonboys.com/posts/my-setup-2025/</link><pubDate>Sat, 04 Jan 2025 11:21:46 -0500</pubDate><guid>https://clintonboys.com/posts/my-setup-2025/</guid><description>&lt;h1 id="my-setup">&lt;/h>My setup&lt;a href="#my-setup">
&lt;/a>
&lt;/h1>&lt;p>&lt;em>January 2025&lt;/em>&lt;/p>
&lt;p>In this post I answer the interview questions from &lt;a href="http://usesthis.com/" target="_blank" rel="noopener">uses this&lt;/a> — again. This is an updated version of &lt;a href="https://clintonboys.com/posts/my-setup-2021/">this post&lt;/a> which I wrote in 2021, and is now the fifth time I have answered these questions. I have enjoyed reading &lt;em>uses this&lt;/em> for many years and I like having a little archive of my own setups — you can see the five times I have done this exercise &lt;a href="https://clintonboys.com/categories/setup/">here&lt;/a>.&lt;/p>
&lt;h2 id="who-are-you-and-what-do-you-do">&lt;/h>Who are you and what do you do?&lt;a href="#who-are-you-and-what-do-you-do">
&lt;/a>
&lt;/h2>&lt;p>I&amp;rsquo;m an Australian mathematician and programmer living in London. I&amp;rsquo;m currently working at &lt;a href="https://ridewithvia.com" target="_blank" rel="noopener">Via&lt;/a>. Outside of work, I enjoy reading, writing, listening to and making music, and spending time with my family.&lt;/p>
&lt;h2 id="what-hardware-do-you-use">&lt;/h>What hardware do you use?&lt;a href="#what-hardware-do-you-use">
&lt;/a>
&lt;/h2>&lt;p>I have two 14&amp;quot; MacBook Pros from 2021 with the M1 Pro &amp;ldquo;Apple Silicon&amp;rdquo; processor — one from work, and one for my personal use. I never mix the two, never sign into personal accounts (iCloud, email, whatever) on my work computer, and vice versa. The M1 MacBook Pro is definitely the best computer I&amp;rsquo;ve ever owned — I wrote up a bit more about why I love it &lt;a href="https://www.mtsolitary.com/20220328150308-m1_pro_macbook_pro/" target="_blank" rel="noopener">here&lt;/a>. At my desk at home, I have my work computer hooked up to a 27&amp;quot; Lenovo monitor, and my personal computer to the side for playing music through a Behringer UR-22 audio interface and some cheap but decent Samson BT3 studio monitors, which I also use in my spare time to record music, when I get the chance. My recording setup is simple: alongside the UR-22 I use Shure SM58 and 57 microphones and a Yamaha P-121 digital piano. It&amp;rsquo;s a basic setup but it works for the basic stuff I do.&lt;/p>
&lt;p>
&lt;img src="https://clintonboys.com/posts/my-setup-2025/IMG_3506_hue7f0e4113135936cdd0ae411e19c0843_1517649_800x0_resize_q100_h2_box_3.webp"
width="800"
height="450"
alt="My setup"
class="single-post-image"
loading="lazy"
decoding="async"
>
&lt;/p>
&lt;p>Also on my desk is my trusty &lt;a href="https://en.wikipedia.org/wiki/Network-attached_storage" target="_blank" rel="noopener">NAS&lt;/a> — a Synology server with lots of storage which functions as a backup and media server for my household.&lt;/p>
&lt;p>I have an iPhone 14 Pro, which I use to take all my photos these days (though many of the older photos on this site were taken with an Olympus E-PM1). An 11&amp;quot; iPad Air gets used when I&amp;rsquo;m making and recording music, or for video calls to relatives in far-away lands. I listen to music through AirPods Pro, which I also have in my ears most of the day for remote meetings at work. My home stereo which gets a lot of use is a half-decent Sonyo receiver hooked up to some Wharfedale speakers, a fairly basic record player and an Apple TV. I also have an Apple Watch.&lt;/p>
&lt;h2 id="and-what-software">&lt;/h>And what software?&lt;a href="#and-what-software">
&lt;/a>
&lt;/h2>&lt;p>I use &lt;a href="https://clintonboys.com/posts/getting-started-with-emacs/">emacs&lt;/a> to edit almost all of my text files, which is the majority of what I do with my personal computer these days (writing code is also editing text files). I use org mode and org agenda to manage all my tasks, to dos and notes, with the excellent Beorg app on iOS to handle everything through my phone. See &lt;a href="https://www.mtsolitary.com/20210309194647-my-org-mode-setup/" target="_blank" rel="noopener">here&lt;/a> for a detailed write-up about my org-mode setup. All my files and photos are backed up at least three times — you can read about my backup solution &lt;a href="https://www.mtsolitary.com/20230507063159-backups_public/" target="_blank" rel="noopener">here&lt;/a>.&lt;/p>
&lt;p>I browse the web in Safari and listen to music in Apple Music, where I have a huge amount of custom playlists I&amp;rsquo;ve put a lot of work into and am very happy with. For recording music I use Logic Pro.&lt;/p>
&lt;p>I also use a bunch of utilities to keep my computer sane: &lt;a href="https://justgetflux.com" target="_blank" rel="noopener">f.lux&lt;/a> so my eyes don&amp;rsquo;t get burnt out, &lt;a href="https://www.alfredapp.com" target="_blank" rel="noopener">Alfred&lt;/a> to launch applications, &lt;a href="https://apps.apple.com/fi/app/divvy-window-manager/" target="_blank" rel="noopener">Divvy&lt;/a> to move windows around using keyboard commands and &lt;a href="https://github.com/bayashi/mclocks" target="_blank" rel="noopener">mclock&lt;/a> to keep two time zones in my menu bar.&lt;/p>
&lt;p>On my phone, apart from the obvious stuff like Messages, Whatsapp (ugh), Mail, Photos, Telegram and Phone, my homescreen consists of Calendar, (Apple) Music, a bunch of navigation apps, Safari, Files, beorg for my todo lists and tasks (though I am also venturing back to Things lately), and iAWriter for editing Markdown on the go. I deleted all my social media accounts years ago, so I also have Apple Books and the Kindle app for reading on my phone.&lt;/p>
&lt;h2 id="what-would-be-your-dream-setup">&lt;/h>What would be your dream setup?&lt;a href="#what-would-be-your-dream-setup">
&lt;/a>
&lt;/h2>&lt;p>This has been the same since I first wrote it in 2015. An infinitely fast and thin laptop computer, with infinite battery and high-speed internet access everywhere. Perfectly comfortable headphones I can wear all day, and a completely lossless music collection. Apart from that I&amp;rsquo;m pretty happy with how things are at the moment: slowly but surely getting better, just like me.&lt;/p></description></item><item><title>Hugo migration</title><link>https://clintonboys.com/projects/hugo-migration/</link><pubDate>Wed, 01 Jan 2025 03:52:30 -0500</pubDate><guid>https://clintonboys.com/projects/hugo-migration/</guid><description>&lt;h1 id="migrating-my-personal-site-to-hugo">&lt;/h>Migrating my personal site to Hugo&lt;a href="#migrating-my-personal-site-to-hugo">
&lt;/a>
&lt;/h1>&lt;p>&lt;em>January 1, 2025&lt;/em>&lt;/p>
&lt;p>As foreshadowed in my recent &lt;a href="https://clintonboys.com/posts/ten-years/">post&lt;/a> reflecting on ten years of running my personal website, I recently migrated the site to &lt;a href="https://gohugo.io/" target="_blank" rel="noopener">Hugo&lt;/a> after many years of blogging with Jekyll&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>. I used the &lt;a href="https://github.com/wjh18/hugo-liftoff?tab=readme-ov-file" target="_blank" rel="noopener">Liftoff&lt;/a> theme by &lt;a href="https://github.com/wjh18" target="_blank" rel="noopener">Will Holmes&lt;/a> as my starting point, and ended up making a large number of customisations — mostly in the CSS, but also some changes a bit deeper in the templating structure, to get back to a similar look and feel that I had in my old site.&lt;/p>
&lt;p>There were quite a few reasons for the change:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Something new&lt;/strong>. I enjoy constantly reshaping, rewriting and rebuilding things I have made. It means I am continuously polishing and improving this site, shaving off little things that shouldn&amp;rsquo;t be there, going over all the copy again, and just generally spending time honing something — &lt;a href="https://blog.jim-nielsen.com/2024/sanding-ui/" target="_blank" rel="noopener">sanding&lt;/a> it — which is a very calming and enjoyable pastime for me.&lt;/li>
&lt;li>&lt;strong>Portfolio&lt;/strong>. This was the big one. My old Jekyll theme (which was a &lt;em>heavily&lt;/em> customised fork of a very old version of the &lt;a href="https://mmistakes.github.io/minimal-mistakes/" target="_blank" rel="noopener">Minimal Mistakes&lt;/a> theme by &lt;a href="https://mademistakes.com/" target="_blank" rel="noopener">Michael Rose&lt;/a>) didn&amp;rsquo;t have a built-in portfolio section, and I didn&amp;rsquo;t really feel like sinking time into learning enough Jekyll to shoehorn one in myself. My new site has a shiny &lt;a href="https://clintonboys.com/projects/">portfolio&lt;/a> section, with pride of place in the top navigation bar, showcasing a bunch of projects I have managed to finish (or half finish) in some form over the years.&lt;/li>
&lt;li>&lt;strong>Dark mode&lt;/strong>. It seems like you can&amp;rsquo;t have a techy personal website these days without a fancy switch that toggles between light and dark mode, defaulting to the setting of the user&amp;rsquo;s browser. The Liftoff theme came with this built in, so all I had to do was tweak the exact theme colours to my liking. I ended up using the same background colour for dark mode that I have used for five years over at &lt;a href="https://www.mtsolitary.com/" target="_blank" rel="noopener">Mt Solitary&lt;/a>, my digital garden.&lt;/li>
&lt;li>&lt;strong>Hugo&lt;/strong>. I chose Jekyll ten years ago, but Hugo feels a lot more modern, is significantly faster, and doesn&amp;rsquo;t have the Ruby dependency, which always gave me trouble. I also have used Hugo to successfully build &lt;a href="https://clintonboys.com/projects/nitzanboys-com/">other&lt;/a> &lt;a href="https://clintonboys.com/projects/mt-solitary/">websites&lt;/a> in the last few years so I&amp;rsquo;ve built up a bit of experience and comfort with it. It just feels a lot lighter and more future-proof.&lt;/li>
&lt;li>&lt;strong>Reading time indication&lt;/strong>. Another nice little feature of the theme I built around was a built-in reading time indication for &lt;a href="https://clintonboys.com/posts/">posts&lt;/a>. Of course this would have been easy to add myself, but it was nice that it came out of the box.&lt;/li>
&lt;li>&lt;strong>Tags&lt;/strong>. There is also built-in support for tags (called &amp;ldquo;categories&amp;rdquo;) in Liftoff, which means you automatically get pages like &lt;a href="https://clintonboys.com/categories/emacs/">these&lt;/a>.&lt;/li>
&lt;li>&lt;strong>Vercel&lt;/strong>. I had already done this in my ten-year revamp of the Jekyll site, but deployment is now done through &lt;a href="https://vercel.com" target="_blank" rel="noopener">Vercel&lt;/a> instead of GitHub Pages. Vercel is a bit more flexible, offers a nice interface for managing multiple projects at once, and offers a very generous free &amp;ldquo;hobbyist&amp;rdquo; plan for personal project sites like mine.&lt;/li>
&lt;/ul>
&lt;p>Here&amp;rsquo;s a picture of the two sites side-by-side. On the left, the new Hugo site (in light mode), on the right, the old Jekyll site (now archived &lt;a href="https://clintonboys-github-io.vercel.app" target="_blank" rel="noopener">here&lt;/a>). You can see the overall design is quite similar.&lt;/p>
&lt;p>
&lt;img src="https://clintonboys.com/projects/hugo-migration/side_by_side_hu3ecd947c97d964afdeabb37601a15c57_1610895_800x0_resize_q100_h2_box_3.webp"
width="800"
height="448"
alt="Side by side comparison"
class="single-post-image"
loading="lazy"
decoding="async"
>
&lt;/p>
&lt;p>As for the list of things I &lt;em>lost&lt;/em> in the migration:&lt;/p>
&lt;ul>
&lt;li>In the old site I had an author information sidebar on each post, which included email, LinkedIn and GitHub links, and a profile picture on the non-article pages.&lt;/li>
&lt;li>Feature images are now smaller and do not spread to the full width of the window.&lt;/li>
&lt;li>Text on large screens has gone from 560px to 900px wide.&lt;/li>
&lt;li>There are no longer year groupings in the blog section.&lt;/li>
&lt;/ul>
&lt;p>I think most of these are actually desirable changes — I prefer the slightly wider look and I had got a bit sick of looking at my face all the time. If people want to see what I look like, they can click through to my LinkedIn or GitHub profiles from the &lt;a href="https://clintonboys.com/about/">about&lt;/a> page and see it there.&lt;/p>
&lt;p>I did the migration very manually, copying pages over one by one and fixing up things that needed to be fixed. A few times during the migration of over fifty pages (and over 60,000 words!), I thought about trying to automate some of the migration, but in the end I found it a much more meditative process to go over everything manually. It also let me catch a few decade-old typos. For each page, the things I had to change were&lt;/p>
&lt;ul>
&lt;li>front matter and feature image&lt;/li>
&lt;li>mathematics typesetting&lt;/li>
&lt;li>internal links&lt;/li>
&lt;/ul>
&lt;p>The things I had to tweak globally were&lt;/p>
&lt;ul>
&lt;li>theme colours&lt;/li>
&lt;li>web fonts&lt;/li>
&lt;li>mathematics typesetting&lt;/li>
&lt;li>Goatcounter support&lt;/li>
&lt;li>completely rejigged the navigation bar&lt;/li>
&lt;li>changed the &amp;ldquo;feature image&amp;rdquo; to be more like I had on my old site, including an image caption&lt;/li>
&lt;li>migrate some posts to a &amp;ldquo;project&amp;rdquo; format instead of a &amp;ldquo;post&amp;rdquo; format&lt;/li>
&lt;/ul>
&lt;p>I enjoyed this project, it really forced me to take a deep look at my online presence and rethink how I want it to look for the decade to come. The end result is something that feels really suited to me, and I&amp;rsquo;m very happy with it.&lt;/p>
&lt;h2 id="footnotes">&lt;/h>Footnotes&lt;a href="#footnotes">
&lt;/a>
&lt;/h2>&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>Hugo and Jekyll are both &amp;ldquo;&lt;a href="https://en.wikipedia.org/wiki/Static_site_generator" target="_blank" rel="noopener">static site generators&lt;/a>&amp;rdquo; (SSGs). If you don&amp;rsquo;t know what that means, unfortunately this post will probably not mean a whole lot to you.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item></channel></rss>