Skip to content

Latest commit

 

History

History
348 lines (273 loc) · 13.9 KB

File metadata and controls

348 lines (273 loc) · 13.9 KB

First-Mile Bootstrap

bootstrap.sh is Base's preferred entry point for a new or uncertain macOS machine. It handles the minimum prerequisites needed before basectl can take over: Homebrew, Git, Bash 4.2+, and either a source checkout or Homebrew installation of Base.

The supported macOS floor is macOS 14 Sonoma. Before either first-mile script checks or changes Homebrew, Git, Bash, or Base state, it reads the macOS version and stops loudly on older releases. For bootstrap.sh, this check also applies to --ensure-bash and --dry-run, so a successful prerequisite probe cannot be mistaken for a supported Base installation. Older macOS versions may work from a manually prepared source checkout, but they are outside Base's tested support contract and cannot use either first-mile installer.

On Ubuntu/Debian Linux, bootstrap.sh stays conservative: it does not run sudo apt from a piped script. Instead, it detects the platform and prints the manual source-checkout commands, including apt prerequisites and the basectl setup --yes handoff for unattended paste-and-run flows.

When only the supported Bash prerequisite is missing, use the focused --ensure-bash path instead of the full install bootstrap. It verifies Bash 4.2+ and installs only the platform Bash package when needed.

Quick Start

Run the bootstrapper from GitHub:

curl -fsSL https://raw.githubusercontent.com/basefoundry/base/HEAD/bootstrap.sh | bash

For a verified first run, pin reviewed Homebrew installer content before executing the bootstrapper:

BASE_BOOTSTRAP_HOMEBREW_INSTALLER_URL=file:///path/to/homebrew-install.sh \
BASE_BOOTSTRAP_HOMEBREW_INSTALLER_SHA256=<sha256> \
curl -fsSL https://raw.githubusercontent.com/basefoundry/base/HEAD/bootstrap.sh | bash

Use BASE_HOMEBREW_INSTALLER_URL and BASE_HOMEBREW_INSTALLER_SHA256 instead when the same pin should apply to all Base Homebrew entry points.

Both bootstrap.sh and install.sh keep the mutable Homebrew installer default and disclose that choice at runtime. Base does not hard-code a Homebrew URL and checksum because Homebrew maintains its official installer independently at HEAD: a fixed digest can become stale as Homebrew changes it, forcing Base releases for upstream installer updates or causing first-mile installs to fail or lag. This is a deliberate availability and trust trade-off, not an integrity guarantee. Managed environments that require verification should provide both the installer URL and SHA-256 pair shown above; partial pinning fails closed.

On macOS, the bootstrapper verifies macOS, installs missing first-mile prerequisites, and then prints the exact commands needed to finish setup. For the default source checkout path, the handoff usually looks like:

~/work/base/bin/basectl setup
~/work/base/bin/basectl update-profile
exec "$SHELL" -l

bootstrap.sh does not edit shell startup files automatically. Shell profile integration remains an explicit basectl update-profile step so the user can see what was installed before Base changes future interactive shells.

Inherited Or Migrated macOS Accounts

An account restored from Time Machine, migrated from another Mac, or shared with a previous owner can retain a stale shell profile and Homebrew state. On Apple Silicon, the most common failure is an Intel Homebrew under /usr/local being selected by a Rosetta-translated shell even though native Homebrew is installed under /opt/homebrew. Homebrew's Ruby traceback may appear before Base is mentioned, but the underlying problem is usually the process architecture, prefix selection, Xcode license, or prefix ownership.

Run this read-only diagnostic as the target user before retrying an install. It inspects each known Homebrew path explicitly before selecting the compatible prefix for the remainder of the current shell:

machine="$(uname -m)"
translated="$(sysctl -in sysctl.proc_translated 2>/dev/null || printf '0')"
printf 'machine=%s\n' "$machine"
printf 'translated=%s\n' "$translated"
printf 'shell=%s\n' "${SHELL:-unknown}"
printf 'path=%s\n' "$PATH"

for candidate in /opt/homebrew/bin/brew /usr/local/bin/brew; do
  if [ -x "$candidate" ]; then
    printf 'brew=%s\n' "$candidate"
    file "$candidate"
    "$candidate" --prefix 2>&1 || true
  fi
done

if [ "$machine" = "arm64" ] && [ "$translated" = "0" ]; then
  export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
