Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions Brewfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ brew "ripgrep" # Fast grep
brew "shellcheck" # Shell script linting
brew "tmux" # Terminal multiplexer
brew "terminal-notifier" if OS.mac? # macOS notifications
brew "mas" if OS.mac? # Mac App Store CLI
brew "vim" # Text editor
brew "yazi" # Terminal file manager
brew "zellij" # Terminal workspace
Expand Down Expand Up @@ -53,18 +54,15 @@ end
# Python Libraries
brew "numpy" # IAP tunnel perf (gcloud)

# AI/ML
brew "whisper-cpp" # Local speech-to-text

# Utilities
brew "ffmpeg"
brew "scc" # Code line counter
brew "testdisk" # Data recovery

# Casks - Dev Tools
cask "codex"
cask "iterm2" # Requires: post-install config
cask "lm-studio"


# Casks - Apps
cask "google-chrome"
Expand All @@ -80,7 +78,11 @@ cask "whatsapp"
cask "font-jetbrains-mono-nerd-font"

# Casks - Networking
cask "tailscale-app" # Mesh VPN (event bus, openclaw)
cask "tailscale-app" # Mesh VPN (event bus, openclaw) — infra, not AI-gated

# Casks - Cloud
cask "gcloud-cli" # Requires: gcloud init

# Mac App Store Apps
mas "Amphetamine", id: 937984704 if OS.mac?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] mas install requires an active App Store session. On a machine not signed in, these entries cause brew bundle to exit non-zero, and since bootstrap.sh runs under set -euo pipefail with no || true on the brew bundle --file call, the whole bootstrap aborts during Phase 1 — before sync_dotfiles runs. Given the PR's robustness theme, consider tolerating bundle failure or noting the App Store sign-in prerequisite.

mas "Infuse", id: 1136220934 if OS.mac?
9 changes: 9 additions & 0 deletions Brewfile.ai
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@
# Claude Code
cask "claude"

# OpenAI Terminal Agent
cask "codex"

# Local LLM runner
cask "lm-studio"

# Local speech-to-text
brew "whisper-cpp"

# OpenClaw Agent Tools
brew "spotify_player" # Spotify CLI (OAuth device flow)
brew "gogcli" # Google Suite CLI
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ CI runs: Lint, Test, Hooks, Bootstrap, claude-review.
**Phase 1 — Pull & packages** (only with `--pull`/`-p`):
1. Pulls latest from git
2. Installs packages: Homebrew (macOS), apt (Debian/Ubuntu), or binary downloads to `~/.local/bin` (SteamOS)
3. Updates existing packages
3. Refreshes package metadata (`brew update`) and installs any missing packages via `brew bundle`. Note: on macOS it does **not** run `brew upgrade`/`brew cleanup`, so already-installed formulae are not bumped to newer versions (kept out for speed and to avoid surprise breakage — upgrade manually with `brew upgrade`).

**Phase 2 — Sync dotfiles** (always runs):
1. Sets default shell to zsh
Expand Down
125 changes: 105 additions & 20 deletions bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ cd "$(dirname "${BASH_SOURCE}")";
GATEWAY_HOST="mac-mini.tailac7b3c.ts.net"
INSTALL_AI=""

# Set up Homebrew environment variables
if [[ "$(uname)" == "Darwin" ]]; then
if [[ -f "/opt/homebrew/bin/brew" ]]; then
eval "$(/opt/homebrew/bin/brew shellenv)"
elif [[ -f "/usr/local/bin/brew" ]]; then
eval "$(/usr/local/bin/brew shellenv)"
fi
fi


