Skip to content

fix(installer): make Arch installs work and stop the installer destroying data - #197

Open
Albe841114 wants to merge 4 commits into
Gentleman-Programming:mainfrom
Albe841114:fix/arch-installer-cachyos
Open

Albe841114 wants to merge 4 commits into
Gentleman-Programming:mainfrom
Albe841114:fix/arch-installer-cachyos

Conversation

@Albe841114

Copy link
Copy Markdown

Linked issues

Closes #183carapace is AUR-only, and pacman is atomic, so the whole install aborts
Closes #193rm -rf Gentleman.Dots on a CWD-relative path
Closes #195 — backup aborts when a directory contains a Unix socket

Partially addresses #190: the --dry-run no-op is fixed here. The Kitty selection point in that issue is not addressed.

Not fixed here, listed so the scope is unambiguous: #194 (E2E pipefail) and #196 (fish configuration overwritten). Both are real and reported, but neither is touched by this branch.

Summary

  • Makes the installer complete on Arch/CachyOS, where it currently aborts at the shell install step.
  • Stops the installer from deleting a directory it did not create, and from deleting one that holds unpublished Git work.
  • Makes --dry-run actually dry, and lets the backup survive Unix sockets.

Why it currently fails on Arch

The shell step runs one atomic pacman -S that includes carapace. That package does not exist in core/extra — it is AUR-only, published as carapace-bin, which does not declare provides=carapace. Because a pacman transaction is atomic, one unresolvable name aborts the whole thing and nothing installs. zsh-theme-powerlevel10k has the same problem.

Changes

File Change
installer/internal/tui/installer.go AUR-only names removed from the Arch lists for fish, zsh and nushell (lines 720, 775, 831); clone deletion routed through a safety check instead of an unconditional rm -rf
installer/internal/system/repostate.go New: classifies a directory before deletion and fails closed when the state cannot be determined
installer/internal/system/exec.go CopyFile/CopyDir skip non-regular files (sockets, FIFOs, device nodes) instead of erroring out
installer/internal/system/dryrun.go New: --dry-run gate, including the os.RemoveAll path that the Run/RunWithLogs gate did not cover
installer/internal/tui/arch_packages_source_test.go New: regression guard that reads the real package-list literals in installer.go, so an AUR-only name cannot come back

What "safe to delete" means here

This part exists because the guard written earlier in this change destroyed this change's own commits during development.

A clean working tree is not sufficient proof that a checkout can be deleted. A clean checkout can hold local commits that exist on no remote: git status --porcelain is empty and the work is still unrecoverable. Committing the work made it more deletable, not less.

The guard now requires: working tree clean, no untracked files, no stashes, and HEAD reachable from a remote-tracking branch. A branch with no configured upstream is treated as unsafe. It never prompts — --non-interactive is documented as CI-friendly, so a refusal is a deterministic abort, and callers decide whether that is fatal.

Test plan

  • go test ./...system and tui packages pass
  • go build — clean
  • RED verified by hand per unit: the implementation was reverted to confirm each test actually fails without it. This caught a false test whose inline fixture could never fail; it was rewritten to read the real source literals, which is why arch_packages_source_test.go exists.
  • Arch package names checked against the archlinux.org API rather than the local machine — CachyOS carries its own repositories and would have produced false positives.
  • Installer run end to end on CachyOS: completes, with starship, zoxide, atuin, fzf and nvim installed.

Notes for review

  • Branch name and commits follow the repository's conventions.
  • No type:* label is set: this account has read-only permission here and cannot apply labels.
  • openspec/changes/archive/2026-08-29-fix-arch-installer/ carries the proposal, design, tasks and verification report. Happy to drop those files if they are not wanted in-tree — the code changes stand on their own.

Four defects reproduced on CachyOS/Arch:

- carapace and zsh-theme-powerlevel10k are AUR-only, and pacman is
  atomic, so one unresolvable name aborted every Arch install
- CopyDir handed Unix sockets to os.ReadFile, so one live socket failed
  the backup of every config
- rm -rf ran on a relative path at two sites with no git check,
  destroying uncommitted work
- --dry-run set GENTLEMAN_DRY_RUN and nothing read it

Refs Gentleman-Programming#182, Gentleman-Programming#174, Gentleman-Programming#190, Gentleman-Programming#192
A clean working tree proved only that nothing was uncommitted, not that
nothing would be lost. Committing local work made a checkout *more*
deletable, not less: `git status --porcelain` went empty, the guard
classified it as a clean checkout, and removal destroyed commits that
existed on no remote. This was observed destroying real work.

Add RepoUnpublishedWork. Deletion now requires the working tree to be
clean AND the repository's work to be published: no stash entries, and
HEAD contained by at least one remote-tracking branch. A repository with
no remotes, or one whose publication cannot be determined, fails closed.

The guard must not over-block either: a branch with no upstream whose
HEAD is already on a remote loses no commits, so it stays deletable.
Only the branch name would go, and that is not work.

Two earlier tests encoded the old assumption by building fixtures with
no remote and expecting deletion; their fixtures now publish to a bare
origin, so they still exercise the delete path.
Merge the four delta specs into openspec/specs/ and move the change
folder to changes/archive/2026-08-29-fix-arch-installer/.

The archive report records what the proposal and design do not: five
work units shipped, not four. Unit 5 was added after verification, when
the installer destroyed this change's own commits and exposed a flaw in
unit 3's guard — a clean working tree proves only that nothing is
uncommitted, not that nothing would be lost.

Also recorded: three defects found while doing this work that had no
upstream issue at the time — backup dying on Unix sockets, the
destructive relative-path rm -rf, and the E2E harness masking per-image
exit status. They have since been reported upstream as Gentleman-Programming#195, Gentleman-Programming#193 and
Gentleman-Programming#194 respectively, alongside Gentleman-Programming#196 for the fish configuration being
overwritten without a backup.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant