Design for Go rewrite of the Bash installer: single static binary with Bubble Tea + Huh TUI, four-phase module contract, DAG execution engine, embedded config + optional overlay, and a minimal Bash pre-install shim. Scoped to installer architecture only; toolset overhaul, CLI browser, and TTY Nerd Font support are queued as follow-up specs.
11 KiB
Installer Architecture Redesign
Status: Design approved, pending implementation plan Date: 2026-04-13 Scope: Installer architecture only. Toolset overhaul, CLI browser, and TTY-font story are queued as follow-up specs.
Context
The current Archduino installer is a Bash entry point (install.sh) that sources 13 files under lib/. It has grown beyond what Bash handles comfortably:
- Monolithic / ordering-fragile.
install.shsources lib files in a fixed order at top level; hard to run a single step and hard to re-run safely. - Not idempotent / no resume. Re-running either duplicates work or silently skips things.
- Mixed concerns per file. Detection, templating, package install, and theme application are intertwined in every lib.
- Thin UX. Only choices are theme + verbose; no dry-run, no module selection, no preview.
- Platform coupling. Arch vs Ubuntu-WSL branches are scattered throughout.
The goal is a principled rebuild across all five axes.
Decision summary
| Area | Choice |
|---|---|
| Language / runtime | Go, single static binary |
| UI | Bubble Tea + Huh (wizard for input, dashboard for execution) |
| Pre-install shim | Minimal Bash script: ensures sudo, git, curl, ca-certificates, fetches binary |
| Module contract | Four-phase: Detect → Plan → Apply → Verify |
| Configuration | Embedded defaults + optional ~/.config/archduino/overlay.yaml |
| Privilege model | User-mode binary; sudo per privileged command; upfront sudo -v + keepalive goroutine |
| State / logging | Detect() is authoritative; run log is advisory UX only |
| Failure policy | DAG execution; failed node fails its descendants; siblings proceed; --halt-on-error flag available |
| Subcommands | One binary: install, theme, new, doctor |
| Platform scope (v1) | Arch-only native + WSL2; PackageManager interface ready for Debian/Fedora later |
Architecture
Module contract
Every installable unit implements:
type Module interface {
Meta() ModuleMeta // id, human name, description, deps []string
Detect(ctx) (State, error) // read current system
Plan(ctx, State) (Plan, error) // diff vs desired; []Action with descriptions
Apply(ctx, Plan, *Reporter) error // execute; stream progress to Reporter
Verify(ctx) error // confirm desired state reached
}
Stateis module-defined.Planis a flat list ofAction{Description, Kind, Exec}so the engine can render plans without knowing modules.Reporterdecouples modules from the TUI.Detect()is authoritative. No cached state overrides what the system reports.
Initial module set (1:1 with today's lib/*.sh):
packages, fonts, zsh, starship, zellij, neovim, yazi, arduino-cli, renode, git (lazygit), scaffold, shell-config, themes.
Execution engine
- DAG-scheduled. Modules declare
deps []string. Engine topologically sorts and runs independent branches concurrently, default parallelism 4. - Failure policy. Failed node marks its descendants
skipped-due-to-dep; siblings proceed. End-of-run summary:X succeeded, Y failed, Z skipped. - Dry-run (
--dry-run, or wizard "Review plan" step): runs Detect + Plan for every selected module, renders the combined plan, exits. - Halt mode (
--halt-on-error): opts into fail-fast for scripted use. - Resume hint. If a prior run log exists, TUI offers "last run:
starshipfailed — resume? / start over? / pick modules?" Hint only; Detect decides what actually runs.
Configuration
- Embedded defaults via
//go:embed: package lists, theme files, Zellij layouts, config templates. - Optional overlay at
~/.config/archduino/overlay.yaml:version: 1 extra_packages: [htop, ncdu] disabled_modules: [renode] theme_overrides: starship: ~/my-starship.toml - Overlay is merged at load time. Schema is versioned; a migration path exists when it grows.
Privilege model
- Binary runs as the invoking user.
- On
installstart, onesudo -vcall; a keepalive goroutine refreshes the timestamp every 50 seconds while the installer is live. - Privileged operations shell out to
sudo pacman …,sudo usermod …. If sudo needs to re-prompt (timestamp expired), the TUI is suspended viatea.ExecProcess. - Config files, git clones, and anything under
$HOMErun as the user — no root-owned files in home.
Platform abstraction
type PackageManager interface {
Install(pkgs []string) error
IsInstalled(pkg string) (bool, error)
Update() error
Name() string
}
- V1 ships
Pacmanonly. Debian/Ubuntu/Fedora impls are stubbed; wiring them is a later spec. Platform{Distro, IsWSL, Arch}detection is centralized. WSL path sets/etc/wsl.confhints and adds the user todialout/uucp.
State and logs
~/.local/state/archduino/runs/<timestamp>.json— per-run structured log:{module, phase, status, duration, error}.~/.local/state/archduino/last.json— most recent run; read by TUI for the resume hint.- Log rotation: keep the last 10 runs.
- Safe to delete at any time; never read for correctness decisions.
User-facing surface
Pre-install shim (scripts/bootstrap.sh)
~30 lines of Bash, served from a stable URL. Invocation:
curl -fsSL https://archduino.dev/install.sh | bash
Responsibilities in order:
- Refuse to run as root.
- Detect distro; abort with a clear message if not Arch (v1 scope).
- Ensure
sudo,git,curl,ca-certificatesare installed (pacman -S --needed). - Detect architecture, fetch the matching release binary from GitHub Releases, verify its SHA256 against the published checksum file, install to
~/.local/bin/archduino,chmod +x. exec archduino install.
Everything further (base-devel, multilib, user groups, toolset) is handled by modules inside the binary.
Subcommands
| Command | Purpose |
|---|---|
archduino install |
Default; wizard + dashboard flow |
archduino theme [name] |
Switch colorscheme across all tools; no arg = TUI picker |
archduino new <name> <board> |
Scaffold a project (current arduino-new behavior) |
archduino doctor |
Run Detect across all modules; report drift in a read-only dashboard |
archduino --version / completion <shell> |
Standard |
Global flags: --dry-run, --halt-on-error, --verbose, --no-tui, --only <mods>, --skip <mods>.
Shell aliases theme and arduino-new are preserved via thin wrappers dropped in ~/.local/bin by the shell-config module.
TUI flow (install)
Wizard phase (Huh forms):
- Welcome — detected platform, WSL status, binary version.
- Theme picker — 5 options, palette preview.
- Module selector — checkbox list; pre-ticked from overlay or "all" by default; disabled modules grayed with reason.
- Advanced (collapsible) — parallelism, halt-on-error toggle, overlay path override.
- Review plan — rendered
Plan()grouped by module, expandable. Back-navigable. - Confirm —
[y/N]gate into Apply.
Apply phase (Bubble Tea dashboard):
┌─ archduino install ──────────────────────────────────┐
│ ✓ packages 12/12 2.4s │
│ ✓ fonts 1/1 0.8s │
│ ⠋ neovim 3/7 running: :MasonInstall… │
│ ⠋ arduino-cli 2/4 running: core install… │
│ · zellij pending (waits: packages) │
│ ✗ starship failed — config template parse │
│ ⊘ shell-config skipped (dep: starship) │
├──────────────────────────────────────────────────────┤
│ [tab] focus module [enter] expand log [q] abort │
└──────────────────────────────────────────────────────┘
- Concurrent modules visible live.
enteron a row expands a scrollable log tail.- On completion: summary view; footer offers
[r]etry failed / [v]iew full log / [q]uit.
Project layout
archduino/
├── cmd/archduino/ # main.go, cobra wiring
├── internal/
│ ├── engine/ # DAG scheduler, Reporter, dry-run, resume
│ ├── module/ # Module interface, Plan/Action types, registry
│ ├── modules/ # one package per module
│ │ ├── packages/
│ │ ├── zsh/
│ │ └── ...
│ ├── platform/ # detection; PackageManager interface + pacman impl
│ ├── privilege/ # sudo -v + keepalive
│ ├── config/ # embedded defaults + overlay merge
│ ├── state/ # run log, last-run tracking
│ ├── theme/ # theme model + switcher
│ └── tui/
│ ├── wizard/ # Huh forms
│ └── dashboard/ # Bubble Tea Apply view
├── assets/ # embedded configs, themes, zellij layout, templates
│ ├── themes/
│ ├── templates/
│ └── layouts/
├── scripts/bootstrap.sh
├── .github/workflows/
│ ├── ci.yml
│ └── release.yml # goreleaser
└── docs/
Testing
- Unit — every module's
DetectandPlanagainst an injectedSystemfake; table-driven cases for clean / already-installed / partial / drifted. - Engine — DAG scheduling, failure propagation, dry-run, resume; pure Go, fully covered.
- Integration — Dockerfile on
archlinux:baserunsarchduino install --no-tui --yesend-to-end in CI; asserts key files/packages/services. One matrix entry for native Arch. WSL smoke test remains manual for v1. - TUI —
teatestfor wizard navigation and dashboard state transitions. No screenshot tests.
Distribution
- GitHub Releases via
goreleaser:linux/amd64,linux/arm64static binaries;SHA256SUMSfile. - No AUR package for v1; the shim is the distribution channel. AUR can follow later.
- Semver; breaking changes bump major.
Migration
- Existing
install.sh+lib/*.shstay onmasterat a taggedv0-bash-finalso users can still run the old installer if they want. feat/renode-dapmerges first; a newfeat/go-rewritebranch hosts the rewrite.- First Go release (
v1.0.0) lands when feature parity with the current Bash installer is verified on a clean Arch VM.
Out of scope (queued for follow-up specs)
- Toolset redesign (re-evaluating which CLI tools are curated).
- CLI browser integration.
- Nerd Font rendering on bare TTY (constrained by the Linux console's 512-glyph PSF limit; needs a separate design covering
fbterm/kmsconor the "SSH-in-from-a-real-terminal" fallback). - Non-Arch platform support (Debian/Ubuntu/Fedora).
- AUR packaging.