elif [ "$machine" = "x86_64" ] && [ "$translated" = "0" ]; then
  export PATH="/usr/local/bin:/opt/homebrew/bin:$PATH"
fi

if command -v brew >/dev/null 2>&1; then
  printf 'selected-brew=%s\n' "$(command -v brew)"
  brew --config 2>&1 || true
else
  printf 'selected-brew=unresolved; stop before ownership checks\n'
fi

On Apple Silicon, a native terminal should report machine=arm64 and translated=0; the diagnostic puts /opt/homebrew/bin first for that shell. If the process is Rosetta-translated (machine=x86_64 and translated=1), open a native terminal before continuing. On an Intel Mac, /usr/local/bin/brew is the expected prefix. Do not select a prefix only because it appears first on PATH; check the architecture and the explicit candidate --prefix results together.

Check the developer-tool boundary separately:

/usr/bin/xcrun --find clang
/usr/bin/xcode-select --print-path

If either command reports that the Command Line Tools or Xcode license is missing, complete Apple's interactive installation or license-acceptance flow as the target user and rerun the read-only checks. Do not hide that prompt in a non-interactive bootstrap pipeline.

After selecting an architecture-compatible brew, inspect prefix ownership and the lock directory before brew install:

if command -v brew >/dev/null 2>&1; then
  brew_prefix="$(brew --prefix 2>/dev/null || true)"
  if [ -n "$brew_prefix" ]; then
    ls -ld "$brew_prefix" "$brew_prefix/var" "$brew_prefix/var/homebrew" \
      "$brew_prefix/var/homebrew/locks" 2>/dev/null || true
    test -w "$brew_prefix" && printf 'prefix-writable=yes\n' || printf 'prefix-writable=no\n'
    test -w "$brew_prefix/var/homebrew/locks" && \
      printf 'locks-writable=yes\n' || printf 'locks-writable=no\n'
  else
    printf 'brew-prefix=unresolved; stop before ownership checks\n'
  fi
else
  printf 'brew=unresolved; stop before ownership checks\n'
fi

The prefix and its lock directory must be owned and writable for the account that is running Base. If another macOS user owns them, stop and ask the owner or your device administrator to repair the Homebrew installation. Base must not automatically run sudo chown, recursively change ownership, delete lock directories, or reset Homebrew state. A source-checkout install is the safer alternative when the shared Homebrew prefix cannot be repaired or is not owned by the target account; use the source checkout install recipe instead.

Once the checks agree, follow the canonical Homebrew install recipe. The diagnostic has already placed the architecture-compatible prefix first on PATH for the current native shell. If the architecture, prefix, ownership, or Xcode checks do not agree, use the source checkout path or stop with the collected read-only output; do not continue to the Homebrew install merely to obtain a longer downstream traceback.

If basectl reports that the current Bash is too old, repair just that first:

curl -fsSL https://raw.githubusercontent.com/basefoundry/base/HEAD/bootstrap.sh | bash -s -- --ensure-bash --dry-run
curl -fsSL https://raw.githubusercontent.com/basefoundry/base/HEAD/bootstrap.sh | bash -s -- --ensure-bash --yes

On macOS this path uses Homebrew Bash. On Ubuntu/Debian it previews and then runs only sudo apt-get update and sudo apt-get install -y bash.

On Ubuntu/Debian, inspect the manual path first:

curl -fsSL https://raw.githubusercontent.com/basefoundry/base/HEAD/bootstrap.sh | bash -s -- --source --dry-run

The output includes sudo apt-get update, the supported apt prerequisite list, the sibling base-bash-libs clone, and the source checkout setup --dry-run, setup --yes, and update-profile commands. Interactive users can run plain setup after reviewing setup --dry-run; Ubuntu/Debian setup prompts before apt, keyring, repository, or remote-installer changes, while non-interactive runs use --yes.

Install Mode

Choose a mode explicitly when the default should not infer one:

curl -fsSL https://raw.githubusercontent.com/basefoundry/base/HEAD/bootstrap.sh | bash -s -- --source
curl -fsSL https://raw.githubusercontent.com/basefoundry/base/HEAD/bootstrap.sh | bash -s -- --brew

Without an explicit mode, bootstrap uses this order:

  1. BASE_BOOTSTRAP_MODE
  2. an existing Homebrew-installed Base formula
  3. an existing source checkout
  4. source mode at ~/work/base

