87 Commits
Author SHA1 Message Date
Jason Ross c2ca21b444 Merge pull request #13 from JMR-dev/claude/pip-decimal-import-error-eVPBv
fix: detect and repair broken _decimal C extension before pip install
2026-05-22 14:21:35 -05:00
Claude 26bd80c97d fix: detect and repair broken _decimal C extension before pip install
When python3-full is not installed on Debian/Ubuntu (common with Python
3.13), the _decimal C extension fails to import, crashing any pip
invocation with RuntimeError.  Add _python3_decimal_ok() to probe for
this, and _fix_python3_decimal() to install python3-full (apt) or
python3-libs (dnf).  _install_pip() now calls these before attempting
any ensurepip/pip operations so the error is fixed automatically rather
than producing an unrecoverable crash.

https://claude.ai/code/session_01Lrg3UV7AJN9KRXyTTWGgZi
2026-05-22 19:20:51 +00:00
Jason Ross b3ad702da0 Merge pull request #12 from JMR-dev/claude/bootstrap-infinite-loop-arm64-VU64t
fix: cap pip detection subprocess with a 10s timeout
2026-05-22 14:14:53 -05:00
Claude 8a0e3ee9e4 fix: add timeouts to every subprocess invocation
`run()` and `shell()` now default to a 30-minute cap (generous enough for
heavy apt/brew installs and large downloads, bounded enough to catch a
true hang). Callers can override per-call.

All remaining direct subprocess.run sites have explicit timeouts sized to
the work they do: short caps for read-only probes (rpm, dpkg, brew list,
flatpak info, gh auth status, xcode-select -p, sysctl, VBoxManage), a
60-minute cap for the pyenv Python compile, a 15-minute cap for the
interactive `gh auth login` browser flow, and ConnectTimeout+30s for the
VM SSH helper. A small `_probe()` wrapper centralizes the probe pattern.

On timeout the bootstrap now warns and either fails the check gracefully
or returns rc=124, rather than blocking indefinitely.

https://claude.ai/code/session_01447pnRM4ogyxs5tVCUNDZW
2026-05-22 19:11:04 +00:00
Claude 4525cbc219 fix: cap pip detection subprocess with a 10s timeout
A hung `python3 -m pip --version` inside `_pip_installed()` could block
the bootstrap indefinitely, since `subprocess.run` was called without a
timeout. Treat a timeout (or a missing python3) as "pip not installed"
so the bootstrap proceeds to install it rather than hanging.

https://claude.ai/code/session_01447pnRM4ogyxs5tVCUNDZW
2026-05-22 19:05:28 +00:00
Jason Ross 1c011f4a7d Merge pull request #11 from JMR-dev/claude/script-idempotency-review-CBYwG
Make _clone_nvim_config idempotent
2026-05-22 13:58:29 -05:00
Claude a4b855b796 Rename existing nvim config to nvim-N instead of skipping
On re-runs, ~/.config/nvim is moved aside to ~/.config/nvim-1
(or nvim-2, nvim-3, ... — first free number) and a fresh clone is
created. End-of-run notices report the backup path so it's visible
after the rest of the bootstrap output scrolls by.
2026-05-22 18:55:44 +00:00
Claude cedaf4ce04 Make _clone_nvim_config idempotent
Previously rmtree'd ~/.config/nvim on every full bootstrap run, destroying
any user edits, plugin state, and undo history. Now skips when the
directory is already a clone of the expected repo, and refuses to touch
unfamiliar contents (different remote, or non-git directory).
2026-05-22 18:51:30 +00:00
Jason Ross 92a582efa7 Merge pull request #10 from JMR-dev/claude/neovim-detection-debian-arm64-FqnpH
Fix Neovim not found after install by symlinking /usr/local/bin/nvim
2026-05-22 13:47:09 -05:00
Claude 10963c848b Fix Neovim not found after install by symlinking /usr/local/bin/nvim
The PATH line written to /etc/profile.d/neovim.sh was only sourced by
login shells via /etc/profile. zsh — set as the default shell by this
script — uses /etc/zsh/zprofile which sources /etc/profile only for
login shells, and terminal emulators typically launch non-login
interactive shells. The result on Debian 13 ARM64 (and other distros):
nvim installed correctly to /opt/nvim-linux-arm64 but was not on PATH
in a normal terminal session.

Symlink the binary into /usr/local/bin/nvim instead — that directory
is always on the default PATH on Linux and macOS (Apple Silicon and
Intel) regardless of shell type. The Zig install already follows this
pattern. Also switch the install-detection probe to the symlink so it
verifies an actually-callable nvim, not just the extracted directory.

