Skip to content

Instantly share code, notes, and snippets.

@henkisdabro
Last active February 14, 2026 10:29
Show Gist options
  • Select an option

  • Save henkisdabro/ab315054c40cfca589aacf8cfd78ae28 to your computer and use it in GitHub Desktop.

Select an option

Save henkisdabro/ab315054c40cfca589aacf8cfd78ae28 to your computer and use it in GitHub Desktop.
Henrik's machine bootstrap - installs gh, op, chezmoi, claude and applies dotfiles
#!/bin/bash
# Bootstrap script for fresh macOS, WSL2, or Linux machines
# Download then run (don't pipe - needs stdin for prompts):
# curl -fsSL https://gist.githubusercontent.com/henkisdabro/ab315054c40cfca589aacf8cfd78ae28/raw/bootstrap.sh -o /tmp/bootstrap.sh
# bash /tmp/bootstrap.sh
#
# This script installs the minimum requirements to run Claude Code:
# - Homebrew (macOS), git, curl, GitHub CLI (gh), bash (macOS)
# - 1Password CLI
# - chezmoi dotfiles (applied automatically)
# - Claude Code (native installer)
# - Homebrew bash set as default shell (macOS - replaces zsh)
# - Clones lifeos repository
#
# After running this script, open a new terminal and start Claude Code.
set -e
# Colours for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Colour
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
echo ""
echo "=========================================="
echo " Bootstrap: Fresh Machine Setup"
echo "=========================================="
echo ""
# Detect platform
IS_MACOS=false
IS_WSL=false
if [ "$(uname -s)" = "Darwin" ]; then
IS_MACOS=true
log_info "Detected macOS"
elif grep -qi microsoft /proc/version 2>/dev/null; then
IS_WSL=true
log_info "Detected WSL2"
else
log_warn "Not WSL2 or macOS - continuing anyway..."
fi
if [ "$IS_MACOS" = true ]; then
# macOS: Install Homebrew first, then git and gh via brew
if command -v brew &>/dev/null; then
log_info "Homebrew already installed"
else
log_info "Installing Homebrew..."
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Source Homebrew into PATH for this session
if [ -x "/opt/homebrew/bin/brew" ]; then
eval "$(/opt/homebrew/bin/brew shellenv)"
elif [ -x "/usr/local/bin/brew" ]; then
eval "$(/usr/local/bin/brew shellenv)"
fi
fi
# Fix Homebrew permissions if installed by another user (shared Macs)
BREW_PREFIX="$(brew --prefix)"
if [ -d "$BREW_PREFIX/var/homebrew" ] && [ ! -w "$BREW_PREFIX/var/homebrew" ]; then
log_warn "Homebrew directories owned by another user - fixing permissions..."
sudo chown -R "$(whoami)" "$BREW_PREFIX"/*
log_info "Homebrew permissions fixed"
fi
# Install only what's missing (avoids compiling from source on older macOS)
BREW_NEEDED=""
command -v gh &>/dev/null || BREW_NEEDED="$BREW_NEEDED gh"
command -v bash &>/dev/null && [[ "$(bash --version)" == *"version 3"* ]] && BREW_NEEDED="$BREW_NEEDED bash"
command -v bash &>/dev/null || BREW_NEEDED="$BREW_NEEDED bash"
# git and curl: prefer system versions (Xcode CLT), only brew install if missing entirely
command -v git &>/dev/null || BREW_NEEDED="$BREW_NEEDED git"
command -v curl &>/dev/null || BREW_NEEDED="$BREW_NEEDED curl"
if [ -n "$BREW_NEEDED" ]; then
log_info "Installing via Homebrew:$BREW_NEEDED"
brew install $BREW_NEEDED
else
log_info "All required tools already installed"
fi
else
# Linux/WSL2: Use apt
log_info "Updating system packages..."
sudo apt update && sudo apt upgrade -y
log_info "Installing git and curl..."
sudo apt install -y git curl
# Install GitHub CLI
if command -v gh &>/dev/null; then
log_info "GitHub CLI already installed: $(gh --version | head -1)"
else
log_info "Installing GitHub CLI..."
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt update && sudo apt install -y gh
fi
fi
# Install 1Password CLI
if command -v op &>/dev/null; then
log_info "1Password CLI already installed: $(op --version)"
else
log_info "Installing 1Password CLI..."
if [ "$IS_MACOS" = true ]; then
brew install --cask 1password-cli
else
curl -sS https://downloads.1password.com/linux/keys/1password.asc | \
sudo gpg --dearmor --yes --output /usr/share/keyrings/1password-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/1password-archive-keyring.gpg] https://downloads.1password.com/linux/debian/$(dpkg --print-architecture) stable main" | \
sudo tee /etc/apt/sources.list.d/1password-cli.list
sudo apt update && sudo apt install -y 1password-cli
fi
fi
# Authenticate with GitHub (if not already)
if gh auth status &>/dev/null; then
log_info "Already authenticated with GitHub"
else
echo ""
log_info "GitHub authentication required"
log_info "This will open a browser for OAuth login - no password or token needed."
echo ""
gh auth login -p https -h github.com -w
fi
# Note: git credential helper is configured by chezmoi's .gitconfig template
# No need to run 'gh auth setup-git' here - it would conflict with chezmoi
# Set up 1Password Service Account token for headless servers
if [ "$IS_MACOS" = false ] && [ "$IS_WSL" = false ]; then
if [ ! -f "$HOME/.config/op/service-account-token" ]; then
echo ""
log_info "This appears to be a headless server."
log_info "Enter 1Password Service Account token (from chezmoi-homelab):"
log_info "(Press Enter to skip if not using Service Account mode)"
read -s OP_TOKEN < /dev/tty
if [ -n "$OP_TOKEN" ]; then
mkdir -p "$HOME/.config/op"
echo "$OP_TOKEN" > "$HOME/.config/op/service-account-token"
chmod 600 "$HOME/.config/op/service-account-token"
export OP_SERVICE_ACCOUNT_TOKEN="$OP_TOKEN"
log_info "Service Account token saved"
else
log_warn "Skipped - 1Password templates will not work without a token"
fi
else
export OP_SERVICE_ACCOUNT_TOKEN="$(cat "$HOME/.config/op/service-account-token")"
log_info "1Password Service Account token loaded from existing config"
fi
fi
# Set Homebrew bash as default shell on macOS
if [ "$IS_MACOS" = true ]; then
BREW_PREFIX="$([[ "$(uname -m)" == "arm64" ]] && echo /opt/homebrew || echo /usr/local)"
BREW_BASH="$BREW_PREFIX/bin/bash"
if [ -x "$BREW_BASH" ]; then
if ! grep -qxF "$BREW_BASH" /etc/shells 2>/dev/null; then
log_info "Adding Homebrew bash to /etc/shells..."
sudo sh -c "echo '$BREW_BASH' >> /etc/shells"
fi
CURRENT_SHELL="$(dscl . -read /Users/"$(whoami)" UserShell 2>/dev/null | awk '{print $2}')"
if [ "$CURRENT_SHELL" != "$BREW_BASH" ]; then
log_info "Setting Homebrew bash as default shell..."
chsh -s "$BREW_BASH"
log_info "Default shell changed to $BREW_BASH (restart terminal to use)"
else
log_info "Homebrew bash is already the default shell"
fi
fi
fi
# Ensure ~/.local/bin is in PATH for this session (chezmoi and claude install there)
export PATH="$HOME/.local/bin:$PATH"
# Install Claude Code (native installer)
if [ -x "$HOME/.local/bin/claude" ]; then
log_info "Claude Code already installed: $(claude --version 2>/dev/null || echo 'version unknown')"
else
log_info "Installing Claude Code (native installer)..."
curl -fsSL https://claude.ai/install.sh | bash
fi
# Install and apply chezmoi dotfiles
if command -v chezmoi &>/dev/null; then
log_info "chezmoi already installed: $(chezmoi --version 2>/dev/null)"
else
if [ "$IS_MACOS" = true ]; then
log_info "Installing chezmoi via Homebrew..."
brew install chezmoi
else
log_info "Installing chezmoi..."
sh -c "$(curl -fsLS get.chezmoi.io)" -- -b "$HOME/.local/bin"
fi
fi
if command -v chezmoi &>/dev/null; then
log_info "Applying dotfiles via chezmoi..."
chezmoi init --apply --force henkisdabro
else
log_warn "chezmoi not found in PATH - skipping dotfiles"
fi
# Clone lifeos repository
LIFEOS_DIR="$HOME/claudecode/lifeos"
if [ -d "$LIFEOS_DIR" ]; then
log_info "lifeos already exists at $LIFEOS_DIR"
else
log_info "Cloning lifeos repository..."
mkdir -p "$HOME/claudecode"
gh repo clone henkisdabro/lifeos "$LIFEOS_DIR"
fi
echo ""
echo "=========================================="
echo " Bootstrap Complete!"
echo "=========================================="
echo ""
echo "NEXT STEPS (in order):"
echo ""
if [ "$IS_MACOS" = true ]; then
echo " 1. Open a NEW terminal window (bash is now your default shell)"
echo ""
echo " 2. Set up 1Password (required for SSH and secrets):"
echo " - Open 1Password desktop app and sign in"
echo " - Go to Settings > Developer:"
echo " * Enable 'Use the SSH agent'"
echo " * Enable 'Integrate with 1Password CLI'"
echo " - Go to Settings > Developer > SSH Agent:"
echo " * Add your SSH key for Git signing"
echo ""
echo " 3. Copy secrets from 1Password to lifeos:"
echo " - Find '${YELLOW}lifeos - .env file and settings${NC}' in 1Password"
echo " - Save the .env file to: ~/claudecode/lifeos/.env"
echo " - Save .gcloud_claude_creds.json to: ~/claudecode/lifeos/.gcloud_claude_creds.json"
echo " (These are required for MCP servers to work)"
echo ""
echo " 4. Start Claude Code:"
echo " cd ~/claudecode/lifeos && claude"
echo ""
echo " For the full macOS development environment (nvm, bun, uv, rustup, plugins):"
echo " bash ~/.local/share/chezmoi/scripts/mac-setup.sh"
else
echo " 1. Restart your shell to pick up PATH changes:"
echo " exec bash"
echo ""
echo " 2. Set up 1Password (required for SSH and secrets):"
echo " - Open 1Password desktop app on Windows and sign in"
echo " - Go to Settings > Developer:"
echo " * Enable 'Use the SSH agent'"
echo " * Enable 'Integrate with 1Password CLI'"
echo " - Go to Settings > Developer > SSH Agent:"
echo " * Add your SSH key for Git signing"
echo " - Verify SSH works: ssh -T git@github.com"
echo ""
echo " 3. Copy secrets from 1Password to lifeos:"
echo " - Find '${YELLOW}lifeos - .env file and settings${NC}' in 1Password"
echo " - Save the .env file to: ~/claudecode/lifeos/.env"
echo " - Save .gcloud_claude_creds.json to: ~/claudecode/lifeos/.gcloud_claude_creds.json"
echo " (These are required for MCP servers to work)"
echo ""
echo " 4. Start Claude Code:"
echo " cd ~/claudecode/lifeos && claude"
echo ""
echo " Alternatively, run the full WSL2 setup script:"
echo " bash ~/.local/share/chezmoi/scripts/wsl2-setup.sh"
fi
echo ""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment