Skip to main content

gui phase1 architecture

Status: proposal, 2026-07-17 β€” historical (2026-08-11): the GUI now compiles, drives the E2E install in drive mode (WOOTC_E2E_DRIVE=1), and green GUI-driven runs are published in the README build/test matrix. Companion to docs/SPEC.md Β§3 and the E2E learnings accumulated on kanpur. Scope: everything that runs on Windows β€” the Wails GUI (app/), the install pipeline it drives, and the contract between the GUI and the deployer across reboots. Phase-2 boot internals are out of scope (owned by the E2E track).

1. Current state​

app/ is a Wails v2 app (fixed 820Γ—620, vanilla-JS frontend, 4 screens: launchpad β†’ progress β†’ done, plus a control screen when root.disk already exists). The Go backend exposes GetImages, GetSystemInfo, StartInstall (9-step pipeline), CancelInstall, Uninstall, Reboot. Vault handling (sha512-crypt hash, ACL-restricted vault.json, deployer shreds after ingest) is designed correctly and matches SPEC Β§2.3.

Hard facts to anchor planning:

  1. The Windows binary does not compile. GOOS=windows go build ./app fails: downloadFile, copyFile, marshalJSON are referenced but never defined; espDrive is declared and unused in configureBCD. The pipeline has never been executed end-to-end. (Not introduced by recent work β€” it predates the VHDX branch.)
  2. The app installs a boot chain the E2E effort has abandoned. setupGRUB2 copies wubildr.efi + configs to ESP:EFI\wootc\ and points BCD at \EFI\wootc\wubildr.efi. The chain proven on kanpur is: BCD β†’ ESP:EFI\fedora\shimx64.efi (MS-signed) β†’ grubx64.efi (embedded prefix /EFI/fedora) β†’ grub.cfg β†’ deployer kernel + initramfs on the ESP (signed GRUB cannot read NTFS). The reference implementation of the working chain is tests/e2e/setup-wootc.ps1 steps 7–8, not the app.
  3. BCD handling differs from what E2E validated. The app sets both displayorder /addfirst (permanent β€” silently changes the user's default boot) and bootsequence /addfirst (one-shot). E2E uses only the one-shot, which is the right UX: nothing permanent changes until TunaOS actually works.
  4. setupSystemdBoot is a stub, but the GUI already offers a "Bootloader" choice.

2. Target architecture​

2.1 Components​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ Windows ─────────────────────────────┐
β”‚ wootc.exe (Wails) β”‚
β”‚ β”œβ”€ ui/ screens: launchpad, progress, done, control β”‚
β”‚ β”œβ”€ catalog embedded images.json + C:\wootc\images.json β”‚
β”‚ β”œβ”€ preflight SystemInfo + hazard checks (Β§2.4) β”‚
β”‚ β”œβ”€ pipeline ordered, resumable steps (Β§2.2) β”‚
β”‚ β”œβ”€ bootchain ESP layout + BCD (one implementation, shared β”‚
β”‚ β”‚ logic with setup-wootc.ps1 by construction: β”‚
β”‚ β”‚ the .ps1 should shrink to a thin caller or be β”‚
β”‚ β”‚ generated β€” today they are two divergent copies)β”‚
β”‚ └─ statebus reads/writes C:\wootc\state.json (Β§2.3) β”‚
β”‚ β”‚
β”‚ C:\wootc\ β”‚
β”‚ β”œβ”€ install\ deployer-vmlinuz, deployer-initramfs.img, vault.jsonβ”‚
β”‚ β”œβ”€ disks\ root.disk β”‚
β”‚ β”œβ”€ logs\ live-journal.log, deployer-last-journal.log β”‚
β”‚ └─ state.json single source of truth for lifecycle (Β§2.3) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

2.2 Install pipeline (revised step list)​

Replace the current 9 steps with the E2E-proven order; every step must be idempotent so a failed install can be re-run without manual cleanup:

  1. Preflight gate (Β§2.4) β€” abort before touching anything.
  2. Disable Fast Startup.
  3. Create directories.
  4. Create root.disk β€” RAW, sparse-allocated, VDL-extended with fsutil setvaliddata (Linux ntfs3 EIOs past VDL otherwise). VHDX was retired in 8136ae6: target images ship losetup, not qemu-nbd.
  5. Download deployer kernel+initramfs+signed shim/grub with sha256 verification (SPEC Β§3.1 promises this; not implemented β€” release pipeline must publish checksums alongside artifacts).
  6. Stage ESP: EFI\fedora\{shimx64,grubx64}.efi, deployer pair to EFI\wootc\, deployer grub.cfg at the signed GRUB's prefix. ESP capacity is a first-class constraint (Β§3, D2).
  7. Write vault.json (unchanged).
  8. Configure BCD: create "wootc" firmware entry pointing at shim, arm one-shot bootsequence only. No displayorder change.
  9. Write state.json β†’ armed.

2.3 Lifecycle state machine + deployer contract​

The single biggest UX gap: after the reboot, the GUI currently knows nothing. kanpur debugging repeatedly needed exactly this information; the deployer already emits it (phase markers, journal streaming to C:\wootc\logs\) β€” it just has no consumer.

States (persisted in C:\wootc\state.json, written by both sides):

absent β†’ staged β†’ armed β†’ deploying β†’ deployed β†’ healthy
β”‚ β”‚ β”‚
└─────────┴──> failed(phase, error)
  • GUI writes staged, armed.
  • Deployer (deploy.sh) writes deploying on start, then deployed or failed with {phase, error, ts} β€” one jq-free echo of JSON to /mnt/ntfs/wootc/state.json next to the existing log streaming; it already has every needed value in /run/wootc-phase and the fatal message.
  • Phase-2 firstboot (wootc-passthrough or a oneshot unit) writes healthy β€” closing the loop that a real boot succeeded.
  • Control screen renders the state: failed shows the phase + tail of deployer-last-journal.log with a "Retry deploy" button.

Re-arming must restore the deployer grub.cfg first. A successful deploy overwrites the ESP grub.cfg with the Phase-2 menu (this cost a full debugging cycle on kanpur β€” re-arming the BCD entry without restoring grub.cfg boots Phase-2, not the deployer, and if the Phase-2 kernel was pruned from the ESP it dead-ends at a GRUB error). "Retry deploy" = restore deployer grub.cfg + verify deployer pair on ESP + one-shot bootsequence + reboot. This belongs in bootchain as one operation; the ad-hoc restore-deployer-grub.ps1 pattern from kanpur is the prototype.

2.4 Preflight gates (SPEC Β§3.5, mostly unimplemented)​

Blockers (refuse install): not UEFI; no admin; ESP unusable (Β§D2); free space < root.disk size + deployer scratch headroom (the kanpur skopeo copy: exit status 2 failure is what running out looks like β€” surface it before reboot, not in dracut); BitLocker with unexportable recovery path. Warnings (accept + mitigate): BitLocker on (suspend for next boot, per SPEC Β§3.5 script); Fast Startup (already handled); Secure Boot state recorded into state.json (chain differs only in confidence, not layout).

3. Open architecture decisions (need explicit calls)​

D1 β€” EFI\fedora\ collision on real dual-boot machines. The signed Fedora grub has embedded prefix /EFI/fedora; on a machine with an actual Fedora install, wootc would overwrite that distro's grub.cfg and shim. The E2E VM never sees this; real users will. Options: (a) detect an existing EFI\fedora\ with a BLS-populated grub.cfg and refuse (MVP-safe, cheap); (b) coexist: preserve + chainload the original cfg; (c) longer term: ship a differently-prefixed signed chain (requires our own shim signing β€” heavy). Recommendation: (a) now, design toward (b).

D2 β€” ESP capacity. The deployer initramfs is ~135 MB; OEM ESPs are commonly 100–260 MB and kanpur's 512 MB ESP already needed manual pruning. Options: (a) preflight-measure and refuse when it can't fit (with clear message); (b) shrink the deployer initramfs (it embeds podman/skopeo/fisherman β€” a slimmer net-boot-style second stage is a real project); (c) FAT32 helper partition created by shrinking C: and pointing shim's grub there via search. Recommendation: (a) for MVP with telemetry on how often it bites, spike (c) after.

D3 β€” systemd-boot in the GUI. Under Secure Boot, sd-boot is not loadable via shim for EL-family targets (not signed by a trusted vendor key), and nothing in the E2E chain exercises it. Keep the field in InstallConfig for forward-compat but hide the choice in the UI for MVP β€” an option that can't work is a support ticket. Revisit alongside UKI-signed images post-MVP.

D4 β€” where the boot-chain logic lives. Today it exists twice (setup-wootc.ps1 for E2E, Go for the app) and they drifted in opposite directions. Either the app's bootchain package becomes the only implementation and E2E invokes wootc.exe --headless-install (best: E2E then tests the product, not a parallel script), or the .ps1 stays canonical and the app shells out to it. Recommendation: headless mode in wootc.exe; it also gives enterprises unattended install for free.

4. Roadmap (ranked)​

  1. Make it compile + CI gate β€” add the three missing helpers, fix espDrive, add GOOS=windows go build ./app (and go vet) to CI.
  2. Port the proven chain into bootchain β€” shim/grub staging, ESP deployer pair, prefix grub.cfg, one-shot-only BCD (drop displayorder), D1(a) guard, D2(a) preflight.
  3. state.json contract β€” GUI writer + deployer writer (3-line change in deploy.sh's log-streaming block) + control-screen consumer with log tail and "Retry deploy".
  4. Preflight gates β€” space math, BitLocker suspend, checksum verification of downloads.
  5. Headless install mode (D4) β€” then swap E2E's setup-wootc.ps1 body for it.
  6. GUI polish: hide bootloader choice (D3), catalog from embedded JSON file instead of Go literals, GetStatus mutex (races today), surface state.json history on the control screen.

Items 1–4 are the MVP cut. 5 is the structural payoff that stops the Windows/E2E drift from ever recurring.