Files
platform/docs/install-variants.md
T
pastilhasandClaude Opus 5 d7d64cd6d6 docs: the install-variant tree, for tomorrow's decision
Enumerates the forty-seven prompts the two scripts actually ask and sorts them
into branches, consents and values — the distinction that decides what a
generated leaf can remove. Four real branches (OS, role, tailnet state, which
half), fourteen consents that let a leaf omit a section entirely, and a set of
values that must stay prompts because baking them in would mean publishing
somebody's hostname.

Names the two things that need deciding rather than deciding them:

Whether a leaf strips dead code or sets constants and calls the base. They are
different artifacts and the plan rests on which one is meant — the first is what
makes it auditable by being short, the second is what keeps it maintainable.

And the combinatorics: 4 OS x 3 roles x 3 tailnet states is 36 leaves before
consents, so the tree cannot be the full product. Publishing a few opinionated
leaves keeps the static-file-anyone-can-diff property; generating on demand does
not, which is the property per-leaf scripts existed for.

Also notes that --unattended and a generated leaf are the same mechanism seen
twice, and that install_config's existing behaviour — keep the user's file when
there is no tty — is the conservatism every unattended answer needs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 03:58:32 +00:00

4.7 KiB
Raw Blame History

The install page, and the scripts behind it

Status: for discussion, 2026-08-13. Nothing here is built. It exists so tomorrow's conversation is about real branches rather than sketched ones — every question below is one the scripts already ask today.

The shape agreed

  • One source — the interactive scripts as they are.
  • A build script that compiles them into single files, because curl | bash cannot fetch libs.
  • The build emits one script per leaf of the question tree, not one script with pre-seeded answers. A person auditing before running reads only their own path.
  • Verification is of the generator, once: anyone regenerates the leaves from source and diffs them against what is published. One thing to trust rather than N.

The questions that actually exist

Forty-seven prompts across the two scripts. Almost none of them should become a branch — the distinction that matters is:

A branch changes which code runs. Removing it makes a script genuinely shorter.

A value changes a string. Removing it makes a script no shorter — it just moves the answer from a prompt to a constant.

A consent is a yes/no about doing a step at all. These are the interesting middle: pre-answering one lets the build delete the section entirely.

Branches — these change what code exists

question answers what it eliminates
operating system macOS · Debian/Ubuntu · Arch · Fedora 17 of 26 machine-setup sections on macOS; the whole case $PM ladder collapses to one arm
machine role homelab · vps · dev swap, ballast, earlyoom, sleep/suspend, boot-hang, static addressing — each is role-gated today
tailnet already connected · set one up · none the entire Tailscale section, its four sub-options and the offscale explanation
which half machine + officer · officer only · machine only one of the two scripts disappears

Consents — pre-answering deletes a section

Docker · fail2ban · unattended-upgrades · Neovim · agent CLIs · shell config · firewall · SSH hardening · DNS · swap · ballast · earlyoom · inotify · boot-on-start.

Fourteen sections that a leaf script can simply not contain.

Values — never a branch

Username · install path · git name and email · port · public URL · Postgres connection · timezone · locale · LAN CIDR · swap size · swappiness.

These stay as prompts even in a generated script, or arrive as environment variables. Baking them into a published file would mean publishing somebody's hostname.


Where this collides with --unattended

--unattended and a generated leaf are the same mechanism seen twice: both are "answer these in advance". The difference is only whether the answer is baked in at build time or supplied at run time.

Worth deciding tomorrow whether a leaf script is literally base.sh --unattended with a header of constants, or whether the build truly strips the dead branches. The second is what makes it auditable-by-being-short; the first is what makes it maintainable. They are not the same artifact, and the whole plan rests on which one we mean.

One thing that already exists and should be preserved either way: with no tty, install_config keeps the user's file rather than replacing it. Every unattended answer needs to be conservative in that same way, and that is a property of each prompt, not of the flag.


The combinatorics

4 OS × 3 roles × 3 tailnet states = 36 leaves before any consent is considered, and consents multiply it past anything anyone would publish.

So the tree the install page walks cannot be the full product. Two ways out, to choose between:

  1. Publish a few opinionated leaves — "Ubuntu VPS, new tailnet", "macOS dev machine", "Ubuntu homelab, existing tailnet" — and send everything else to the full interactive script.
  2. Generate on demand — the page composes the leaf when the questions are answered. Stronger, but the artifact is no longer a static file anyone can diff against the repo, which costs the verification property the whole design was for.

My inclination is (1), because (2) quietly trades away the thing that made per-leaf scripts worth building. But it is a real trade and it is yours.


Open, for tomorrow

  • Does a leaf strip dead code, or set constants and call the base?
  • How many leaves get published, and what happens to the rest?
  • Does the install page show the script before running it? It should — that is the moment auditing is cheap and nobody will do it afterwards.
  • The report from install-report.md names a script commit. A generated leaf needs to name the source commit it was generated from, or the report cannot be checked against anything.