Building a Childcare Calculator

Childcare calculator: Why a static page, and why not a spreadsheet - Part 1

~6 min read Childcare costs are one of those bills that look simple until you try to predict them. A childminder charges an hourly rate. You book some days. The government funds some of the hours. Bank holidays don't count. And somehow the number on the invoice is never the number you guessed. I wanted a way to type in my setup and see the month's cost, day by day, before the invoice arrived. This post is about the first decision: what to build it with, and what the first version actually did.
Andrew Townsend
Andrew Townsend
3 min read
Building Identity System

Central login service: Making Jenkins sign in - Part 5

~20 min read Part 4 ended with an OpenID Connect provider that passed its test suite, covered the whole Authorization Code flow, required PKCE, hashed its client secrets, and was, by the specification as I'd read it, correct. Then I pointed Jenkins at it, and the next half hour produced three commits with titles like fix jenkins login, fixing the fix further and Adding preffered username. This part is about those three commits. They're short (the three of them touch well under a hundred lines
Andrew Townsend
Andrew Townsend
12 min read
Building Finance System

Personal finance aggregator: Account balances and disconnecting banks cleanly - Part 8

~10 min read The next two pieces of this project both came from the same question: does the app actually reflect the full state of my accounts, in both directions - what's currently in them, and which ones I've actually chosen to keep connected. This post covers pulling real balances into the dashboard, and building a way to disconnect a bank that doesn't leave stale data lying around pretending to be current. Balances were already paid for TrueLayer's Data API has balance endpoints - /data/
Andrew Townsend
Andrew Townsend
7 min read

Building a 4-Zone Irrigation Controller with ESPHome - Part 2

ESPHome Configuration Here’s a sample YAML config for a 4-zone irrigation controller: esphome: name: esphome-web-806180 friendly_name: ESPHome Garden Irrigation Controller min_version: 2025.5.0 name_add_mac_suffix: false esp32: board: esp32dev framework: type: esp-idf # Enable logging logger: # Enable Home Assistant API # Enable Home Assistant API api: # Allow Over-The-Air updates ota: - platform: esphome text_sensor: - platform: version name: "ESPHome Version" h
Andrew Townsend
Andrew Townsend
3 min read
Building Identity System

Central login service: Becoming an OpenID Connect provider - Part 4

~28 min read Everything so far assumed one thing: that the app I'm protecting can sit behind my reverse proxy. The proxy asks the login service "is this request allowed?", the answer arrives as a status code and a few headers, and the app never has to know how any of it happened. Then I wanted Jenkins to use the same accounts. Jenkins can't play that game. It isn't a little app I wrote; it has its own idea of users, sessions and security realms, and it ships with a plugin that wants to speak O
Andrew Townsend
Andrew Townsend
16 min read
Building Finance System

Personal finance aggregator: Keeping data fresh: manual sync, resilience, and real stats - Part 7

~10 min read Everything up to this point assumed the nightly sync just works. It mostly does - right up until one bank connection has a problem, at which point the version of fetch() I'd had since the very first CLI prototype turned "one bank's data is temporarily unavailable" into "nobody's data updates tonight." This post is about the sync loop growing up: a manual trigger for when 3am is too long to wait, failure isolation so one bad connection can't take the rest down with it, error message
Andrew Townsend
Andrew Townsend
7 min read
Building Identity System

Central login service: An admin page and a proper frontend - Part 3

~25 min read By the end of Part 2 the login service was deployed and doing its job, and its entire user interface was one HTML form built from a Python string. That was fine for a service whose only visible surface was "type your password". It stopped being fine within a day, because I immediately wanted three things the form couldn't give me: 1. A way to see who has an account and when they last signed in. 2. A landing page listing the apps that sit behind the login, so the service is somew
Andrew Townsend
Andrew Townsend
15 min read
Building Finance System

Personal finance aggregator: Auth, two-factor, and account settings - Part 6

~10 min read Status: superseded. Everything described below was built, shipped and then deleted within about two days. On 28 September, two commits moved login, passwords and two-factor out of this app entirely and into a separate identity-system service that nginx consults before any request reaches the app. The post stays because the design reasoning (and the gaps it ran into) are the reason that move happened - the full story is in post 9. This is a single-user app sitting in front of real
Andrew Townsend
Andrew Townsend
7 min read
Building Identity System

Central login service: Shipping it - Jenkins, uv and a Debian container - Part 2

~25 min read Part 1 ended with a login service that passed its tests and ran on my laptop. That's the easy half. A login service that isn't reachable from the other machines in the house isn't a login service, and "reachable" brought a pile of small decisions and an unglamorous run of commits with messages like ci: add uv.lock, fix python missmatch and fix deploy script. This post is about all of that: the deployment target, the Jenkins pipeline, moving the build to uv, the rsync --delete that
Andrew Townsend
Andrew Townsend
14 min read
Building Finance System

Personal finance aggregator: Building the frontend: React, routing, and a design system - Part 5

~10 min read For a while, the frontend was one component: log in, see a table of transactions, link a bank from a text input. That was fine for checking the backend actually worked, but it wasn't a tool I wanted to open every day. This post covers turning that single page into a proper multi-page app - Home, Accounts, Transactions, Settings - with real navigation and a design system underneath it, instead of default browser styling on a scattering of <div>s. It's the work of one commit (Add rou
Andrew Townsend
Andrew Townsend
7 min read
Building Identity System

Central login service: One place to type a password - Part 1

~25 min read Every small tool I build for myself eventually needs a login. A finance tracker, an invoicing app, a dashboard for something plugged in to the wall. The first time, I wrote the whole thing inline: a user table, Argon2 hashing, sessions, a password-change form, two-factor enrolment with recovery codes. It worked, it was even fairly pleasant, and then I looked at the second app on my list and realised I was about to write all of it again. This series is about the project I built ins
Andrew Townsend
Andrew Townsend
16 min read
Building Finance System

Personal finance aggregator: From script to service - Part 4

~10 min read The CLI prototype proved the OAuth flow worked. It also had a very short list of things it fundamentally couldn't do: run anywhere but my laptop, keep transaction history instead of overwriting it, or be looked at by anything other than cat transactions.json. This post covers the two commits that fixed all three: init: basic backend/frontend, which turned the script into a FastAPI package with SQLite storage and typed settings, and Add TrueLayer web linking, provider/storage packag
Andrew Townsend
Andrew Townsend
9 min read