slides(tappaas): write the Sommerhack 2026 'one year in' deck
Talk announced at pretalx.varum.dk/sommerhack-2026/talk/WD8EVP/ — 27 August, Taler Teltet, 60 minutes. Figures come from the repo rather than the abstract: commit counts and the per-month graph from git log, source counts from src/STATISTICS.md as regenerated 2026-08-23. Twelve TODO markers remain for the personal material (costs, the autumn 2025 dip, war stories). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
87d6d83ff3
commit
ef00334f94
1 changed files with 712 additions and 0 deletions
712
slides/tappaas/one-year-in/index.md
Normal file
712
slides/tappaas/one-year-in/index.md
Normal file
|
|
@ -0,0 +1,712 @@
|
||||||
|
---
|
||||||
|
marp: true
|
||||||
|
theme: gaia
|
||||||
|
class: invert
|
||||||
|
paginate: true
|
||||||
|
title: TAPPaaS one year in — The Good, the Bad, and the Ugly
|
||||||
|
description: Sommerhack 2026, Taler Teltet — 27 August, 15:30
|
||||||
|
---
|
||||||
|
|
||||||
|
<style>
|
||||||
|
section { font-size: 26px; }
|
||||||
|
h1 { font-size: 1.5em; }
|
||||||
|
h2 { font-size: 1.15em; }
|
||||||
|
pre { font-size: 0.7em; line-height: 1.35; }
|
||||||
|
table { font-size: 0.78em; }
|
||||||
|
th, td { padding: 0.2em 0.55em; }
|
||||||
|
/* Unfinished content, loud on purpose: an unfinished deck should never be
|
||||||
|
presented by accident. Written in the markdown as `TODO: ...`. */
|
||||||
|
code.todo { color: #ff8a80; font-weight: 600; }
|
||||||
|
/* Mermaid renders at its natural size, which is far too small on a projector. */
|
||||||
|
div.mermaid { display: flex; justify-content: center; width: 100%; }
|
||||||
|
div.mermaid svg { width: 100% !important; height: auto !important; max-height: 500px; }
|
||||||
|
section.diagram h2 { margin-bottom: 0.1em; }
|
||||||
|
/* Section dividers: gaia's `lead` does the centring; this only sets the scale. */
|
||||||
|
section.chapter h1 { font-size: 2.6em; margin-bottom: 0.1em; }
|
||||||
|
/* Punch lines: the slides that are one sentence, said loudly. */
|
||||||
|
section.punch h1 { font-size: 2.1em; line-height: 1.15; }
|
||||||
|
section.chapter p { font-size: 1.15em; opacity: 0.75; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- _paginate: false -->
|
||||||
|
|
||||||
|
# TAPPaaS — one year in
|
||||||
|
|
||||||
|
## The Good, the Bad, and the Ugly
|
||||||
|
|
||||||
|
**Lars Rossen** · Sommerhack 2026 · Taler Teltet
|
||||||
|
27 August, 15:30
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Sixty minutes. Roughly: 8 opening, 18 Good, 14 Bad, 14 Ugly, 6 close.
|
||||||
|
Leave the last 5 for questions from the tent — this crowd will have them.
|
||||||
|
|
||||||
|
Tone: this is a confession, not a product pitch. The proof is the mess.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Last year, in this tent, I pitched a dream
|
||||||
|
|
||||||
|
> Everyone needs a cloud in their basement.
|
||||||
|
> A **T**rusted, **A**utomated, fully **P**rivate **P**latform **a**s **a** **S**ervice.
|
||||||
|
|
||||||
|
On commodity hardware:
|
||||||
|
|
||||||
|
- your data, your hardware
|
||||||
|
- no hidden fees
|
||||||
|
- no reliance on closed source
|
||||||
|
- **no reliance on the Internet**
|
||||||
|
|
||||||
|
A year later I am back with the messy proof that it is real —
|
||||||
|
and to make the case that you should build one too.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Read the four bullets slowly. The fourth is the one that sounds mad and turns
|
||||||
|
out to be the whole point — come back to it in the AI section.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The receipts, up front
|
||||||
|
|
||||||
|
| | |
|
||||||
|
| --- | --- |
|
||||||
|
| First commit | **10 May 2025** — "Initial commit" |
|
||||||
|
| Commits when I submitted this abstract | **1,091** |
|
||||||
|
| Commits standing here today | **1,488** |
|
||||||
|
| Contributors | 3 (I am ~79% of it) |
|
||||||
|
| ADRs written | **24** |
|
||||||
|
| Lines of code (bash · TypeScript · Python · Nix) | **111,777** |
|
||||||
|
| Of which **test** code | **30,282** — 29% |
|
||||||
|
| Lines of documentation | **~34,000** |
|
||||||
|
| App modules in the tree | **13** |
|
||||||
|
|
||||||
|
Everything in this talk is in a public repository. You can check my homework.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
The 1091 → 1488 jump is the joke that lands: I wrote the abstract in the
|
||||||
|
spring and the project kept going. Point at it — and at "13 app modules"
|
||||||
|
when the abstract promised 8. Figures: src/STATISTICS.md, 2026-08-23.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What I am actually going to do to you
|
||||||
|
|
||||||
|
1. **The Good** — it runs. Here is what "it runs" means, and one surprise.
|
||||||
|
2. **The Bad** — what this really cost. The commit graph is not pretty.
|
||||||
|
3. **The Ugly** — the confession. I gave an AI root on every node.
|
||||||
|
4. **Your turn** — why you should build one, and how to start small.
|
||||||
|
|
||||||
|
I am not selling anything. There is nothing to buy.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- _class: invert lead chapter -->
|
||||||
|
|
||||||
|
# The Good
|
||||||
|
|
||||||
|
It runs.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Deliberately short divider slide. Say "it runs" out loud and pause.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- _class: invert diagram -->
|
||||||
|
|
||||||
|
## What is actually in the basement
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
sat["satellite VPS<br/>public ingress<br/>off-site backup"]
|
||||||
|
net["OPNsense · network<br/>zones · VLANs<br/>DNS · certificates"]
|
||||||
|
subgraph cluster["Proxmox cluster — commodity boxes"]
|
||||||
|
n1["tappaas1 · foundation"]
|
||||||
|
n2["tappaas2 · AI / HA"]
|
||||||
|
n3["tappaas3 · backup"]
|
||||||
|
end
|
||||||
|
subgraph found["Foundation modules"]
|
||||||
|
cicd["tappaas-cicd<br/>the mothership"]
|
||||||
|
idp["identity"]
|
||||||
|
bak["backup · PBS"]
|
||||||
|
log["logging"]
|
||||||
|
end
|
||||||
|
apps["Application<br/>modules<br/>NixOS VMs"]
|
||||||
|
sat -.WireGuard.-> net
|
||||||
|
net --> cluster
|
||||||
|
cluster --> found
|
||||||
|
cicd --> apps
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Point out: OPNsense is a module too. The mothership is a module. There is no
|
||||||
|
privileged hand-built snowflake anywhere in this picture.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## I promised you eight. There are thirteen.
|
||||||
|
|
||||||
|
| Module | What it is | Lines |
|
||||||
|
| --- | --- | ---: |
|
||||||
|
| `nextcloud` + `nextcloud-hpb` + `coturn` | files, calendar, contacts, calls | 5,081 |
|
||||||
|
| `litellm` | AI gateway — keys, quotas, routing | 2,455 |
|
||||||
|
| `openwebui` | the chat window | 1,911 |
|
||||||
|
| `deconz` | Zigbee | 1,451 |
|
||||||
|
| `vllm-amd` | **local LLM inference** | 1,407 |
|
||||||
|
| `euro-office` | documents in the browser | 1,148 |
|
||||||
|
| `hass` | home automation | 1,095 |
|
||||||
|
| `vaultwarden` | passwords | 513 |
|
||||||
|
| `windows-server` | the one thing that will not die | 361 |
|
||||||
|
| `netbird-client` · `n8n` | mesh VPN · automation | 263 |
|
||||||
|
|
||||||
|
`TODO: tick off which are actually live in the basement on the night — the tree carries more than the basement runs`
|
||||||
|
|
||||||
|
<!--
|
||||||
|
The abstract promised eight. Say plainly that five more landed since spring —
|
||||||
|
that is the same joke as the commit count, and it lands twice.
|
||||||
|
Then be honest about what "in the tree" means versus "live in my basement":
|
||||||
|
a module existing and a module your family depends on are different claims.
|
||||||
|
Line counts are there to show these are real modules, not folder names.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## "It runs" is a bigger claim than it sounds
|
||||||
|
|
||||||
|
Every one of those services, without me:
|
||||||
|
|
||||||
|
- **updates itself** on a schedule — and the update is gated by a test
|
||||||
|
- **backs itself up** nightly to Proxmox Backup Server, and off-site
|
||||||
|
- **reports its own health**, so I find out before my family does
|
||||||
|
- **has a certificate** that renews
|
||||||
|
- **has one login** — my identity provider, not eight password fields
|
||||||
|
|
||||||
|
The interesting engineering is not installing Nextcloud.
|
||||||
|
It is Nextcloud still being there, patched, in eighteen months, unattended.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
This is the core argument of the whole project. Slow down here.
|
||||||
|
Self-hosting is easy. Self-hosting you can forget about is not.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## One idea does all the work: everything is a module
|
||||||
|
|
||||||
|
```text
|
||||||
|
nextcloud/
|
||||||
|
├── nextcloud.json # the contract — what it is, needs, provides
|
||||||
|
├── nextcloud.nix # the NixOS machine
|
||||||
|
├── install.sh # put it there (once)
|
||||||
|
├── update.sh # keep it patched (on schedule)
|
||||||
|
├── test.sh # prove it still works (gates the update)
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
The firewall is a module. The backup server is a module.
|
||||||
|
The mothership that installs the modules is a module.
|
||||||
|
|
||||||
|
**One model to learn, not eight.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `dependsOn` — the trick the whole thing rests on
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"description": "Nextcloud — files, calendar, contacts",
|
||||||
|
"vmname": "nextcloud", "vmid": 210,
|
||||||
|
"dependsOn": ["cluster:vm", "templates:nixos", "backup:vm",
|
||||||
|
"network:proxy", "identity:identity"],
|
||||||
|
"config": {
|
||||||
|
"cluster:vm": { "cores": 4, "memory": "8192", "diskSize": "64G" },
|
||||||
|
"network:proxy": { "proxyPort": 443 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Install **order** is computed from these declarations. Nobody maintains a list.
|
||||||
|
Add a module, and the platform works out that it needs a VM, an OS, a VLAN,
|
||||||
|
a certificate, a login and a backup job — in that order.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
If someone asks "is this just Ansible/Terraform?" — the answer is: those are
|
||||||
|
how you build one machine. This is about a machine estate that has a shape.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Reduce flexibility. On purpose.
|
||||||
|
|
||||||
|
> A key design goal is to **REDUCE** flexibility.
|
||||||
|
> There is value in decisions having been taken up front.
|
||||||
|
|
||||||
|
Some use cases will not fit TAPPaaS. That is the trade.
|
||||||
|
For what fits, it is dramatically easier.
|
||||||
|
|
||||||
|
Zones, VLANs, naming, storage roles, backup policy, identity model — decided.
|
||||||
|
You get to pick the applications, and where they live.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
This is the least popular slide with hackers and the most important one.
|
||||||
|
Every hour you spend re-deciding VLAN layout is an hour not spent on services.
|
||||||
|
Expect pushback; welcome it.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- _class: invert lead chapter -->
|
||||||
|
|
||||||
|
# The surprise
|
||||||
|
|
||||||
|
My basement grew a brain.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Local AI. Private, offline, mine.
|
||||||
|
|
||||||
|
| | |
|
||||||
|
| --- | --- |
|
||||||
|
| Silicon | AMD Ryzen AI MAX+ 395 — Radeon 8060S (Strix Halo) |
|
||||||
|
| Memory | **128 GB unified** — the GPU sees nearly all of it |
|
||||||
|
| Largest model tested | `gpt-oss-120b` — **120B parameters** |
|
||||||
|
| Speed | ~50 tok/s at 7B FP16 · ~20 tok/s at 30B 4-bit |
|
||||||
|
| API | OpenAI-compatible, on my own VLAN |
|
||||||
|
| Data leaving the building | **none** |
|
||||||
|
|
||||||
|
One commodity box. Not a rack, not a hyperscaler, not a monthly bill.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Stress "unified memory" — that is what makes a 120B model possible on a box
|
||||||
|
that fits under a desk. This is a genuinely new thing as of this year.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- _class: invert punch -->
|
||||||
|
|
||||||
|
## The demo I have been waiting a year to do
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
# Pull the cable out.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Then ask it something.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
DEMO. Physically unplug the uplink. Visible. Then a real question in OpenWebUI.
|
||||||
|
Have a screen-recorded fallback ready — the tent's projector will betray you.
|
||||||
|
If it works live, this is the moment of the talk. Do not rush it.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Why this is the point, not a party trick
|
||||||
|
|
||||||
|
Sovereignty is not a checkbox you tick at a vendor.
|
||||||
|
|
||||||
|
- The model runs on hardware **you** own
|
||||||
|
- Your documents are indexed on **your** VLAN
|
||||||
|
- Nobody re-prices it, deprecates it, or reads it
|
||||||
|
- It works when the fibre is cut, the account is suspended,
|
||||||
|
or the terms of service change on a Tuesday
|
||||||
|
|
||||||
|
`litellm` in front means apps ask for "a model" — local today,
|
||||||
|
someone else's tomorrow, **your choice, revocable**.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Tie back to bullet four from the dream slide: "no reliance on the Internet".
|
||||||
|
That was the mad-sounding one. Here it is, cashed in.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- _class: invert lead chapter -->
|
||||||
|
|
||||||
|
# The Bad
|
||||||
|
|
||||||
|
What it actually cost.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The brutal commit graph of a side project
|
||||||
|
|
||||||
|
```text
|
||||||
|
2025-05 99 ██████████ ← the honeymoon
|
||||||
|
2025-06 21 ██
|
||||||
|
2025-07 53 █████
|
||||||
|
2025-08 141 ██████████████ ← Sommerhack 2025
|
||||||
|
2025-09 6 █ ← life
|
||||||
|
2025-10 9 █ ← still life
|
||||||
|
2025-11 41 ████
|
||||||
|
2025-12 47 █████
|
||||||
|
2026-01 66 ██████
|
||||||
|
2026-02 141 ██████████████
|
||||||
|
2026-03 33 ███
|
||||||
|
2026-04 19 ██
|
||||||
|
2026-05 190 ██████████████████
|
||||||
|
2026-06 351 ██████████████████████████████████ ← something changed
|
||||||
|
2026-07 164 ████████████████
|
||||||
|
2026-08 83 ████████ (to the 20th)
|
||||||
|
```
|
||||||
|
|
||||||
|
This is what a real side project looks like. Not a burndown chart. A heartbeat
|
||||||
|
with two near-death experiences in it.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Point at Sept/Oct 2025: six and nine commits. Two months of nearly nothing.
|
||||||
|
Be honest about why — the audience has all had that autumn.
|
||||||
|
TODO below is yours to fill in.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The two dips are the honest part
|
||||||
|
|
||||||
|
**September–October 2025: 15 commits in two months.**
|
||||||
|
|
||||||
|
`TODO: what actually happened here — say it plainly, it is the most relatable slide in the deck`
|
||||||
|
|
||||||
|
The lesson I take from it: a self-hosted platform that needs *you* every week
|
||||||
|
is not a platform, it is a pet.
|
||||||
|
|
||||||
|
The dips are the real test. The services stayed up.
|
||||||
|
Because updates, backups and tests do not need me to be enthusiastic.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Strong point: the automation is not a nice-to-have, it is what makes the
|
||||||
|
project survivable by a human with a life.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## June 2026: 351 commits. Something changed.
|
||||||
|
|
||||||
|
That is not me getting three times better at typing.
|
||||||
|
|
||||||
|
That is the month I leaned all the way into AI-assisted development —
|
||||||
|
which is exactly the confession in part three.
|
||||||
|
|
||||||
|
Hold that thought.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Deliberate hook into The Ugly. The commit spike is the evidence, and the
|
||||||
|
audience will already be suspicious. Good. Let them be.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The iceberg under every "simple" service
|
||||||
|
|
||||||
|
| | Files | Lines |
|
||||||
|
| --- | ---: | ---: |
|
||||||
|
| **Foundation** — the platform | 575 | **114,138** |
|
||||||
|
| **Apps** — the things you actually use | 144 | **15,726** |
|
||||||
|
|
||||||
|
For every line in an app module, there are **seven lines of platform underneath**.
|
||||||
|
|
||||||
|
`tappaas-cicd` alone — the mothership — is **81,942 lines**, 72% of the
|
||||||
|
foundation. Inside it: 8 TypeScript managers (36,920 lines) and the
|
||||||
|
controller layer that talks to Proxmox, OPNsense and the switch (29,352).
|
||||||
|
|
||||||
|
<!--
|
||||||
|
The 7:1 platform-to-app ratio is the single most useful number in this talk
|
||||||
|
for anyone thinking "I'll just spin up Docker Compose". That works — until you
|
||||||
|
want it to still work next year without you.
|
||||||
|
Source: src/STATISTICS.md, regenerated 2026-08-23.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What it costs in money
|
||||||
|
|
||||||
|
| | |
|
||||||
|
| --- | --- |
|
||||||
|
| Hardware | `TODO: what you actually spent, be honest, include the mistakes` |
|
||||||
|
| Electricity | `TODO: measured W → kr/year at current DK prices` |
|
||||||
|
| Satellite VPS | `TODO: kr/month` |
|
||||||
|
| Domain + DNS | `TODO:` |
|
||||||
|
| Licences | 0 |
|
||||||
|
| **Versus the hyperscaler equivalent** | `TODO: the comparison, done fairly — include your time at 0` |
|
||||||
|
|
||||||
|
The honest framing: this is **not** cheaper if you value your time at anything.
|
||||||
|
It is cheaper if you were going to tinker anyway, and it is *ownable* at any price.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Do not oversell the economics. This audience will smell it instantly, and the
|
||||||
|
sovereignty argument does not need a fake cost argument propping it up.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What it costs in things that are not money
|
||||||
|
|
||||||
|
- **Learning curve you cannot delegate** — Proxmox, NixOS, OPNsense, ZFS,
|
||||||
|
VLANs, PKI, OIDC. Any one of those is a weekend. You need all six.
|
||||||
|
- **You are the on-call.** At 22:00. On holiday.
|
||||||
|
- **Family SLA.** The moment calendars are on it, downtime is a domestic matter,
|
||||||
|
not a technical one.
|
||||||
|
- **Decision fatigue** — which is precisely why the project reduces flexibility.
|
||||||
|
- **The "why don't you just" tax** — from every friend, every time.
|
||||||
|
|
||||||
|
`TODO: your best war story — the outage that taught you the most`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What I got wrong
|
||||||
|
|
||||||
|
`TODO: pick three, be specific, be unflattering — this slide buys credibility for everything else`
|
||||||
|
|
||||||
|
Candidates from the graph and the repo:
|
||||||
|
|
||||||
|
- Numbered foundation modules (`05-`, `10-`, `30-`…) — retired in the ADR-007
|
||||||
|
refactor once ordering had to come from `dependsOn` instead
|
||||||
|
- `TODO:`
|
||||||
|
- `TODO:`
|
||||||
|
|
||||||
|
The pattern: everywhere I encoded an ordering or a name as a *convention*,
|
||||||
|
I later had to make it a *declaration*.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
The 05-/10-/30- → dependsOn story is a genuinely good, concrete lesson and it
|
||||||
|
is in the repo history. Tell it properly if the other two are thin.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- _class: invert lead chapter -->
|
||||||
|
|
||||||
|
# The Ugly
|
||||||
|
|
||||||
|
The confession.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- _class: invert punch -->
|
||||||
|
|
||||||
|
# I used AI to build it.
|
||||||
|
|
||||||
|
# And I gave it root on every node.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Not "AI-assisted autocomplete".
|
||||||
|
|
||||||
|
Root. `ssh`. `nixos-rebuild`. `qm`. `pvesh`. The firewall. The secrets.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Say it flatly and then be quiet for three full seconds. Let the tent react.
|
||||||
|
This is the slide people came for and the one they will argue with afterwards.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Why on earth would you do that
|
||||||
|
|
||||||
|
Because the alternative was that it never got built.
|
||||||
|
|
||||||
|
- One retiree, evenings and weekends, six unfamiliar technology stacks
|
||||||
|
- 112,000 lines of platform code for thirteen app modules
|
||||||
|
- The plumbing is *tedious*, not clever — the exact shape of work to hand over
|
||||||
|
|
||||||
|
The graph does not lie: **May 190, June 351.** That is what handing over the
|
||||||
|
tedium looks like.
|
||||||
|
|
||||||
|
And the honest part: I could not have hand-written the last third of this.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Do not be defensive. The result is on the projector; the method is the price.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- _class: invert punch -->
|
||||||
|
|
||||||
|
# So the real question is not "did you"
|
||||||
|
|
||||||
|
# It is: **what did you fence it with?**
|
||||||
|
|
||||||
|
The guardrails are not vibes. They are written down, in the repo,
|
||||||
|
loaded on every single session, and they override anything the model
|
||||||
|
would otherwise default to.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
This is the pivot from confession to engineering. Everything after this is
|
||||||
|
transferable to anyone in the tent using these tools.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Guardrail 1 — the line it may never cross
|
||||||
|
|
||||||
|
> **Never run `git commit` or `git push` — full stop.**
|
||||||
|
> The operator performs ALL commits and pushes themselves.
|
||||||
|
> This holds even when a request seems to imply it — "land it", "ship it",
|
||||||
|
> "move this to main" — and even when a previous turn involved committing.
|
||||||
|
> That is NOT standing authorization.
|
||||||
|
|
||||||
|
The AI may change any file on disk. It may not make a change **permanent**.
|
||||||
|
|
||||||
|
Every single line that entered history passed under my eyes.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Verbatim from CLAUDE.md. The distinction — mutate freely, persist never — is
|
||||||
|
the single most useful idea in this section. Say why: git is the undo button,
|
||||||
|
so the undo button is the thing it must not touch.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Guardrail 2 — the blast radius is designed
|
||||||
|
|
||||||
|
| Layer | What it bounds |
|
||||||
|
| --- | --- |
|
||||||
|
| **Modules** | A mistake lands in one VM, not "the server" |
|
||||||
|
| **Zones** | A compromised VM cannot reach what its VLAN forbids |
|
||||||
|
| **Proxmox snapshots** | Minutes-old rollback, per machine |
|
||||||
|
| **PBS + off-site** | Nightly, immutable, pull-based |
|
||||||
|
| **NixOS** | `nixos-rebuild test` before `switch` — a bad config dies at reboot |
|
||||||
|
| **30,282 lines of tests** | The update does not land unless the service proves it works |
|
||||||
|
|
||||||
|
Root access is only terrifying if the system underneath is a snowflake.
|
||||||
|
Mine is disposable by construction.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
This is the actual answer to "you gave an AI root?!". The architecture that
|
||||||
|
makes unattended updates safe is the same architecture that makes an
|
||||||
|
over-eager agent survivable. Same property, two beneficiaries.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Guardrail 3 — confirm before the irreversible
|
||||||
|
|
||||||
|
The standing rules, as written:
|
||||||
|
|
||||||
|
- **Confirm before destructive ops** — deleting a VM it did not create,
|
||||||
|
dropping a storage pool, force-pushing `main`/`stable`, wiping `/etc/secrets`
|
||||||
|
- **Fix root causes, not symptoms** — no `--no-verify`, no silenced errors,
|
||||||
|
no bypassed CI to make an install "succeed"
|
||||||
|
- **Read before you rebuild** — `test` before `switch`
|
||||||
|
- **Propose tests first**, then run them
|
||||||
|
|
||||||
|
Note what these have in common: they are all rules about **honesty**,
|
||||||
|
not about capability.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
The failure mode with a capable agent is not malice. It is an agent that
|
||||||
|
makes the red thing turn green by removing the check. Name that explicitly.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Guardrail 4 — I stayed the reviewer
|
||||||
|
|
||||||
|
Eight specialist roles — architect, bash, python, nix, tester, security,
|
||||||
|
infra, PM — with a security review in the path of every change.
|
||||||
|
|
||||||
|
But the load-bearing part is duller than that:
|
||||||
|
|
||||||
|
- I read the diff
|
||||||
|
- I write the commit message
|
||||||
|
- I press the button
|
||||||
|
- 24 ADRs exist so that *I* still know why the system is shaped this way
|
||||||
|
|
||||||
|
The day I stop reading diffs, this stops being sovereign
|
||||||
|
and starts being someone else's system running in my basement.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Am I still in control? Honestly.
|
||||||
|
|
||||||
|
**Yes — but "control" moved.**
|
||||||
|
|
||||||
|
I no longer control every line. I control:
|
||||||
|
|
||||||
|
- the **architecture** — modules, zones, contracts
|
||||||
|
- the **gate** — commits, pushes, releases
|
||||||
|
- the **proof** — 145 test suites, 29% of the source, that must be green
|
||||||
|
- the **exit** — it is all open source, on my hardware, in my hands
|
||||||
|
|
||||||
|
That is a real answer, not a comfortable one. Ask me the hard version in Q&A.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Do not claim more than this. If someone says "that's not control, that's
|
||||||
|
supervision" — agree, and say supervision with a hard gate and a working undo
|
||||||
|
is what control has always meant in operations.
|
||||||
|
-->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- _class: invert lead chapter -->
|
||||||
|
|
||||||
|
# Your turn
|
||||||
|
|
||||||
|
Build one too.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Why you, specifically, should
|
||||||
|
|
||||||
|
**European sovereignty is not a policy problem you can wait out.**
|
||||||
|
|
||||||
|
It is thousands of small boxes, in basements and back offices,
|
||||||
|
running software nobody can withdraw.
|
||||||
|
|
||||||
|
- Your data has to live *somewhere*. Somewhere can be here.
|
||||||
|
- A skill you own beats a subscription you rent.
|
||||||
|
- Every basement cloud makes the next one cheaper to build.
|
||||||
|
|
||||||
|
`TODO: your one-sentence version of this — say it in your own words, not mine`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Start smaller than I did
|
||||||
|
|
||||||
|
| Step | What you get |
|
||||||
|
| --- | --- |
|
||||||
|
| 1. One box, evaluation tier | 4 cores / 16 GB / a disk. Nested virt is fine. |
|
||||||
|
| 2. Proxmox + OPNsense | Zones, VLANs, DNS, certificates that renew |
|
||||||
|
| 3. The mothership | `tappaas-cicd` — the thing that installs the rest |
|
||||||
|
| 4. **One** service | Vaultwarden. Small, useful, immediately missed. |
|
||||||
|
| 5. Backup **before** service two | Non-negotiable. Ask me why. |
|
||||||
|
|
||||||
|
No public IP? A satellite VPS is the escape hatch.
|
||||||
|
No GPU? Skip local AI, keep everything else.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Where to find all of it
|
||||||
|
|
||||||
|
- **tappaas.org** — docs, work in progress, honest about it
|
||||||
|
- **codeberg.org/TAPPaaS/TAPPaaS** — the code, the 24 ADRs, the commit graph
|
||||||
|
- **sovereigncomputing.org** — the wider argument
|
||||||
|
- **This deck** — slides.makerfloss.eu/tappaas/one-year-in
|
||||||
|
- **MakerFLOSS** — Orange Makerspace, bi-weekly FLOSS jam. Come build one with us.
|
||||||
|
|
||||||
|
Contributions welcome. Open an issue before you open a pull request —
|
||||||
|
somebody may already be packaging your app.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- _class: invert lead chapter -->
|
||||||
|
|
||||||
|
# Questions
|
||||||
|
|
||||||
|
The harder, the better.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Prepared answers to have loaded:
|
||||||
|
- "Isn't this just Ansible?" → estate vs. machine; dependsOn
|
||||||
|
- "Why NixOS?" → reproducible rebuild is the undo button
|
||||||
|
- "You gave an AI root — seriously?" → guardrail 2, blast radius by design
|
||||||
|
- "What if you get hit by a bus?" → open source, docs, ADRs, TODO: honest answer
|
||||||
|
- "Cheaper than Google?" → no. Ownable, though.
|
||||||
|
- "Can I run it on one Raspberry Pi?" → no. Evaluation tier, x86, be realistic.
|
||||||
|
-->
|
||||||
Loading…
Add table
Reference in a new issue