https://claude.ai/code/session_01VP9hCLH1c5F5iv468jSkqs
2026-05-22 18:27:10 +00:00
Jason Ross e5448b71ba Merge pull request #9 from JMR-dev/claude/pulumi-repo-setup-failure-KEncj
Fix Pulumi repo setup failure by installing from official tarball
2026-05-22 13:14:27 -05:00
Claude 6661aba3d6 Fix Pulumi repo setup failure by installing from official tarball
Pulumi does not publish apt or yum repositories — the URLs the setup
function referenced (api.pulumi.com/releases/sdk/{apt,rpm}-keyring.gpg
and {apt,yum}.releases.pulumi.com) return 404 / do not resolve, which
broke `bootstrap_environment.py` on every fresh Linux run.

Replace `setup_pulumi_repo` with a special-package installer that
downloads the official GitHub release tarball, verifies the SHA256
against the published checksums file, extracts to /opt/pulumi, and
exposes the binaries via PATH — the same pattern used for Neovim.
macOS continues to install Pulumi via brew.

https://claude.ai/code/session_018L1gFoc8L3CYqLE2wNjpy6
2026-05-22 18:13:43 +00:00
Jason Ross 16f058f83c Merge pull request #8 from JMR-dev/claude/debian-arm64-install-errors-ekxEt
Fix five Debian arm64 install failures
2026-05-22 13:05:53 -05:00
Claude da80fe81bc Fix five Debian arm64 install failures
- dotnet-sdk-10.0: add setup_dotnet_repo() to register the Microsoft apt
  feed (packages.microsoft.com) before attempting the install
- lua: map to lua5.4 in apt-get overrides (Debian has no unversioned lua pkg)
- pulumi: add setup_pulumi_repo() to register apt.releases.pulumi.com before
  attempting the install; add dnf/yum variant too
- qemu: map to qemu-system in apt-get overrides (Debian meta-package name)
- python3 -m ensurepip: Debian intentionally strips ensurepip from the system
  Python package; fall back to apt-get install python3-pip automatically

Also add libnsl2 → libnsl-dev mapping for Debian (bonus pyenv build fix).

https://claude.ai/code/session_017KuwqSq7nCp2iWiTeaNivn
2026-05-22 18:05:05 +00:00
Jason Ross 2e9cbb0cab Merge pull request #7 from JMR-dev/claude/headless-default-gui-flag-KCpH8
Default to headless install; add --gui flag; add lazygit and pulumi
2026-05-22 12:57:26 -05:00
Claude 18182aee58 Default to headless install; add --gui flag; add lazygit and pulumi
- Inverts the GUI opt-out model: GUI packages and Flatpak are now skipped
  by default (headless-friendly), and --gui opts in to installing them.
  Removes --no-gui entirely.
- Adds lazygit and pulumi to the default system package list.

https://claude.ai/code/session_01CsTAu2SNhE5ZAQm3RnVwp3
2026-05-22 17:56:27 +00:00
Jason Ross 5b184d81ff Merge pull request #6 from JMR-dev/claude/custom-packages-checksum-yf1cS
Fix custom package checksum verification for Mac and Linux arm64
2026-05-22 12:52:08 -05:00
Claude 8668c12f7d Fix custom package checksum verification for Mac and Linux arm64
Three root causes were causing failures on non-x86_64-Linux platforms:

1. Single SHA256 per package: the pinned fallback checksum in
   formatted_packages.py was a single value computed for linux-x86_64 only.
   Downloads on linux-aarch64, macos-x86_64, and macos-aarch64 produced
   different binaries with different digests, so verification always failed
   on those platforms when the fetch_latest resolver was unavailable.

   Fix: replace the single `sha256` field with a `sha256_map` dict keyed by
   "{os}-{arch}" (e.g. "linux-aarch64", "macos-arm64"). Added the correct
   pinned digests for all four supported platforms for both Go 1.26.3 and
   Firecracker 1.15.1, fetched from official sources.

2. Case-sensitive SHA256 comparison: _sha256_of() always returns lowercase
   hex, but checksums returned by external APIs could be uppercase.
   _verify() compared them without normalising case, causing false mismatches.

   Fix: new resolved_sha256 property always returns a lowercased digest;
   _resolve_latest() also lowercases the dynamically-fetched sha before
   storing it.

