Things I've built

Mostly one self-hosted estate: close to thirty services behind a single sign-on provider, on hardware I run. The short version of each is what it does; the longer version is why it was harder than it looks.

Octopus Auth

Single sign-on for every service on the estate — TOTP, recovery codes, and revocable sessions.

Every app behind one login. Two-factor is mandatory and enforced at enrolment, so an account cannot exist without it. Sessions are revocable without storing a single token: each one carries the epoch it was minted under, and bumping a counter on the user orphans all of them at once — no session table, no denylist to prune.

Signing moved from a shared HMAC secret to RS256, because with a shared secret the key that verifies a token also mints one — any of the six services holding it could have forged an admin session. The migration runs live: verification accepts both algorithms at once so week-old sessions keep working, and keys carry an id so rotation needs no flag day. An earlier version of that change refused to boot when half-configured, which turned an incomplete migration into an outage; it now derives what it needs from the key it already has.

  • Mandatory TOTP with single-use recovery codes
  • Session revocation with no server-side session store
  • Live HS256 → RS256 migration, no forced logout
  • 84 tests, including JWT algorithm-confusion forgery
JavaScript

updated today · 62 commits · 113 tests

Auth Client

The shared auth contract, as an installable package with an executable specification.

Five services had each written their own copy of "check this session", which is five chances to get one security decision wrong — and the reason a revocation feature could not be rolled out fleet-wide: none of them ran the same code.

The rule that justifies the package: a valid signature does not mean the caller is logged in. The auth service issues short-lived tokens partway through login, signed with the same key, and accepting one as a session would let a stolen password through and defeat two-factor entirely. The test suite is the specification — anyone writing auth without this package has to satisfy the same cases.

  • One implementation of the session contract, not five
  • Tests are the spec; publishing is gated on them
  • Fails closed when the authority is unreachable
JavaScript

updated today · 13 commits · 90 tests

See more on GitHub

Octopus EDM

A browser music workstation with neural synthesis and a live round-trip into FL Studio.

Step sequencer, piano roll, mixer and sample browser built on the Web Audio API, with lookahead scheduling so timing survives the main thread being busy. Sample search is semantic rather than filename matching — an audio-text embedding model indexes the library, so "dark supersaw" finds one.

It talks to FL Studio through its piano-roll scripting API, with a shared pattern document as the source of truth so edits compose in either direction. There is also a MIDI file writer implemented from the specification rather than pulled from a library, which matters more than it sounds: note-off has to sort before note-on when one note ends exactly where the next begins, or the file plays one long stuck note. It is installable to a phone and works offline.

  • Web Audio lookahead scheduler
  • Semantic sample search via audio-text embeddings
  • Standard MIDI File writer, no dependencies
  • Installable PWA with an auth-safe service worker
TypeScript JavaScript Python

updated 20 days ago · 63 commits

Octopus Cortex

An AI assistant with real tools, reachable from Discord and the web.

Not a chat wrapper. It plans, calls tools against the actual estate, and reports what it did — file operations, service checks, generating musical patterns that land in the workstation above. The interesting problems are the unglamorous ones: attributing work to the right account when a bot acts on someone's behalf, and making sure a request that fails does not take the process down with it.

  • Tool-calling orchestration with a planning step
  • Discord and web share one implementation
  • Per-user attribution for actions taken by a bot
JavaScript

updated today · 408 commits · 407 tests

Octopus Shopper

Recipe parsing and grocery price comparison across several retailers.

Parses a recipe from a URL or a block of pasted text into structured ingredients, then prices it. Some retailers have a signed API; others need a headless browser, which is slower and far more fragile, so the two paths are kept clearly separate.

Most of the real work has been in the data. Ingredients arrived in three different shapes from three eras of the code and 176 rows were invisible because of it. Quantities are stored as text, because "1 ½" and "to taste" are not numbers and the numeric column silently lost them.

  • Signed retailer APIs plus headless-browser fallback
  • LLM recipe parsing into a structured schema
  • Migrations that reconcile three historical data shapes
JavaScript CSS

updated today · 57 commits · 95 tests

Octopus Science

A chemistry study tool: periodic table, Lewis structures, and units that stay honest.

