The archive (v0) describes a server that grew by accident. The new one is built on purpose, one phase at a time, and every phase follows
the same loop. The point is not only the result: it is being able to explain, later, why each piece is what it is.
- Frame the problem. One paragraph: what this phase must achieve, and which v0 lesson or open issue it answers.
- Requirements, from v0. Split into must (the phase fails without it), should (clearly better with it) and won't (explicitly out of scope). v0 is the specification: its services, its data, and its incidents become acceptance criteria.
- Candidates. Two or three real options, including "keep what v0 does" when it is a fair contender.
- Criteria, decided before testing. A short weighted list (for example: rebuild time, moving parts, failure modes, fit with a shared media-center machine, how much I have to learn, how well it is documented). Writing them first keeps the winner from being chosen by the last thing I tried.
- Experiments, time-boxed. Each candidate is tried in a throwaway VM (see
lab/). A time box (one evening, one weekend) is part of the experiment: "how long did it take to get working" is itself a result. - Record the result. What worked, what did not, what surprised me, measured numbers. Failures are results too.
- Decide, in an ADR. One file in
docs/decisions/, from the template: context, options, criteria, outcome, consequences. - Integrate. The winner is merged into
reengineering, andreengineeringintomain, which is what is published and deployed. The experiments themselves are not published: the decision record keeps what was measured, and the scripts worth keeping are inlab/.
- Nothing touches the real server before it has been rebuilt from scratch in a VM at least once. The lab is the default, the server is the exception.
- Every experiment ends with a way back: a snapshot, a branch, or a script that undoes it.
- Backups come before data. Nothing holds real data until a restore has been rehearsed.
- Measure, do not guess. Numbers worth collecting: time from an empty disk to running services, time to restore, count of manual steps, idle RAM. v0 baseline: 9 manual steps in the rebuild order, never timed.
- Secrets never enter the repository in clear text, not even in an experiment.
- Clean over clever (principles): tools are used as documented, through maintained NixOS modules, with no glue scripts; any exception goes in the register.
- Small steps, verified each time. A step is done when its check passes, not when the command returns.
| Place | What |
|---|---|
main |
what is published and what the server deploys; the archive of v0 is the tag v0 |
reengineering |
where the phases are integrated, before they are merged into main |
docs/decisions/ |
one ADR per decision, numbered |
docs/method.md |
this file |
docs/principles.md, docs/exceptions.md |
what a good answer looks like, and the register of exceptions |
lab/ |
the tooling to create, snapshot and destroy the throwaway VMs |
~/lab/tidepool/ (outside the repo) |
VM disks and base images, never committed |
Each phase ends in one or more decision records. What was decided, as it stands:
| # | Phase | Question it answers | Decision |
|---|---|---|---|
| 0 | Lab | Can I build, break and rebuild a machine cheaply and repeatably? | throwaway QEMU/KVM VMs (ADR 0001) |
| 1 | Foundations | How is the host described as code, and where do secrets live? | NixOS as a flake (ADR 0002); sops-nix with age, the key kept off the machine, the values in a private repository (ADR 0003) |
| 2 | Backup | Can I restore, before there is anything to lose? | pgBackRest for the databases; Borg, in two repositories, for the files; ZFS for the services; PostgreSQL 17; an offsite copy (ADR 0004, 0005, 0006, 0007) |
| 3 | Edge | How does traffic reach the services, with which certificates? | nginx with the NixOS ACME module, the DNS challenge delegated by CNAME to a public acme-dns (ADR 0008); plain WireGuard (ADR 0009); VM services private by default (ADR 0010) |
| 4 | Services | In what order, and how, does each service move over from v0? | native Nextcloud, Vaultwarden, Syncthing, Samba and smartd; Jellyfin and Immich as containers pinned by digest; WebDAV through nginx; Nextcloud as the single sign-on provider (ADR 0011) |
| 5 | Observability | How do I find out something broke without noticing by chance? | Prometheus and Alertmanager with declared rules, mail only, an outside heartbeat (ADR 0012) |
| 6 | Network and VMs | What is exposed, and how is the VM lab kept safe? | Incus with two pools and Podman instead of Docker, behind a publication gate (ADR 0013) |
| 7 | Automation | How is "rebuild from scratch" proven on every change? | nix flake check on every change and the restore drill before a deployment (ADR 0014, runbook); checks that restore backups (ADR 0015) |
| 8 | Updates and deploys | How do new versions reach the machine safely, and where do the checks run? | the server pulls what was merged, the backups run first, Renovate on the server, two merges (ADR 0016, 0017, 0018) |
| 9 | The real machine | What does the flake need to know about the real hardware, and how are its disks encrypted and its network brought up? | LUKS on every disk opened by the TPM, signed boot images, a hardware module and the WiFi (ADR 0005, 0019, runbook); built and proved in the lab, deployment pending (pending) |