Last active
February 14, 2026 10:29
-
-
Save henkisdabro/ab315054c40cfca589aacf8cfd78ae28 to your computer and use it in GitHub Desktop.
Henrik's machine bootstrap - installs gh, op, chezmoi, claude and applies dotfiles
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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