# 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.