This keeps an existing Homebrew install from being silently displaced by a source checkout. Homebrew and source installs can coexist; the active basectl is whichever executable the shell finds first on PATH.

Common Options

bootstrap.sh --source
bootstrap.sh --brew
bootstrap.sh --install-dir ~/work/base
bootstrap.sh --repo-url https://github.com/basefoundry/base.git
bootstrap.sh --branch <name>
bootstrap.sh --no-homebrew-install
bootstrap.sh --ensure-bash
bootstrap.sh --dry-run
bootstrap.sh --yes

Use --dry-run to inspect the planned prerequisite installs and Base install route without changing the machine. On Ubuntu/Debian, the bootstrapper always prints manual commands rather than mutating apt state itself, except for the focused --ensure-bash --yes path that installs only Bash.

Contributor Path

Contributors should first complete the source checkout install recipe, then add the sibling library checkout and contributor profile:

git clone https://github.com/basefoundry/base-bash-libs.git ~/work/base-bash-libs
~/work/base/bin/basectl setup --profile dev

The sibling base-bash-libs checkout gives the source-tree BATS suite the reusable Bash libraries it validates against. If that checkout already exists, update it before running the full contributor test contract. The dev profile installs contributor prerequisites such as BATS, ShellCheck, and GitHub CLI. On Ubuntu/Debian, GitHub CLI is installed through GitHub CLI's official Debian/Ubuntu apt repository/keyring, while authentication remains user-owned. After that, use basectl test base for the dogfood test contract.

Named profiles compose when a contributor also wants site-reliability tools:

~/work/base/bin/basectl setup --profile dev,sre

The sre profile installs local diagnostic tools only. It does not configure cloud accounts, kube contexts, credentials, or production access.

AI coding tools stay behind an explicit opt-in profile:

~/work/base/bin/basectl setup --profile ai

The ai profile installs Codex CLI and Claude Code with their official installers. Base checks tool availability and version output, but it does not configure accounts, credentials, model access, or organization policy. See Remote Installer Policy for the allowed URLs, dry-run behavior, non-interactive behavior, and managed-device guidance.

Direct Install Recipes

These are the canonical direct-install command sequences. Other Base documentation should link here rather than repeat them.

Homebrew Install Recipe

Use this path when Homebrew is already installed and Base should be managed like a normal formula:

brew trust basefoundry/base
brew install basefoundry/base/base
basectl setup
basectl update-profile
exec "$SHELL" -l

Stable Source Install Recipe

Use this path when you want the published release without Homebrew. Keep both the installer script and the cloned checkout on the same immutable release ref:

git clone --branch v1.9.0 https://github.com/basefoundry/base.git ~/work/base
~/work/base/bin/basectl setup
~/work/base/bin/basectl update-profile
exec "$SHELL" -l

The stable checkout reports basectl 1.9.0. For future releases, replace the release version and tag with the intended published release.

Source Checkout Install Recipe

Use this path when contributing to Base or intentionally dogfooding mutable development code. Naming main makes the moving source identity explicit:

git clone --branch main https://github.com/basefoundry/base.git ~/work/base
~/work/base/bin/basectl setup
~/work/base/bin/basectl update-profile
exec "$SHELL" -l

During the 1.10.0 development line, this checkout reports an identity such as basectl 1.10.0-dev+g<short-sha>. A dirty checkout appends .dirty.

Relationship To Other Install Paths

Use bootstrap.sh when the machine may not have Homebrew, Git, or a supported Bash yet. Homebrew bootstrap follows the remote installer trust model described in Remote Installer Policy.

For a direct Homebrew install, use the canonical Homebrew install recipe.

Use install.sh when you specifically want the source-install script to clone or update Base and run setup/profile commands in one path. Pin the installer script and checkout together for a stable install:

curl -fsSL https://raw.githubusercontent.com/basefoundry/base/v1.9.0/install.sh \
  | bash -s -- --branch v1.9.0

Use the HEAD installer only for an intentional contributor or dogfood checkout, and pass --branch main or another explicit ref. bootstrap.sh is the more complete first-mile path for blank machines.

Boundaries

bootstrap.sh is intentionally small. It does not configure project repositories, install project dependencies, manage IDE settings, or update shell startup files. Those steps belong to basectl setup, basectl repo, basectl update-profile, and the project manifest workflow. When a repository already exists and the Base baseline should go through review, include --issue <number> with basectl repo init ... --pr after bootstrap and setup are complete.