3. Firecracker not skipped on macOS: _resolve_latest_firecracker() returns
   None on macOS (firecracker is Linux-only), causing a fallback to the
   pinned linux binary URL with a linux-only checksum map entry. The download
   and verification would both fail misleadingly.

   Fix: explicit early-continue guard in install_custom_packages() when
   name == "firecracker" and IS_MACOS.
2026-05-22 17:49:58 +00:00
Jason Ross 539833344b Merge pull request #5 from JMR-dev/claude/add-python-build-deps-u1Gg5
Add missing Python build deps and fix apt-get name overrides
2026-05-18 16:44:49 -05:00
Claude 01bb797433 Add missing Python build deps and fix apt-get name overrides
Adds curl, ncurses-devel, xz (tool), libxml2-devel, and xmlsec1-devel to
SYSTEM_PACKAGES to cover the full pyenv/CPython build dependency set.

Also adds apt-get overrides for all Fedora-named Python build packages
(bzip2-devel → libbz2-dev, openssl-devel → libssl-dev, etc.) so they
resolve to the correct Debian/Ubuntu package names at install time.
Adds corresponding brew overrides for the new packages.

https://claude.ai/code/session_01P3CkL5oXzkTvayityg3uaH
2026-05-18 21:32:30 +00:00
Jason Ross caa010091f Merge pull request #4 from JMR-dev/claude/fix-package-install-errors-A2rBu
Handle OSError from subprocess when filesystem I/O fails
2026-05-18 12:28:39 -05:00
Claude 112b0005e4 Handle OSError from subprocess when filesystem I/O fails
When the system has disk I/O errors (errno 5), subprocess.run raises
OSError before a process can even start. The run() and shell() helpers
now catch OSError: if check=False the error is logged as a warning and
a returncode=1 CompletedProcess is returned so callers like
install_system_packages can continue; if check=True the error is
re-raised as before.

https://claude.ai/code/session_01Gsfw2QLhQiFVEvZQC5nVcU
2026-05-18 17:26:52 +00:00
Jason Ross 21a635de8e Merge pull request #3 from JMR-dev/claude/fix-debian-arm64-install-Mmx95
Fix Docker repo setup for Debian ARM64
2026-05-18 12:06:17 -05:00
Claude aa39c2f493 Fix README usage command
- curl and unzip were pipe-chained, but -o writes to a file so the pipe
  produced nothing; split into separate steps with &&
- Extracted directory is bootstrap_dev_env-main, not bootstrap
- Script is bootstrap_environment.py, not bootstrap_dev_env

https://claude.ai/code/session_01CkJR1eHoBVMyNNeGdbRBYK
2026-05-18 17:05:35 +00:00
Claude e230da3d28 Fix Docker repo setup for Debian ARM64
Two issues caused failures on Debian 13 (Trixie) ARM64:

1. apt-get update was never called before installing gnupg, leaving the
   package cache stale. The cached version (gnupg2 2.4.7-21+b3) was no
   longer available on the ARM64 mirror, producing 404 errors. Adding
   apt-get update before the install fixes this.

2. The Docker GPG key and apt repo URL were hardcoded to the Ubuntu
   endpoint. Debian has its own Docker repo at
   https://download.docker.com/linux/debian. The distro ID is now read
   from /etc/os-release and used to select the correct URL.

https://claude.ai/code/session_01CkJR1eHoBVMyNNeGdbRBYK
2026-05-18 16:50:10 +00:00
Jason Ross b463636192 Create README.md
Usage instructions
2026-05-18 11:29:20 -05:00
Jason Ross 6bec83b5e9 Merge pull request #2 from JMR-dev/claude/add-macos-support-s0p5z
Add macOS support with Homebrew + Xcode CLT bootstrap
2026-05-18 10:56:33 -05:00
Claude b4b4fde78d macOS firecracker VM: backend per host (nested-virt aware)
Pick a hypervisor based on the host's actual nested-virt capability
instead of always using QEMU/HVF (which doesn't expose nested KVM):

  * Apple Silicon M3+ on macOS 15 Sequoia+: QEMU/HVF with
    -cpu host,el2=on so the Linux guest's KVM (and therefore
    firecracker microVMs) actually works.
  * Intel Mac: VirtualBox with --nested-hw-virt on. Cask is
    installed on demand; the seed ISO and SSH key flow are shared
    with the QEMU path.
  * Apple Silicon M1/M2 (any macOS), or M3+ pre-Sequoia: skip the
    whole VM step and print a notice pointing at a Linux cloud VM
    as the only path to firecracker on that hardware.