# ==============================================================================
# Functions
# ==============================================================================
Expand Down Expand Up @@ -47,6 +57,14 @@ set_default_shell() {
return 0
fi

# chsh requires manual password entry on macOS unless run as root.
# Skip in non-interactive sessions to avoid hanging.
if [[ ! -t 0 ]]; then
echo "Warning: Cannot change default shell to zsh automatically in non-interactive mode."
echo " Please run manually: chsh -s $zsh_path"
return 0
fi

echo "Setting default shell to zsh..."
if chsh -s "$zsh_path" 2>/dev/null; then
echo "Default shell set to zsh"
Expand Down Expand Up @@ -368,9 +386,9 @@ install_tmux_plugin_manager() {
mkdir -p "$tpm_dir"
git clone https://github.com/tmux-plugins/tpm "$tpm_dir"

# Source tmux config if tmux is installed
if command -v tmux >/dev/null 2>&1 && [[ -e "$HOME/.tmux.conf" ]]; then
tmux source "$HOME/.tmux.conf"
# Source tmux config if tmux is installed and running
if command -v tmux >/dev/null 2>&1 && tmux info &>/dev/null && [[ -e "$HOME/.tmux.conf" ]]; then
tmux source "$HOME/.tmux.conf" || true
fi
}

Expand Down Expand Up @@ -488,9 +506,9 @@ prompt_ai_install() {
return 0
fi

# Non-interactive: default to install
# Non-interactive: default to skip
if [[ ! -t 0 ]]; then
INSTALL_AI=true
INSTALL_AI=false
return 0
fi

Expand Down Expand Up @@ -809,6 +827,14 @@ install_packages() {
if ! command -v brew >/dev/null 2>&1; then
echo "Installing Homebrew..."
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
if [[ -f "/opt/homebrew/bin/brew" ]]; then
eval "$(/opt/homebrew/bin/brew shellenv)"
elif [[ -f "/usr/local/bin/brew" ]]; then
eval "$(/usr/local/bin/brew shellenv)"
fi
else
echo "Updating Homebrew..."

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] This branch now runs only brew update (metadata); the old brew update / brew upgrade / brew cleanup block was removed from update_packages. Net effect: ./bootstrap.sh -p no longer upgrades installed Homebrew formulae or reclaims disk via cleanup (brew bundle installs missing packages but does not upgrade existing ones). That diverges from CLAUDE.md's Phase 1 'Updates existing packages.' If intentional (speed / avoid breakage), consider updating the CLAUDE.md and --help wording so the documented behavior matches; otherwise restore brew upgrade / cleanup.

brew update
fi

install_brew_packages
Expand All @@ -830,15 +856,6 @@ install_packages() {

update_packages() {
if [[ "$(uname)" == "Darwin" ]]; then
if command -v brew >/dev/null 2>&1; then
echo "Updating Homebrew and upgrading packages..."
brew update
brew upgrade
brew cleanup
else
echo "Homebrew not installed, skipping"
fi

if command -v softwareupdate >/dev/null 2>&1; then
echo "Checking for macOS software updates..."
softwareupdate --list
Expand Down Expand Up @@ -869,7 +886,7 @@ update_packages() {
# Update global npm packages
if command -v npm >/dev/null 2>&1; then
echo "Updating global npm packages..."
npm update -g
npm update -g || true
fi

# Update cargo packages
Expand All @@ -881,7 +898,7 @@ update_packages() {
fi

# Update pip packages (no safe "update all" — list managed packages explicitly)
if command -v pip3 >/dev/null 2>&1; then
if [[ "$INSTALL_AI" == true ]] && command -v pip3 >/dev/null 2>&1; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Gating the pip update on INSTALL_AI == true means it never runs on Linux: prompt_ai_install (the only setter of INSTALL_AI) is invoked only inside the macOS install_brew_packages path. On Debian/Ubuntu/SteamOS, INSTALL_AI stays empty, so piper-tts is no longer installed or upgraded there, with no way to opt in. If that is intended (AI is macOS/gateway-only), a short comment here would keep the asymmetry from being read as a bug later.

echo "Updating pip packages..."
pip3 install --upgrade piper-tts 2>/dev/null || pip3 install --upgrade --break-system-packages piper-tts 2>/dev/null || true
fi
Expand Down Expand Up @@ -985,19 +1002,48 @@ install_brew_packages() {
return 0
fi

local temp_pip_conf=false
# Check if we need to temporarily override pip.conf to bypass corporate mirror issues for gcloud-cli
if [[ "$(uname)" == "Darwin" ]] && [[ -f "/Library/Application Support/pip/pip.conf" ]]; then
if [[ ! -f "$HOME/.config/pip/pip.conf" ]]; then
echo "Temporarily configuring PyPI mirror for Homebrew Python dependencies..."
mkdir -p "$HOME/.config/pip"
echo -e "[global]\nindex-url = https://pypi.org/simple" > "$HOME/.config/pip/pip.conf"
temp_pip_conf=true
# Guarantee cleanup even if a brew bundle below aborts under set -euo
# pipefail. A leftover temp file would otherwise mask the corporate
# pip.conf on every subsequent run (the guard above skips re-creating
# it once $HOME/.config/pip/pip.conf exists).
trap 'rm -f "$HOME/.config/pip/pip.conf"' EXIT
fi
fi

echo "Installing Homebrew packages..."
# Use --adopt to take ownership of existing apps instead of erroring
HOMEBREW_CASK_OPTS="--adopt" brew bundle --file="$brewfile"
# Use --adopt to take ownership of existing apps instead of erroring.
# Tolerate a non-zero exit (e.g. a `mas` entry failing because the machine
# isn't signed into the App Store) so Phase 1 doesn't abort before
# sync_dotfiles runs — brew bundle already reports which packages failed.
HOMEBREW_CASK_OPTS="--adopt" brew bundle --file="$brewfile" \
|| echo " Warning: some Homebrew packages failed to install (continuing)."

# Conditionally install AI assistant packages (Claude, OpenClaw tools)
prompt_ai_install
if [[ "$INSTALL_AI" == true ]]; then
local ai_brewfile="$dotfiles_dir/Brewfile.ai"
if [[ -f "$ai_brewfile" ]]; then
echo "Installing AI assistant packages..."
HOMEBREW_CASK_OPTS="--adopt" brew bundle --file="$ai_brewfile"
HOMEBREW_CASK_OPTS="--adopt" brew bundle --file="$ai_brewfile" \
|| echo " Warning: some AI Homebrew packages failed to install (continuing)."
fi
fi

# Remove the temporary pip.conf on the success path and clear the
# safety-net trap (cleanup only ran via the trap on an early abort).
if [[ "$temp_pip_conf" == true ]]; then
rm -f "$HOME/.config/pip/pip.conf"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Important] The script runs under set -euo pipefail. If brew bundle at line 1018 (or the AI bundle at 1026) exits non-zero, the script aborts before reaching this cleanup, leaving the temp ~/.config/pip/pip.conf in place. Since that temp file masks the corporate /Library/Application Support/pip/pip.conf (which this code only touches when no user config exists), a failed brew bundle permanently shadows the corporate mirror config on subsequent runs too: the existence guard at line 1008 then sees the leftover file and skips recreating/cleaning it. Guarantee cleanup with a trap set right after creating the temp file (trap to rm the file on RETURN/EXIT) instead of an inline rm at the end of the function.

trap - EXIT
echo "Removed temporary PyPI mirror override."
fi
}

run_brew_hooks() {
Expand Down Expand Up @@ -1072,7 +1118,7 @@ install_yazi_flavor() {
# Install catppuccin-mocha flavor if not present
if ! $ya_cmd pkg list 2>/dev/null | grep -q "catppuccin-mocha"; then
echo "Installing yazi catppuccin-mocha flavor..."
$ya_cmd pkg add yazi-rs/flavors:catppuccin-mocha
$ya_cmd pkg add yazi-rs/flavors:catppuccin-mocha || true
fi
}

Expand Down Expand Up @@ -1143,6 +1189,42 @@ install_claude_mcp_servers() {
fi
}

configure_npm_registry() {
# Skip if ~/.npmrc already contains the registry auth configurations
if [[ -f "$HOME/.npmrc" ]] && grep -q "ah-3p-staging-npm" "$HOME/.npmrc" 2>/dev/null; then
return 0
fi

# Require gcloud and npm to be installed
if ! command -v gcloud >/dev/null 2>&1 || ! command -v npm >/dev/null 2>&1; then
return 0
fi

# Check if user has an active gcloud account
local active_account
active_account=$(gcloud config get-value account 2>/dev/null) || return 0
if [[ -z "$active_account" ]]; then
return 0
fi

echo "Configuring NPM registry credentials via gcloud..."
local settings
if settings=$(gcloud artifacts print-settings npm --project=artifact-foundry-prod --repository=ah-3p-staging-npm --location=us 2>/dev/null); then
# Strip the deprecated always-auth line. The trailing `|| true` keeps a
# fully-filtered output (grep exit 1) from aborting bootstrap under
# `set -euo pipefail`.
# Note: print-settings embeds a short-lived OAuth _authToken that is not
# refreshed here (the guard above skips re-running once ah-3p-staging-npm
# is present in ~/.npmrc). Re-run this step if npm auth starts failing.
# Prepend a newline so the first appended line can't concatenate onto an
# existing ~/.npmrc entry that lacks a trailing newline.
{ printf '\n'; echo "$settings" | { grep -v "always-auth" || true; }; } >> "$HOME/.npmrc"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Two small notes on this append: (1) the unconditional leading newline writes a blank first line into a freshly-created ~/.npmrc; guarding on a non-empty file before prepending the newline avoids the stray blank line. (2) As the comment above notes, the embedded short-lived OAuth token is not refreshed on later runs because the ah-3p-staging-npm guard skips re-execution. That is documented, so just flagging it as a known re-run footgun for when npm auth starts failing.

echo "NPM registry credentials configured in ~/.npmrc"
else
echo " Warning: Failed to configure NPM credentials automatically"
fi
}

sync_dotfiles() {
# Set default shell to zsh
set_default_shell
Expand Down Expand Up @@ -1201,6 +1283,9 @@ sync_dotfiles() {
install_launch_agents
cleanup_legacy_cron

# Configure NPM registry auth if authenticated with gcloud
configure_npm_registry

# Reload zsh configuration
if [[ -n "${ZSH_VERSION:-}" ]]; then
source ~/.zshrc 2>/dev/null || echo "Restart your terminal or run: source ~/.zshrc"
Expand Down
33 changes: 0 additions & 33 deletions brew-hooks/iterm2.sh

This file was deleted.

23 changes: 23 additions & 0 deletions brew-hooks/stats.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# Post-install hook for Stats app
# Imports Stats preferences from dotfiles

set -euo pipefail

dotfiles_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
plist_src="$dotfiles_dir/preferences/Stats.plist"

if [[ ! -f "$plist_src" ]]; then
exit 0
fi

# Only run on macOS
if [[ "$(uname)" != "Darwin" ]]; then
exit 0
fi

echo "Importing Stats preferences..."
# Close Stats app if running to ensure it picks up the new config on restart
killall Stats 2>/dev/null || true

defaults import eu.exelban.Stats "$plist_src"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] This defaults import runs on every run_brew_hooks invocation (i.e. every -p bootstrap), unconditionally overwriting eu.exelban.Stats — so any settings the user changed in the Stats GUI are silently reverted to the committed plist on the next bootstrap. If that is the intended source-of-truth behavior it is fine; otherwise consider importing only when the prefs domain is absent.

5 changes: 5 additions & 0 deletions home/.bin/toggle-btop-theme
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ else
fi

# Update btop config (macOS sed requires '' after -i; Linux would use -i alone)
if [[ -L "$BTOP_CONF" ]]; then
TARGET="$(readlink "$BTOP_CONF")"
rm -f "$BTOP_CONF"
cp "$TARGET" "$BTOP_CONF"
fi
sed -i '' "s/^color_theme = .*/color_theme = \"$THEME\"/" "$BTOP_CONF"

# Signal btop to reload config (if running)
Expand Down
9 changes: 9 additions & 0 deletions home/.exports
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@
# GEMINI_API_KEY - Required for Gemini API access
# AGENT_EVENT_BUS_URL - URL for event bus MCP server (e.g., https://host/agent-event-bus/mcp)

# Set up Homebrew PATH for macOS
if [[ "$(uname)" == "Darwin" ]]; then
if [[ -f "/opt/homebrew/bin/brew" ]]; then
eval "$(/opt/homebrew/bin/brew shellenv)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] eval of brew shellenv forks a brew subprocess on every interactive shell startup; with many panes or tabs (zellij) that latency adds up. Since bootstrap.sh already runs the same block at its top for its own session, you could skip it here or guard on an unset HOMEBREW_PREFIX so it evaluates only once per shell tree.

elif [[ -f "/usr/local/bin/brew" ]]; then
eval "$(/usr/local/bin/brew shellenv)"
fi
fi

# PATH configuration
if [ -d "$HOME/.local/bin" ]; then
export PATH="$HOME/.local/bin:$PATH"
Expand Down
3 changes: 3 additions & 0 deletions home/.ssh/config
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
Include ~/.ssh/config.local

Host speck-vm
HostName speck-vm
IdentityFile ~/.ssh/google_compute_engine


Host *
IgnoreUnknown UseKeychain
AddKeysToAgent yes
Expand Down
10 changes: 0 additions & 10 deletions preferences/iTerm Profile.json

This file was deleted.

Loading
Loading