Eltahir
← All posts

The machine that builds my machine

A new dev box used to cost me the better part of a day of yak-shaving. Now a bare Linux install becomes my exact workstation, unattended, in minutes — because the setup is a program, not a checklist.

4 min read Developer experience
  • Automation
  • Linux
  • Reproducibility
  • Tooling
Built itdev-boost private — opening soon

A fresh development machine used to cost me the better part of a day: install the editor, the language toolchains, the CLIs, the fonts, the services, the dotfiles — then discover three weeks later that the old machine had one tweak I never wrote down. The setup was tribal knowledge living in my head and a README that was wrong the moment I saved it.

So I stopped treating provisioning as a chore and started treating it as a program. dev-boost takes a bare Linux box and turns it into my exact workstation, unattended, in minutes.

Why a setup script isn’t enough

The obvious answer is “write a bash script.” I did, twice. It rots, because a script that runs top-to-bottom once is not the same thing as a system you can trust to converge.

A top-to-bottom scriptWhat you actually want
Re-running it re-does everything (or breaks)Re-running is a safe no-op
A failure leaves the machine half-builtA failure is resumable from where it stopped
”What’s installed?” lives in your headThe desired state is a file you can read and diff
Adding a tool means editing control flowAdding a tool means adding one entry

The gap between those two columns is the whole point. The first is automation; the second is convergence.

Two rules make it converge

Separate the engine from the data. The logic that installs knows nothing about what to install. Tools, fonts, services, and dotfiles are declarative data; the engine just executes it. A new tool is one record, not a new branch.

Make every step idempotent and verify-guarded. Each step declares how to check whether it’s already satisfied and how to make it so. The engine runs the check first and only acts on a miss — so a second run does nothing, and a failed run is safe to re-run.

Terminal window
# A unit is satisfied when `verify` exits 0; otherwise `install` runs and verify must then pass.
unit "ripgrep" \
--verify 'command -v rg' \
--install 'sudo dnf install -y ripgrep'
unit "git config: pull.rebase" \
--verify 'test "$(git config --global pull.rebase)" = "true"' \
--install 'git config --global pull.rebase true'

Because every unit is guarded, the engine is forward-only and reproducible: you never hand-patch state, you change the data and re-run, and the machine moves toward the declared state from wherever it is.

What a run looks like

Point it at a fresh Fedora install and the whole environment materializes — packages, language runtimes, the editor and its config, services, fonts — the same way every time. Run it again next month after you’ve added three tools, and it installs exactly those three.

The payoff isn’t only speed (a lost afternoon becomes a coffee). It’s that my environment is now legible: its definition is a file I can review, and the thing that builds everything else is held to the same bar as everything else — engine/data separation, idempotence, a written constitution, and a few hundred tests.

The tradeoff I accepted

Declarative convergence costs more up front than apt install in a terminal. You write the verify alongside the install, and you resist the urge to “just do it manually this once.” The return is that the cost is paid once and amortized across every machine, every reinstall, and every teammate who clones it.

That’s the pattern under most of what I build: don’t do the work twice — build the thing that does the work, and make it reproducible.

Contact

Send a message

Esc