docs: add installer architecture redesign spec
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.
This commit is contained in:
@@ -0,0 +1,228 @@
|
|||||||
|
# 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.sh` sources 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:
|
||||||
|
|
||||||
|
```go
|
||||||
|
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
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `State` is module-defined.
|
||||||
|
- `Plan` is a flat list of `Action{Description, Kind, Exec}` so the engine can render plans without knowing modules.
|
||||||
|
- `Reporter` decouples 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: `starship` failed — 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`:
|
||||||
|
```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 `install` start, one `sudo -v` call; 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 via `tea.ExecProcess`.
|
||||||
|
- Config files, git clones, and anything under `$HOME` run as the user — no root-owned files in home.
|
||||||
|
|
||||||
|
### Platform abstraction
|
||||||
|
|
||||||
|
```go
|
||||||
|
type PackageManager interface {
|
||||||
|
Install(pkgs []string) error
|
||||||
|
IsInstalled(pkg string) (bool, error)
|
||||||
|
Update() error
|
||||||
|
Name() string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- V1 ships `Pacman` only. Debian/Ubuntu/Fedora impls are stubbed; wiring them is a later spec.
|
||||||
|
- `Platform{Distro, IsWSL, Arch}` detection is centralized. WSL path sets `/etc/wsl.conf` hints and adds the user to `dialout`/`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:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://archduino.dev/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
Responsibilities in order:
|
||||||
|
|
||||||
|
1. Refuse to run as root.
|
||||||
|
2. Detect distro; abort with a clear message if not Arch (v1 scope).
|
||||||
|
3. Ensure `sudo`, `git`, `curl`, `ca-certificates` are installed (`pacman -S --needed`).
|
||||||
|
4. 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`.
|
||||||
|
5. `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):**
|
||||||
|
|
||||||
|
1. Welcome — detected platform, WSL status, binary version.
|
||||||
|
2. Theme picker — 5 options, palette preview.
|
||||||
|
3. Module selector — checkbox list; pre-ticked from overlay or "all" by default; disabled modules grayed with reason.
|
||||||
|
4. Advanced (collapsible) — parallelism, halt-on-error toggle, overlay path override.
|
||||||
|
5. Review plan — rendered `Plan()` grouped by module, expandable. Back-navigable.
|
||||||
|
6. 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.
|
||||||
|
- `enter` on 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 `Detect` and `Plan` against an injected `System` fake; 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:base` runs `archduino install --no-tui --yes` end-to-end in CI; asserts key files/packages/services. One matrix entry for native Arch. WSL smoke test remains manual for v1.
|
||||||
|
- **TUI** — `teatest` for wizard navigation and dashboard state transitions. No screenshot tests.
|
||||||
|
|
||||||
|
## Distribution
|
||||||
|
|
||||||
|
- **GitHub Releases** via `goreleaser`: `linux/amd64`, `linux/arm64` static binaries; `SHA256SUMS` file.
|
||||||
|
- 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/*.sh` stay on `master` at a tagged `v0-bash-final` so users can still run the old installer if they want.
|
||||||
|
- `feat/renode-dap` merges first; a new `feat/go-rewrite` branch 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`/`kmscon` or the "SSH-in-from-a-real-terminal" fallback).
|
||||||
|
- Non-Arch platform support (Debian/Ubuntu/Fedora).
|
||||||
|
- AUR packaging.
|
||||||
Reference in New Issue
Block a user