Built as a companion to a first-year chemistry text. It draws Lewis structures, balances equations, and reads a textbook PDF looking for one specific thing: places where a figure is quoted in SI and US units that do not actually agree.

The unit scanner is the part with judgement in it. Conversions are read from a single table rather than retyped per call site, and a guard fails the build if the two ever drift apart — a converter that is wrong in one direction only is the kind of bug that survives review. The periodic table was the fiddlier problem: the detail card lives in the table's own empty block, so it has to stay readable from a wide desktop down to a phone at 260% zoom without ever covering an element. That is arithmetic, not taste, and it is asserted against every tile at thirteen widths.

  • Detects SI/US unit disagreements in textbook figures
  • Conversions defined once, with a test that fails on drift
  • Detail card provably never overlaps a table cell, at any zoom
JavaScript CSS

updated today · 26 commits · 181 tests

Octopus EE

An electronics planner for a modular USB-MIDI controller, before anything is soldered.

A dual virtual breadboard tied to a real parts inventory and to ESP32-S3 pin rules. The point is to find the mistakes while they are still free — a wire between two holes on the same strip is refused with a reason rather than silently accepted, and the generated firmware carries the pins the project actually assigns instead of a plausible default.

The original build plan was written without access to this platform and several of its load-bearing assumptions were simply wrong — it assumed an OIDC provider that does not exist here. Those corrections are recorded in the repository rather than quietly fixed, because the next person to read the plan will believe it otherwise.

  • Validates wiring against real ESP32-S3 pin constraints
  • Firmware generated from the project, not from a template
  • Superseded assumptions written down, not silently dropped
JavaScript CSS

updated yesterday · 88 commits · 213 tests

Octopus Planner

Project and canvas planning, and a migration that refused to guess.

The interesting part is not the planner, it is what happened when it gained multiple users. Rows created before ownership existed had no owner, and the obvious migration — backfill them to the most likely account — risks handing one person's work to somebody else, which is the exact thing being fixed.

So they belong to nobody until claimed, and an admin turning up is treated as proof enough of who was there first. To everyone else those rows do not exist. Asking for someone else's row returns a 404 rather than a 403 throughout, because a 403 confirms the row is there.

  • Additive migrations only — no table is rebuilt or dropped
  • Unowned rows are claimed, never guessed at
  • 404 rather than 403, so a probe learns nothing
JavaScript TypeScript CSS

updated today · 16 commits · 2 tests

Budget and Health

Two trackers that hold other people's data, and the boundary that keeps them apart.

Both give every account its own database file rather than a shared table with an owner column. It is the less fashionable choice and it is the right one here: the file is the boundary, so a query that forgets to filter is still correct, and the failure mode of forgetting is nothing rather than everyone's data.

That only holds while two people can never resolve to the same file, and the filename is built from the username. For a long time the only thing preventing a hostile name was a validation rule in a different service — invisible from the file relying on it. It is now enforced in both places, and refuses rather than sanitises, because stripping the bad characters would quietly map two people onto one database.

  • A database per account — the file is the isolation boundary
  • Path safety enforced where the data lives, not only upstream
  • Refuses unsafe input instead of cleaning it into a collision
JavaScript CSS

updated today · 94 commits · 15 tests

See more on GitHub

The estate

Close to thirty containerised services on NixOS, deployed from git.

A declarative host running containerised services, deployed by pulling from git rather than by anyone SSHing in — including an operations service that drives redeployments through the orchestrator's API and rotates credentials across stacks.

The lesson that cost the most: a variable set in the orchestrator only reaches a container if the compose file names it. Configuration that looks correct in the UI and never arrives is a genuinely nasty class of bug, because nothing errors — the service simply keeps using the old default while you believe you changed it.

  • NixOS host, git-managed container stacks
  • Redeploys and credential rotation over the orchestrator API
  • Everything behind one SSO provider

Alfred

The Discord bot this all started as.

Where the estate began, and still running. Kept on the page because the distance between this and the auth service is the actual story.

JavaScript

updated today · 76 commits · 2 tests

See more on GitHub

Fork me on GitHub! 

Fork mutable on GitHub! 

Fork Luci on GitHub!