Backend selection is centralized in _select_vm_backend(); the rest
of setup_firecracker_vm (image download/verify, cloud-init seed,
SSH wait, in-VM firecracker probe, zsh wrapper) is shared.

https://claude.ai/code/session_01R2CnCeEBYT5QoHiwxczDNq
2026-05-18 15:25:42 +00:00
Claude 1ffece623e macOS: provision Fedora-on-QEMU VM and firecracker() zsh bridge
After the normal package install on macOS, fetch the latest Fedora
cloud qcow2 (auto-discovered from dl.fedoraproject.org), verify its
SHA256, build a cloud-init seed ISO with hdiutil, boot it under
QEMU/HVF (UEFI on aarch64, q35 on x86_64), and wait for cloud-init
to install firecracker inside the guest.

Then write a `firecracker()` function block into ~/.zshrc that
starts the backing VM on demand and proxies invocations via SSH.
The host's own firecracker custom-package install is dropped on
macOS since it's Linux-only. Suppress the whole flow with --no-vm.

https://claude.ai/code/session_01R2CnCeEBYT5QoHiwxczDNq
2026-05-18 14:40:49 +00:00
Claude 5560d6b793 Add macOS support with Homebrew + Xcode CLT bootstrap
Detect Darwin automatically and, on macOS, install the Xcode Command
Line Tools and Homebrew as the first actions before any package work.
Route system packages through brew (formulae and casks), skip the
Flatpak section entirely, refuse to run as root (brew won't), and
adjust custom-package URL/install-path templates with new {os}, {os_go},
{os_zig}, {os_nvim} substitutions. Architecture detection remains
dynamic on both Apple Silicon and Intel.

https://claude.ai/code/session_01R2CnCeEBYT5QoHiwxczDNq
2026-05-18 14:27:01 +00:00
Jason Ross 4cf2645aaf Merge pull request #1 from JMR-dev/claude/refactor-packages-python-20kcR
Refactor package list to Python module with dynamic versions
2026-05-18 09:10:35 -05:00
Claude 8acdb30e83 Add zsh + oh-my-zsh, set default shell per distro family
- Add zsh to SYSTEM_PACKAGES
- Detect RHEL-family vs Debian-family from /etc/os-release ID/ID_LIKE
  (falls back to PKG_MGR if os-release is unreadable)
- ensure_zsh_default sets zsh as the invoking user's login shell after
  system install: usermod -s on RHEL-family (chsh under default
  authselect refuses other-user changes), chsh -s elsewhere; SUDO_USER
  is preferred so sudo invocations target the real user
- Add oh-my-zsh to CUSTOM_PACKAGES; the installer runs the official
  unattended script (requires zsh + git, which are already installed by
  this point) and rewrites ZSH_THEME to "gnzh" in ~/.zshrc
- As a final step, run 'zsh -c "source ~/.zshrc"' to validate the rc
  file (the user's interactive shell is unaffected — they need a new
  terminal to pick up the new default shell)
2026-05-18 14:05:09 +00:00
Claude a05df917f7 Add pip as a custom package via python -m ensurepip
pip is unusual among the custom packages because it ships inside CPython
itself, so it doesn't need a URL or archive. The handler bootstraps it
from the standard-library wheel via 'python3 -m ensurepip --upgrade' and
then self-upgrades to the latest version. Detection uses
'python3 -m pip --version' instead of a filesystem path.
2026-05-18 13:54:46 +00:00
Claude a4d1dfb9fb Refactor package list to Python module with dynamic versions
- Move SYSTEM_PACKAGES, FLATPAK_PACKAGES, and CUSTOM_PACKAGES into
  formatted_packages.py as typed Python data; drop the bespoke parser
- Custom package URLs are now string templates with {version}, {arch},
  and {arch_go} substitutions, so the same entry works on x86_64 and
  aarch64 without hand-editing the URL
- Add best-effort latest-version resolvers for Go, Firecracker, and Zig
  (using go.dev JSON, GitHub releases, and ziglang index.json
  respectively); on any failure the pinned version + sha256 are used
  and the fallback is logged
- Make Neovim's archive name and install dir arch-aware (x86_64/arm64)
- Document that --no-gui skips the entire Flatpak section, including
  installing flatpak itself
2026-05-18 13:44:39 +00:00
JMR-dev 418d794320 add gitignore 2026-05-17 11:21:01 -05:00
JMR-dev f1c324c139 refinements 2026-05-17 10:49:03 -05:00
JMR-dev 4d8d33c6ed initial commit 2026-05-15 22:10:21 -05:00