Compare commits

...
Author SHA1 Message Date
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
2 changed files with 82 additions and 23 deletions
+65 -21
View File
@@ -5,7 +5,7 @@ Bootstrap packages declared in formatted_packages.py.
Sections handled:
System Packages — installed via dnf, apt-get, or brew (macOS)
Flatpak Packages — installed via flatpak from Flathub (Linux only;
skipped with --no-gui and skipped entirely on macOS)
skipped by default and skipped entirely on macOS; use --gui to enable)
Custom Packages — downloaded, verified, extracted
macOS firecracker VM — provisions a Fedora cloud image under a
hypervisor that supports nested virtualization,
@@ -23,8 +23,8 @@ Xcode Command Line Tools and Homebrew, which is then used as the system
package manager.
Usage:
Linux: sudo python3 bootstrap_environment.py [--only system|flatpak|custom] [--no-gui]
macOS: python3 bootstrap_environment.py [--only system|custom] [--no-gui] [--no-vm]
Linux: sudo python3 bootstrap_environment.py [--only system|flatpak|custom] [--gui]
macOS: python3 bootstrap_environment.py [--only system|custom] [--gui] [--no-vm]
(do NOT use sudo on macOS — Homebrew refuses to run as root)
"""
@@ -47,7 +47,7 @@ import urllib.error
import urllib.request
from dataclasses import dataclass
from pathlib import Path
from typing import Optional
from typing import Optional, Union
import formatted_packages
@@ -390,6 +390,23 @@ _OVERRIDES: dict[str, dict[str, Optional[list[str]]]] = {
"apt-get": {
"ffmpeg-free": ["ffmpeg"], # Fedora-specific name
"rg": ["ripgrep"],
# Python build deps — Fedora/RHEL naming differs from Debian/Ubuntu
"bzip2-devel": ["libbz2-dev"],
"gdbm-libs": ["libgdbm-dev"],
"libffi-devel": ["libffi-dev"],
"libuuid-devel": ["uuid-dev"],
"libxml2-devel": ["libxml2-dev"],
"libzstd-devel": ["libzstd-dev"],
"ncurses-devel": ["libncursesw5-dev"],
"openssl-devel": ["libssl-dev"],
"readline-devel": ["libreadline-dev"],
"sqlite": ["sqlite3"],
"sqlite-devel": ["libsqlite3-dev"],
"tk-devel": ["tk-dev"],
"xmlsec1-devel": ["libxmlsec1-dev"],
"xz": ["xz-utils"],
"xz-devel": ["liblzma-dev"],
"zlib-devel": ["zlib1g-dev"],
},
"brew": {
# Provided by Xcode CLT or the OS — no-op on macOS.
@@ -422,15 +439,20 @@ _OVERRIDES: dict[str, dict[str, Optional[list[str]]]] = {
"webcamoid": None,
# Python build deps — macOS SDK / brew formulas already bundle headers.
"bzip2-devel": None,
"curl": None, # built-in on macOS
"gdbm-libs": ["gdbm"],
"libffi-devel": ["libffi"],
"libnsl2": None,
"libuuid-devel": None,
"libxml2-devel": ["libxml2"],
"libzstd-devel": ["zstd"],
"ncurses-devel": None, # provided by macOS SDK
"openssl-devel": ["openssl@3"],
"readline-devel": ["readline"],
"sqlite-devel": None,
"tk-devel": ["tcl-tk"],
"xmlsec1-devel": ["libxmlsec1"],
"xz": ["xz"],
"xz-devel": None,
"zlib-devel": None,
},
@@ -627,7 +649,7 @@ _SPECIAL_PKGS: set[str] = (
else {"github-desktop", "zoom", "obsidian", "minikube", "bashtop", "pipx", "poetry"}
)
# GUI apps — skipped when --no-gui is passed (headless environments).
# GUI apps — skipped by default (headless); included only when --gui is passed.
_GUI_SYSTEM_PKGS = {
"github-desktop",
"google-chrome-stable",
@@ -868,7 +890,8 @@ class CustomPackage:
name: str
version: Optional[str] = None # pinned fallback version
url_template: Optional[str] = None # uses {version}, {arch}, {arch_go}
sha256: Optional[str] = None # hex digest of the pinned archive
sha256: Optional[str] = None # single-arch hex digest (set by _resolve_latest)
sha256_map: Optional[dict] = None # per-platform pinned digests: {"os-arch": hex}
sha256_url_template: Optional[str] = None # template for a .minisig URL
minisign_key: Optional[str] = None # base64 public key for minisign verification
fetch_latest: Optional[str] = None # latest-version resolver hint
@@ -885,6 +908,22 @@ class CustomPackage:
if self.sha256_url_template else None
)
@property
def resolved_sha256(self) -> Optional[str]:
"""Return the SHA256 hex for the current OS+arch, lowercased.
sha256 (set dynamically by _resolve_latest) takes priority over sha256_map
so that a freshly fetched checksum always wins over the pinned fallback.
"""
if self.sha256:
return self.sha256.lower()
if self.sha256_map:
key = f"{OS}-{ARCH}"
val = self.sha256_map.get(key)
if val:
return val.lower()
return None
@property
def display_name(self) -> str:
return f"{self.name}-{self.version}" if self.version else self.name
@@ -952,10 +991,11 @@ def _sha256_of(path: Path) -> str:
def _verify(archive: Path, pkg: CustomPackage) -> bool:
"""Returns True if verification passed (or nothing to verify), False on failure."""
if pkg.sha256:
expected = pkg.resolved_sha256
if expected:
actual = _sha256_of(archive)
if actual != pkg.sha256:
err(f"SHA256 mismatch for {pkg.name}: expected {pkg.sha256}, got {actual}")
if actual != expected:
err(f"SHA256 mismatch for {pkg.name}: expected {expected}, got {actual}")
return False
print(" SHA256 OK")
elif pkg.sha256_url:
@@ -1447,7 +1487,7 @@ def _resolve_latest(pkg: CustomPackage) -> None:
return
print(f" Latest is {latest_version} (pinned was {pkg.version}); using latest.")
pkg.version = latest_version
pkg.sha256 = latest_sha
pkg.sha256 = latest_sha.lower()
pkg.sha256_url_template = None # prefer the freshly resolved sha256
@@ -1461,6 +1501,11 @@ def install_custom_packages(to_install: list[CustomPackage]) -> None:
if check_path is None and name_lower != "pip":
warn(f"{pkg.name}: no known install path — script will not detect future installs")
# Packages that are OS-specific
if name_lower == "firecracker" and IS_MACOS:
warn(f"{pkg.name}: Linux-only — skipping on macOS")
continue
# Handlers that manage their own download/install
if name_lower == "nvm":
_install_nvm()
@@ -2181,10 +2226,10 @@ def main() -> None:
)
ap.add_argument("--only", choices=["system", "flatpak", "custom"],
help="Install only the named section")
ap.add_argument("--no-gui", action="store_true",
help="Skip GUI applications (suitable for headless environments). "
"Excludes GUI system packages and skips the entire Flatpak "
"section, including installing flatpak itself.")
ap.add_argument("--gui", action="store_true",
help="Include GUI applications (headed environments). "
"Adds GUI system packages and enables the Flatpak "
"section. By default GUI apps and Flatpak are skipped.")
ap.add_argument("--no-vm", action="store_true",
help="macOS only: skip provisioning the Fedora-on-QEMU VM that "
"backs the firecracker() zsh wrapper.")
@@ -2200,8 +2245,8 @@ def main() -> None:
print(f"OS: {OS}")
print(f"Architecture: {ARCH}")
print(f"Package manager: {PKG_MGR}")
if args.no_gui:
print("Mode: headless (--no-gui) — skipping GUI apps and Flatpak")
if not args.gui:
print("Mode: headless (default) — skipping GUI apps and Flatpak")
if IS_MACOS:
# Refuse to run as root before doing anything (brew won't run as root).
@@ -2211,19 +2256,18 @@ def main() -> None:
print("Checking installed packages ...")
if args.no_gui:
if not args.gui:
skipped_gui = [p for p in system_pkgs if p in _GUI_SYSTEM_PKGS]
system_pkgs = [p for p in system_pkgs if p not in _GUI_SYSTEM_PKGS]
if skipped_gui:
print(f" [NO-GUI] Skipping GUI system packages: {_fmt(skipped_gui)}")
print(f" [HEADLESS] Skipping GUI system packages: {_fmt(skipped_gui)}")
flatpak_pkgs = []
# Flatpak is Linux-only — macOS has no Flatpak section regardless of flags.
# --no-gui also suppresses the Flatpak section entirely (both `flatpak`
# itself and the Flathub apps), even when --only=flatpak is requested.
# GUI apps and Flatpak are skipped by default; --gui re-enables them.
do_flatpak = (
args.only in (None, "flatpak")
and not args.no_gui
and args.gui
and not IS_MACOS
)
+17 -2
View File
@@ -36,6 +36,7 @@ SYSTEM_PACKAGES: list[str] = [
"git",
"github-desktop",
"google-chrome-stable",
"lazygit",
"lua",
"minisign",
"minikube",
@@ -43,6 +44,7 @@ SYSTEM_PACKAGES: list[str] = [
"obsidian",
"pipx",
"poetry",
"pulumi",
"podman",
"qemu",
"restic",
@@ -59,18 +61,23 @@ SYSTEM_PACKAGES: list[str] = [
"zsh",
"bzip2",
"bzip2-devel",
"curl",
"gdbm-libs",
"libffi-devel",
"libnsl2",
"libuuid-devel",
"libxml2-devel",
"libzstd-devel",
"make",
"ncurses-devel",
"openssl-devel",
"patch",
"readline-devel",
"sqlite",
"sqlite-devel",
"tk-devel",
"xmlsec1-devel",
"xz",
"xz-devel",
"zlib-devel",
]
@@ -89,7 +96,12 @@ CUSTOM_PACKAGES: list[dict] = [
"name": "go",
"version": "1.26.3",
"url_template": "https://go.dev/dl/go{version}.{os_go}-{arch_go}.tar.gz",
"sha256": "2b2cfc7148493da5e73981bffbf3353af381d5f93e789c82c79aff64962eb556",
"sha256_map": {
"linux-x86_64": "2b2cfc7148493da5e73981bffbf3353af381d5f93e789c82c79aff64962eb556",
"linux-aarch64": "9d89a3ea57d141c2b22d70083f2c8459ba3890f2d9e818e7e933b75614936565",
"macos-x86_64": "278d580b32e299fe4a9c990fcf2d02acfe538c7e551a6ee18f9c7164573d2c63",
"macos-aarch64": "875cf54a15311eee2c99b9dd67c68c4a49351d489ab622bf2cfd28c8f2078d3c",
},
"fetch_latest": "go",
},
{"name": "neovim"},
@@ -100,7 +112,10 @@ CUSTOM_PACKAGES: list[dict] = [
"https://github.com/firecracker-microvm/firecracker/releases/download/"
"v{version}/firecracker-v{version}-{arch}.tgz"
),
"sha256": "d4a32ab2322d887ca1bc4a4e7afa9cc35393e6362dfc2b3becb389d362e4275a",
"sha256_map": {
"linux-x86_64": "d4a32ab2322d887ca1bc4a4e7afa9cc35393e6362dfc2b3becb389d362e4275a",
"linux-aarch64": "00654ac1e702a22744121ea9f10a4f792ebd7c3a744cba587dfac9fcb79b41a5",
},
"fetch_latest": "firecracker",
},
{