Skip to content

feat: setup unification — one packages.json + root setup.sh/verify.sh, shims, dotfiles (#36, Phases 1-7)#37

Merged
ulises-c merged 33 commits into
mainfrom
feat/setup-unification
Jun 15, 2026
Merged

feat: setup unification — one packages.json + root setup.sh/verify.sh, shims, dotfiles (#36, Phases 1-7)#37
ulises-c merged 33 commits into
mainfrom
feat/setup-unification

Conversation

@ulises-c

@ulises-c ulises-c commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Summary

All phases of the setup-script unification (#36, spec in UNIFICATION.md): one root packages.json + one root setup.sh + one root verify.sh backed by shared engines, with platform quirks isolated in small modules. The per-folder scripts are thin shims onto the root entrypoints and the legacy per-folder JSONs are gone.

Phase 1 — unified data + parity harness (b9ebac3)

  • Root packages.json (96 entries) merged from the three per-folder JSONs per the migration rules: package_manager keyed by {macos,ubuntu,arch,server}, work:trueenvironment:["work"], legacy curl manager folded into custom, <platform>_name overrides preserved.
  • scripts/parity-check.sh replicates each legacy script's exact jq selection and diffs it against the planned unified-core semantics: 231 checks, 0 failures.

Phase 2 — engine extraction + root dispatcher (a40fa60)

  • lib/core.sh: arg parsing, platform detection (--platform/--distro override, --profile server), dry-run wrappers, jq selection engine, npm/pnpm supply-chain cooldown, config deploys, shared linux desktop flow.
  • platforms/{macos,ubuntu,arch,server}.sh: bootstrap + manager quirks only (Homebrew, apt repos + snap, yay hardening, server services).
  • Root setup.sh: detect platform → source core + module → run.
  • Gate: scripts/dryrun-parity.sh diffs normalized old-vs-new --dry-run output for all 22 platform × flag combos — 0 failures (mutation-tested: demoting one package's priority trips 9 failures).

Phase 3 — shared verify engine + root verify.sh (a8d4149)

  • lib/verify.sh: read-only check engine over root packages.json, reusing lib/core.sh's jq defs and platform detection. Per-manager probe semantics are platform-faithful ports of macOS/verify.sh and linux-desktop/verify.sh, including their asymmetries (legacy macOS has no command -v fallback for casks/pipx/app-store; legacy linux falls back everywhere and probes pacman for custom packages).
  • Root verify.sh: flags mirror setup.sh plus --all; server is rejected (no legacy verify to unify — Phase 3 scope is macOS + linux-desktop).
  • Gate: scripts/verify-parity.sh runs legacy vs root verify for all 21 platform × flag combos — 0 failures on normalized check lines (mutation-tested: breaking the apt probe trips 9 failures, dropping a section trips all 21).
  • Intentional change, excluded from the status diff but count-asserted: pnpm-managed packages (codeburn) are now actually verified — legacy scripts always reported them missing ("unknown manager").

Phase 4 — thin shims + docs (95e7425, hardened in 8286bb9)

  • macOS/setup.sh, macOS/verify.sh, linux-desktop/setup.sh, linux-desktop/verify.sh, linux-server/setup.sh are now one-line shims that exec the root scripts — documented per-folder usage keeps working (verified byte-identical output, including zsh macOS/... invocation). The macOS/server shims pin --platform after "$@" so the folder identity wins over stray flags; the linux-desktop shims hard-error on non-Linux hosts, preserving the legacy unsupported-distro guard.
  • Gates dryrun-parity.sh/verify-parity.sh deleted: with the legacy scripts gone they self-compare trivially. Last meaningful green run (22/22 and 21/21) at a8d4149. In their place, scripts/dryrun-smoke.sh (root setup.sh --dry-run must exit clean with a full plan on all four platforms) runs locally and as a new CI job. parity-check.sh (JSON-level) stays valid until Phase 5.
  • verify_parse_args gains --profile, so verify.sh --profile server hits the intended server rejection instead of warning and checking the wrong platform.
  • macOS/ scripts are bash now — shellcheck exclusions lifted in pre-commit and CI.
  • Docs: root README setup section, per-folder READMEs point at the root entrypoints, new repo CLAUDE.md.
  • Behavior note: macOS/verify.sh now gates work-tagged packages on --work/--all (matching setup.sh) instead of checking them unconditionally.

Phase 5 — cleanup (0ba9c22)

  • Deleted the three legacy per-folder package JSONs (nothing read them since Phase 2) and scripts/parity-check.sh with them (its inputs are gone; final run immediately before deletion: 231/231).
  • Server claude-code install folded into packages.json (package_manager.server: "custom"); platforms/server.sh reads the command via custom_cmd instead of hardcoding it.
  • Legacy custom-install gap closed: by-name-handled customs (pyenv, nvm, claude-code, tailscale, docker) are marked handled_by_setup: true; print_custom_reminders moved into lib/core.sh and the desktop flow now calls it, so ubuntu prints manual install commands for git-xet/claude-desktop instead of silently skipping them (reminders, not auto-runs — both need sudo/repo setup).
  • Gate: full-flag dry-run diffed against a pre-change baseline on all four platforms — byte-identical except the intended ubuntu reminders section.
  • Resolves the UNIFICATION.md open questions: server stays a platform key (A); install_command keeps both string and object forms; priority: "none" tier kept.

Phase 6 — merge main + port in-flight features (54e1b1e)

Phase 7 — dotfiles consolidation (56bdd45)

  • New dotfiles/ for configs shared verbatim across platforms: tmux.conf (was duplicated in linux-desktop/ + linux-server/) and ghostty.config (was duplicated in macOS/ + linux-desktop/). All engine deploy sites repointed; per-folder copies deleted.
  • macOS now deploys ~/.tmux.conf — the legacy macOS setup never did, leaving Macs on the default green status bar.
  • ghostty keeps a single universal config (the copies were already byte-identical).
  • zshrc override system (de7e43a): dotfiles/zshrc.example is the shared base, deployed by the new deploy_zshrc helper; a platform folder shipping its own zshrc.example overrides it. Only linux-server/ does — headless, so its zshrc drops the Ghostty/fastfetch/notification hooks. macOS is not part of zshrc deploys (its flow appends to the user's existing ~/.zshrc; macOS/zshrc.example stays reference-only).
  • Gate: per-platform dry-run diff vs pre-change baselines — identical everywhere except macOS's new tmux step and the ubuntu/arch zshrc source path moving to dotfiles/; the server diff proves the override still selects linux-server/zshrc.example.
  • macOS zsh unification (a95fbc2): the dotfiles zshrc is fully cross-platform — macOS bits guarded on /opt/homebrew/$OSTYPE (Homebrew PATH+FPATH, brew antidote/p10k/fzf paths, ls -G, brewup, PIPX_DEFAULT_PYTHON, nvm --delete-prefix); zsh_plugins.txt moves to dotfiles/; macOS deploys both via the engine instead of appending lines to ~/.zshrc (add_to_zshrc deleted; old ~/.zshrc backed up on first deploy). antidote/zoxide/fzf/bat/fd/terminal-notifier gain macos: brew; the brew zsh-autosuggestions/zsh-syntax-highlighting entries are dropped (antidote manages the plugins — brew uninstall the old formulae after migrating). verify.sh gains the matching macOS config checks. macOS/zshrc.example + zshrc-upgrade.md deleted — that upgrade plan ships in full here.

Bug fix surfaced by the gate (21061bf)

Legacy --optional runs crashed mid-script under set -e when a tier had no pnpm packages — linux-desktop died before the Docker step, macOS before the work/App Store sections. Fixed in the legacy scripts so the gate compares intended behavior.

Intentional behavior differences (documented in the gate headers)

  • macOS work packages install inside their priority tier instead of a separate end-of-run section (same package set).
  • Server tailscale/docker/claude installs go through run_eval — legacy piped run's dry-run echo into sudo sh.
  • Server default-shell check reads the real login shell via getent instead of $SHELL.
  • Verify: pnpm packages get real checks; legacy macOS verify checks work-tagged packages unconditionally (it predates env filtering), so the parity gate passes --work --personal on macOS rows.

Test plan

  • bash scripts/parity-check.sh — 231/231
  • bash scripts/dryrun-parity.sh — 22/22 (run on Ubuntu host; covers all four platforms via --dry-run)
  • bash scripts/verify-parity.sh — 21/21 (same host; both sides resolve host state identically) — both gates re-run green at a8d4149 immediately before the shim conversion, then deleted
  • bash scripts/dryrun-smoke.sh — 4/4 platforms (also a CI job now)
  • Shim parity — each shim's output diffed byte-identical vs direct root invocation (incl. stub-brew macOS run, zsh invocation, and pin-override attempts like linux-server/setup.sh --distro ubuntu)
  • Code review of the Phase 4 diff (7 finder angles) — findings fixed in 8286bb9
  • bash scripts/parity-check.sh — final 231/231 run immediately before its deletion in Phase 5
  • Phase 5 baseline diff — full-flag --dry-run on all four platforms vs pre-change output: identical except the intended ubuntu reminders section; live verify.sh --work still green on the Ubuntu host
  • Code review of the Phase 5 diff — one dormant bug (reminder-tier concatenation) fixed before push
  • Phase 6 merge gate — per-platform dry-run diff vs pre-merge snapshot: only the ported p10k/lact additions; dryrun-smoke 4/4 post-merge
  • Phase 7 gate — per-platform dry-run diff vs pre-change baseline: identical except macOS's new tmux deploy; dryrun-smoke 4/4; live verify.sh --work green
  • macOS zsh unification gate — dry-run diff vs baseline: ubuntu/arch/server unchanged, macOS shows exactly the intended package/deploy changes; zsh -n on the unified zshrc; reviewed (4 findings fixed: stale TODO section, missing macOS verify checks, stale plugin docs; p10k instant-prompt caveat documented)
  • Live verify.sh --work on Ubuntu host — same 57 checks as legacy (57 ok vs legacy 56 ok + 1 missing: the pnpm fix)
  • shellcheck --severity=warning clean on all new scripts
  • Security review of branch changes — no findings (re-run after Phase 3: no findings)
  • Live run on CachyOS desktop (bash setup.sh --dry-run, then real run; bash verify.sh --work) before merge — per UNIFICATION.md only the Arch path can be exercised live

🤖 Generated with Claude Code

ulises-c and others added 6 commits June 9, 2026 19:57
)

Merge the three per-folder package JSONs (macOS 59, linux-desktop 61,
linux-server 31 entries) into one root packages.json (96 entries) per the
UNIFICATION.md migration rules:

- package_manager is an object keyed by platform {macos,ubuntu,arch,server}
- macOS work:true -> environment:["work"]
- legacy curl manager (and macOS mongodb brew+install_command) folded into
  custom; install_command accepts a bare string or a per-platform object
- <platform>_name overrides preserved and extended (macos_name/server_name)
- packages whose priority/environment/optional genuinely differ across
  platforms keep one entry per platform group (git, jq, discord, hf, git-xet,
  notion, llama.cpp, octave, zsh-autosuggestions, zsh-syntax-highlighting,
  docker vs docker-ce) instead of new override machinery

scripts/parity-check.sh replicates each legacy script's exact jq selection
on its old JSON and diffs against the planned unified-core semantics on the
new JSON, across platform x manager x priority x work/personal combos:
231 checks, 0 failures. Legacy JSONs and scripts untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t -e

pnpm_install returned 1 when a tier had no pnpm packages ([[ -n ]] && run),
which errexit turns into a mid-run crash: linux-desktop --optional died
before the Docker step and macOS --optional before the work/App Store
sections. Guard with an explicit early return instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…root setup.sh (#36)

Extract the shared install engine from linux-desktop/setup.sh (the superset)
into lib/core.sh: arg parsing, platform detection (uname + /etc/os-release,
--platform/--distro override, --profile server), run/run_eval dry-run
wrappers, jq package selection over root packages.json (envok/icfor/pname —
lifted from the Phase 1 parity harness), npm/pnpm supply-chain cooldown,
config deploys, and the shared linux desktop flow.

Platform quirks live in platforms/<platform>.sh:
- macos: Homebrew bootstrap, brew/cask/custom tiers, .zshrc line management,
  expat-pinned pyenv build, XDG Ghostty config, App Store reminders
- ubuntu: apt+snap tiers, external apt repos, batcat/fdfind symlinks,
  curl-based Tailscale/Docker
- arch: yay bootstrap via pacman, hardened batch installs with per-package
  retry, pacman -T pyenv deps, systemd enables
- server: apt-only profile, Tailscale/Docker/Cockpit, homepage stack,
  .env fills, claude-code (stays hardcoded until Phase 5 folds it into data)

Root setup.sh detects the platform and dispatches. Legacy per-folder scripts
are untouched (shims come in Phase 4).

Gate: scripts/dryrun-parity.sh runs old vs new --dry-run for all 22
platform × flag combos and diffs normalized install/eval lines (batches
split per package, eval: prefix stripped, backup timestamps canonicalized,
order-insensitive) — 22/22 pass. Phase 1 parity-check.sh still passes
231/231. shellcheck --severity=warning clean.

Intentional changes vs legacy, absorbed by normalization or out of gate
scope: macOS work packages install inside their priority tier instead of a
separate section; server tailscale/docker/claude installs go through
run_eval (legacy piped run's dry-run echo into 'sudo sh'); server default
shell check reads the real login shell via getent instead of $SHELL.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
tmux.conf is duplicated in linux-desktop/ and linux-server/ and missing
from macOS/ entirely; ghostty.config is duplicated in macOS/ and
linux-desktop/. Plan a dotfiles/ directory as a follow-up PR once #37
merges and the root engine owns config deploys.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extract the read-only verification logic from macOS/verify.sh and
linux-desktop/verify.sh into lib/verify.sh, driven by the root
packages.json via lib/core.sh's jq defs (envok/pname) and platform
detection. Root verify.sh dispatches; flags mirror setup.sh plus --all
(adds work+personal+optional and the priority-"none" tier).

Check semantics are platform-faithful ports, including the asymmetries:
legacy macOS has no command -v fallback for casks/pipx/app-store (only
brew formulas, labeled "via system/other"); legacy linux falls back to
command -v everywhere and probes pacman for custom packages. Labels use
the linux "name (mgr:rname)" format. Server has no legacy verify.sh and
is rejected with an error (out of Phase 3 scope).

Gate: scripts/verify-parity.sh runs legacy vs root verify for all 21
platform × flag combos on the same host and diffs normalized check
lines (sorted; legacy curl→custom and macOS mongodb brew→custom manager
renames; macOS "name (mgr:rname)" collapsed to the legacy install-token
form). 21/21 pass; mutation-tested (breaking the apt probe trips 9
failures, dropping the medium section trips all 21). Phase 1/2 gates
still pass (231 + 22 checks); shellcheck --severity=warning clean.

Intentional change vs legacy, excluded from the status diff but count-
asserted: pnpm-managed packages (codeburn) are now actually verified —
legacy scripts always reported them missing ("unknown manager"). Legacy
macOS verify checks work-tagged packages unconditionally (it predates
env filtering), so the gate passes --work --personal on macOS rows.

Live on this Ubuntu host: root verify.sh --work runs the same 57 checks
as legacy, 57 ok vs legacy's 56 ok + 1 missing (the pnpm fix above).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ulises-c ulises-c changed the title feat: setup unification Phases 1-2 — unified packages.json + root setup.sh (#36) feat: setup unification Phases 1-3 — unified packages.json + root setup.sh + verify.sh (#36) Jun 10, 2026
ulises-c and others added 2 commits June 10, 2026 00:37
…oot entrypoint docs (#36)

Convert macOS/setup.sh, linux-desktop/setup.sh, linux-server/setup.sh,
macOS/verify.sh, and linux-desktop/verify.sh into thin shims that exec the
root scripts with the right --platform, so documented per-folder usage keeps
working. Shim output diffed byte-identical against direct root invocation
for every platform (macOS via stubbed brew, and via zsh invocation).

Behavior note: macOS verify now gates work-tagged packages on --work/--all
(matching setup.sh) instead of checking them unconditionally — the legacy
script predated env filtering.

Delete scripts/dryrun-parity.sh and scripts/verify-parity.sh: once the
legacy scripts are shims, both gates compare the root scripts against
themselves and pass trivially. They did their job — last meaningful green
run (22/22 and 21/21) at a8d4149. scripts/parity-check.sh stays: it compares
JSONs, not scripts, and remains valid until Phase 5 deletes the legacy JSONs.

Docs: root README gains a Setup section for setup.sh/verify.sh; per-folder
READMEs point at the root entrypoints; new repo CLAUDE.md documents the
architecture and conventions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… dry-run smoke gate (#36)

Review findings on the Phase 4 commit, fixed:

- Shims pin --platform AFTER "$@" so the folder's identity wins over stray
  --distro/--platform flags (core_parse_args is last-wins; previously
  `bash linux-server/setup.sh --distro ubuntu` would have installed the
  desktop set on the server — legacy scripts ignored stray flags).
- linux-desktop shims hard-error on non-Linux hosts, restoring the legacy
  unsupported-distro guard (previously they'd auto-detect macos on a Mac
  and run the Homebrew installer).
- Shims resolve the repo root from BASH_SOURCE (fallback $0 for zsh), so
  bash-sourcing one no longer execs a setup.sh relative to the cwd.
- verify_parse_args accepts --profile, so `verify.sh --profile server` hits
  the intended server rejection instead of warning and running the
  auto-detected platform's checks.
- New scripts/dryrun-smoke.sh + CI job: root setup.sh --dry-run must exit
  clean with a full install plan on all four platforms — without it,
  nothing executed the root scripts after the parity gates were deleted.
- macOS/ scripts are bash now: lifted the shellcheck exclusions in
  pre-commit and CI, dropped them from the zsh -n globs.
- Doc fixes: CLAUDE.md documents verify.sh's real flag set; README no longer
  implies linux-server has a verify shim; TODO marks the deleted gates in
  past tense; brew_packages.md expat note points at platforms/macos.sh.

Verified: shim output byte-identical to direct root invocation (incl. via
zsh and with pin-override attempts); smoke gate green on all platforms;
shellcheck clean including macOS/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ulises-c ulises-c changed the title feat: setup unification Phases 1-3 — unified packages.json + root setup.sh + verify.sh (#36) feat: setup unification Phases 1-4 — unified packages.json + root setup.sh + verify.sh + shims (#36) Jun 10, 2026
@ulises-c ulises-c linked an issue Jun 10, 2026 that may be closed by this pull request
9 tasks
… claude-code into data (#36)

Delete macOS/macOS_packages.json, linux-desktop/linux_desktop_packages.json,
and linux-server/linux_server_packages.json — root packages.json has been the
single source of truth since Phase 2, and nothing read them anymore.
scripts/parity-check.sh goes with them (its inputs are gone); final run
immediately before deletion: 231/231 green.

Fold the server claude-code install into packages.json: claude-code gains
package_manager.server = custom, and platforms/server.sh reads the command
via custom_cmd instead of hardcoding it (dry-run output unchanged).

Close the legacy custom-install gap: customs that the engine runs by name
(pyenv, nvm, claude-code, tailscale, docker) are now marked
handled_by_setup: true; print_custom_reminders moves from platforms/server.sh
into lib/core.sh and the desktop flow calls it, so ubuntu prints manual
install commands for git-xet and claude-desktop instead of silently skipping
them (legacy scripts never executed those entries' install_commands; they're
reminders rather than auto-runs because both need sudo/repo setup).

Verified: full-flag dry-run diffed against a pre-change baseline on all four
platforms — byte-identical except the new ubuntu reminders section (server
diff is only the backup timestamp); dryrun-smoke 4/4; live verify.sh --work
green on the ubuntu host; shellcheck + jq clean.

Resolves the UNIFICATION.md open questions: server stays a platform key (A),
install_command keeps both string and object forms, priority "none" tier kept.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ulises-c ulises-c changed the title feat: setup unification Phases 1-4 — unified packages.json + root setup.sh + verify.sh + shims (#36) feat: setup unification Phases 1-5 — one packages.json + root setup.sh/verify.sh, per-folder shims (#36) Jun 10, 2026
ulises-c and others added 2 commits June 10, 2026 01:31
…ified stack (#36)

Merge origin/main (PRs #28 powerlevel10k/LACT, #35 railguard fixes,
#38 claude-hud) into the unification branch. The two PRs that touched the
legacy setup stack landed while this branch converted it to shims, so their
features are ported into the unified system as part of the conflict
resolution:

- packages.json gains zsh-theme-powerlevel10k (arch/yay, high) and lact
  (arch/yay, medium) — the entries main added to the now-deleted
  linux-desktop/linux_desktop_packages.json.
- The shared desktop flow (lib/core.sh) deploys ~/.p10k.zsh from
  linux-desktop/p10k.zsh.example via deploy_config — the step main added to
  the legacy linux-desktop/setup.sh, which stays a shim.
- Everything else from main merges clean: zshrc.examples, ghostty font,
  p10k.zsh.example, LACT configs/docs, railguard and claude-hud config.

Gate: per-platform full-flag dry-run diffed against the pre-merge snapshot —
macOS and server unchanged (timestamp only); ubuntu/arch show exactly the
intended additions (p10k deploy, the two arch packages, and the zshrc
update caused by main's new zshrc.example content). dryrun-smoke 4/4;
shellcheck + jq clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s tmux (#36)

Create dotfiles/ for configs shared verbatim across platforms and move the
two byte-identical duplicates there: tmux.conf (was linux-desktop/ +
linux-server/) and ghostty.config (was macOS/ + linux-desktop/). All engine
deploy sites repoint to $SETUP_ROOT/dotfiles/: the shared desktop flow and
ghostty_deploy_linux in lib/core.sh, the server zsh/tmux block, and the
macOS ghostty step.

macOS additionally gains a tmux.conf deploy (via the shared deploy_config) —
the legacy macOS setup never deployed one, leaving Macs on the default green
status bar while Linux boxes got the configured blue top bar.

The zshrc variants intentionally differ per platform and stay per-folder.
Ghostty keeps a single universal config — no overlay mechanism until an
OS-specific setting actually exists.

Gate: per-platform full-flag dry-run diffed against a pre-change baseline —
identical on ubuntu/arch/server (timestamp noise only); macOS shows exactly
the new tmux deploy step. dryrun-smoke 4/4; live verify.sh --work green;
shellcheck clean. Docs updated (CLAUDE.md, linux-server README files list,
ghostty_config.md — also synced its stale config snippet with the real file).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ulises-c ulises-c changed the title feat: setup unification Phases 1-5 — one packages.json + root setup.sh/verify.sh, per-folder shims (#36) feat: setup unification — one packages.json + root setup.sh/verify.sh, shims, dotfiles (#36, Phases 1-7) Jun 10, 2026
ulises-c and others added 14 commits June 10, 2026 01:46
…h server override (#36)

Move linux-desktop/zshrc.example to dotfiles/zshrc.example as the shared
zshrc base, deployed by the new deploy_zshrc helper in lib/core.sh. The
helper implements the override system: a platform folder shipping its own
zshrc.example wins over the dotfiles base. Only linux-server/ does — it is
headless, so its zshrc drops the Ghostty/fastfetch/notification hooks.

macOS is unaffected: its flow appends lines to the user's existing ~/.zshrc
(add_to_zshrc) rather than deploying a file, and macOS/zshrc.example stays
as reference documentation (zshrc-upgrade.md parity pointers now reference
dotfiles/zshrc.example).

Gate: per-platform full-flag dry-run diffed against a pre-change baseline —
macOS identical; ubuntu/arch differ only in the cp source path (now
dotfiles/); server differs only in backup timestamps, proving the override
still selects linux-server/zshrc.example. dryrun-smoke 4/4; shellcheck and
zsh -n clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…h stack (#36)

dotfiles/zshrc.example is now the one zshrc for macOS, Ubuntu, and Arch.
macOS-specific bits are guarded so they no-op on Linux: Homebrew PATH +
completions FPATH (before compinit), brew source paths for antidote /
powerlevel10k / fzf alongside the /usr/share ones, BSD `ls -G`, brewup,
PIPX_DEFAULT_PYTHON via pyenv shims (Tahoe bottle fix), and nvm
`--delete-prefix default` (brew-node shadowing). GPG_TTY moves into the
shared base. zsh_plugins.txt moves to dotfiles/ (list was already what the
macOS migration plan called for, including zsh-notify).

platforms/macos.sh switches from appending lines to the user's ~/.zshrc
(add_to_zshrc — deleted) to deploying the shared zshrc + plugin list via the
engine; the previous ~/.zshrc is backed up on first deploy. In-process
exports during setup are unchanged.

packages.json: zsh-antidote/zoxide/fzf/bat/fd gain macos:brew (antidote via
macos_name); new terminal-notifier entry (zsh-notify's macOS backend); the
brew zsh-autosuggestions/zsh-syntax-highlighting entries are gone — antidote
manages those plugins now (brew uninstall the old formulae after migrating;
the server's apt entries are untouched).

verify.sh gains macOS checks for ~/.zshrc, ~/.zsh_plugins.txt, ~/.tmux.conf,
and the brew antidote path, mirroring the linux extras.

macOS/zshrc.example and macOS/zshrc-upgrade.md deleted — the upgrade plan is
fully absorbed (every item on its list ships here). Docs/TODO updated.

Known caveat (pre-existing, now also on macOS): the deployed p10k config
sets POWERLEVEL9K_INSTANT_PROMPT=verbose while fastfetch prints during
init — p10k may warn on new tabs; set instant prompt to quiet in
~/.p10k.zsh if it nags.

Gate: per-platform full-flag dry-run vs pre-change baseline — ubuntu/arch/
server timestamp-only; macOS shows exactly the intended changes (antidote+
zoxide in high tier, fzf/bat/fd/terminal-notifier in medium, the two plugin
formulae gone, 10 add-to-zshrc lines replaced by zshrc+plugins deploys).
zsh -n, shellcheck, jq, dryrun-smoke 4/4, live verify.sh --work all green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
High-resolution mouse wheels emit ~7-10 events per notch and Ghostty
reports each as a full click, flooding mouse-mode TUIs — micro inserts
the mangled sequence fragments as text. mouse-scroll-multiplier = 0.25
cuts the burst below the breaking point and fixes the too-fast scroll
feel in TUIs (ghostty-org/ghostty#4259).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
date +%s%3N and bare timeout are GNU-isms — BSD date emits a literal N
(breaking the elapsed-ms arithmetic) and macOS only ships gtimeout via
coreutils. now_ms() falls back to python3; the runner is resolved via
command -v and skipped if absent.

pytest is now invoked as python -m pytest (uv run --no-sync python -m
pytest when uv.lock is present): console-script bins are absent in uv
--no-sync venvs, and --no-sync keeps the hook from re-resolving envs
with pinned torch builds. A probe import skips the suite cleanly when
pytest isn't in the env.

Fixes #33
Fixes #30

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…l on macOS

- uv joins the package list (brew / pipx / yay — pipx rides the existing
  ubuntu tier, no engine changes). Fixes #24
- brew cask installs use --adopt so an app already in /Applications
  (manual install) is taken over instead of hard-failing the whole
  bulk-install tier. Fixes #31
- claude-code on macOS switches from brew cask to the official curl
  installer via the shared claude_code_step (self-updates in place,
  already guarded + dry-run aware; verify.sh already probes it with
  command -v claude). Part of #39

Gated: per-platform full-flag dry-run diff shows only the intended
lines; scripts/dryrun-smoke.sh passes on all four platforms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… alias

- apt_bootstrap gates the GitHub CLI apt repo on the sources file
  instead of command -v gh, so machines with a stale distro gh
  (Ubuntu universe 2.45, Projects-classic GraphQL breakage) get
  migrated to the official repo on their next run. Fixes #25
- Arch now installs rustup (arch_name) instead of the raw rust
  package, matching Ubuntu's existing ubuntu_name override; a new
  rust_toolchain_step runs 'rustup default stable' on fresh boxes
  where rustup ships without a toolchain. cargo's ~/.cargo/bin
  joins PATH in the shared zshrc. Part of #39
- The shared zshrc aliases the App Store Tailscale's bundled CLI
  on macOS (kept App Store per network-extension entitlements).
  Fixes #22

Gated: per-platform dry-run diff shows only intended lines;
dryrun-smoke, shellcheck, zsh -n, jq all pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ence batch)

Web-verified against upstream/AUR/brew as of 2026-06:

- git-xet (arch): AUR git-xet-bin packages the defunct pre-acquisition
  XetHub client (flagged out-of-date, 0 votes), not HF's git-xet —
  switch to the official install.sh reminder already used on ubuntu
- hf (arch): extra/python-huggingface-hub is stale (flagged 2026-05) —
  pipx like ubuntu, restoring the [cli] extras
- micro (ubuntu): noble apt frozen at 2.0.13 — official classic snap
  tracks releases (server stays apt; Debian carries newer)
- nvtop (ubuntu): noble 3.0.2 vs 3.3.x — snap published by the
  upstream author
- obs-studio (ubuntu): codify the official obsproject PPA in
  apt_bootstrap (this desktop already runs PPA 32.1.2 manually)
- docker (macos): cask renamed docker-desktop; bare token now collides
  with the CLI-only formula

Part of #39

Gated: per-platform dry-run diff shows only intended lines;
dryrun-smoke, shellcheck, jq pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…not Debian

The server platform targets the Ubuntu Server 24.04 box, which has the
same frozen noble universe as the desktop AND ships snapd preinstalled —
so the apt/snap split between the two noble platforms was unjustified.
snap_install_tier moves from ubuntu.sh to core.sh and the server flow
gains a snap section.

Docs carried the same wrong premise ("Raspberry Pi / Debian"): the Pi is
a separate, not-yet-onboarded target (TODO.md); server.sh header and
CLAUDE.md now say so.

Part of #39

Gated: server dry-run gains only the snap section lines; other
platforms unchanged; dryrun-smoke, shellcheck, jq pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
From a brew-leaves audit of the Mac mini M4 and the MBP (all Macs are
M-series):

- New entries: forgejo-cli, go, codespell, mlx-lm, swiftlint, swiftformat,
  opencode (anomalyco tap / AUR opencode-bin / curl reminder on Ubuntu),
  obsidian (classic snap on Ubuntu), anki, zen-browser (flatpak reminder
  on Ubuntu — engine has no flatpak manager)
- rust/shellcheck/ripgrep/nmap gain macos: brew on their existing entries
- Server migrates off apt zsh-autosuggestions/zsh-syntax-highlighting to
  antidote: noble has no zsh-antidote package, so setup clones it to
  ~/.antidote and deploys linux-server/zsh_plugins.txt (headless subset —
  no zsh-notify, no fpath completions since compinit runs before
  antidote load in the server zshrc)
- TODO.md records the per-Mac live-run cleanup lists (testing leftovers,
  pinned pythons, brew zsh plugins, claude-code cask)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…view of 2fb881b

Code-review findings on the brew-leaves audit commit:

- lib/verify.sh: probe forgejo-cli via its actual binary (fj) and
  zen-browser via its flatpak app id — the generic custom-manager
  fallback could never see either, so verify reported them missing
  forever after a correct install
- platforms/server.sh: gate the antidote install on antidote.zsh
  (not the bare directory), and pre-clone the plugins with
  `antidote bundle` right after deploying zsh_plugins.txt, while
  network is provably up — first login no longer depends on GitHub
- linux-server/zshrc.example: also require ~/.zsh_plugins.txt before
  running antidote load, restoring the deleted apt sourcing's
  graceful degradation when files are missing
- linux-server/apt_packages.md: replace the stale apt plugin entries
  with antidote, and correct micro/nvtop to snap (stale since
  fe5999f); renumbered

Gates: shellcheck + zsh -n clean, dryrun-smoke passes all 4
platforms, server dry-run diff shows only the new pre-clone line.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The git-clone path was built on a wrong premise: noble/universe carries
zsh-antidote 1.9.4-1 (verified via apt-cache; the earlier "no noble
package" claim came from a misread packages.ubuntu.com lookup). The
server now installs it like every other platform:

- packages.json: server: apt; drop the custom install_command +
  handled_by_setup; restore the platform-neutral description
- platforms/server.sh: remove the clone block; the plugin pre-clone
  step now sources /usr/share/zsh-antidote/antidote.zsh
- linux-server/zshrc.example: source the apt path (same as the
  desktop zshrc), keeping the zsh_plugins.txt guard
- linux-server/apt_packages.md: antidote entry is an apt package again

Gates: jq/shellcheck/zsh -n clean, dryrun-smoke passes all 4
platforms, server dry-run diff shows only zsh-antidote joining the
apt high tier, the clone lines gone, and the pre-clone path change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
verify green except the by-design manual installs; one leftover
(apt's stale micro shadows the snap) plus a general caveat for the
remaining live runs: setup migrates install methods but never
uninstalls the old copy, so check for shadowed binaries after each run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The only installer was the arch-only zsh-theme-powerlevel10k yay entry,
so Ubuntu and macOS silently fell back to the vcs_info prompt. Load
romkatv/powerlevel10k through antidote everywhere instead: plugins.txt
gains the theme, the zshrc drops the system-path sourcing and guards the
fallback on $+functions[p10k], and the packages.json entry goes away.
Instant prompt verbose→quiet — fastfetch runs during ghostty init and
would warn on every tab now that p10k actually loads.

Verified live on the Ubuntu desktop (p10k loads, dryrun-smoke green x4).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The statusLine command hardcoded /usr/bin/node, which doesn't exist on
nvm-managed machines (issue #34 task 3, deferred by PR #38 and then
lost when the PR auto-closed the issue). Resolve via command -v node
with a newest-nvm-version glob fallback for contexts where nvm isn't
on PATH. TODO gains a tracker for the other dropped #34 task (claude-hud
display config not in the repo).

Verified live: the HUD renders through ~/.nvm/.../bin/node.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ulises-c and others added 8 commits June 10, 2026 16:10
The symlink made the repo file Claude Code's writable user-settings
store, so every model switch, plugin install, and /config change landed
in the repo as permanent dirt. install.sh now copies the template
(migrating the old symlink in place; re-runs back up and reset the live
copy), and railguard install's machine-specific rewrite is undone by
redeploying the template over the live file instead of snapshotting the
repo file. settings-drift.sh diffs template vs live, normalizing key
order and ignoring the per-machine model key — which is also why the
template no longer pins advisorModel.

Migrated and verified live on the Ubuntu desktop: live file is a real
copy carrying the machine's model pin, drift check reports in sync,
dryrun-smoke green x4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
desktop_main and macos.sh carried near-identical copies of the
related-scripts menu, diverging only in which verify shim they pointed
at — and both shims exec the root verify.sh, which auto-detects the
platform anyway. Hoist one list next to print_related_scripts and point
it at root verify.sh; the per-folder shims remain valid entrypoints,
the menu just stops preferring them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…config

The deploy only renamed config.ghostty at the Application Support path,
but Ghostty also reads pre-1.2.3 plain `config` there — and that path
loads AFTER XDG with later values overriding, so a legacy Mac config
would silently defeat the deployed one. Rename both filenames at both
locations, and write the XDG target as plain `config` to match the
Linux deploy (both names are valid; uniform is better).

Found while confirming Ghostty's XDG support on macOS (it works; the
comment claiming so was already right). The Ubuntu desktop even had a
stray empty config.ghostty shadowing scenario waiting — cleaned up
locally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…se negatives

Tested end-to-end on a fresh macOS install (clean OS, no prior setup).

- platforms/macos.sh: install brew/cask packages one-by-one instead of
  all-at-once so a single failure doesn't abort the tier; collect per-package
  errors into a BREW_FAILURES array and print a summary at the end
- platforms/macos.sh: wire up p10k deploy (was Linux-desktop only)
- dotfiles/p10k.zsh.example: move p10k config here as shared cross-platform
  source (replaces linux-desktop/p10k.zsh.example)
- lib/core.sh: update Linux desktop_main p10k path to dotfiles/
- lib/verify.sh: fix ghostty check — config deploys as `config`, not `config.ghostty`
- packages.json: add macos_name=Spark Desktop for Spark Mail (App Store
  bundle name differs from display name)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace Forgejo-specific option 5 with a generic "Self-Hosted Git Server"
to accommodate additional providers (Codeberg, etc.) as new menu entries
later. Rename IS_FORGEJO→IS_SELF_HOSTED and FORGEJO_HOSTNAME→GIT_HOSTNAME;
prompt for SSH alias instead of hardcoding "forgejo".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Xcode project generator from a YAML/JSON spec; macOS-only brew formula,
placed alongside swiftlint/swiftformat.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ulises-c
ulises-c marked this pull request as ready for review June 15, 2026 05:17
@ulises-c
ulises-c merged commit 7e1b42f into main Jun 15, 2026
4 checks passed
@ulises-c
ulises-c deleted the feat/setup-unification branch June 15, 2026 05:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Setup-script unification: one root setup.sh + one packages.json

1 participant