Skip to content

Instantly share code, notes, and snippets.

@shedali
Last active November 20, 2025 14:43
Show Gist options
  • Select an option

  • Save shedali/38c78cbf3d42855a83a44f54bc1cc2ab to your computer and use it in GitHub Desktop.

Select an option

Save shedali/38c78cbf3d42855a83a44f54bc1cc2ab to your computer and use it in GitHub Desktop.
bootstrap
#!/bin/bash
# Run this on a new Mac:
# curl -fsSL https://gist.githubusercontent.com/shedali/38c78cbf3d42855a83a44f54bc1cc2ab/raw/bootstrap.sh | bash
set -e
# Install Xcode Command Line Tools if not present
if ! xcode-select -p &>/dev/null; then
echo "Installing Xcode Command Line Tools..."
xcode-select --install
echo "Please complete the Xcode Command Line Tools installation in the dialog, then re-run this script."
exit 0
fi
if ! command -v nix &>/dev/null; then
echo "Nix not found, installing..."
curl -fsSL https://install.determinate.systems/nix | sh -s -- install --determinate
echo "Nix installed, sourcing environment..."
# Source Nix to make it available in current shell
if [ -e "/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh" ]; then
. "/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh"
fi
fi
echo "Nix is available, continuing setup..."
# Function to sync or clone a git repository
sync_or_clone_repo() {
local repo=$1
local target_dir=$2
if [ -d "$target_dir/.git" ]; then
echo "Repository exists at $target_dir, syncing..."
cd "$target_dir"
# Check for unmerged paths (merge conflicts)
if ! git diff --check &>/dev/null && git ls-files -u | grep -q .; then
echo "ERROR: Repository has unmerged files (merge conflicts)"
echo "Please resolve conflicts manually in $target_dir"
cd - >/dev/null
return 1
fi
# Check for any changes (tracked, untracked, or modified)
local has_changes=false
if ! git diff-index --quiet HEAD -- 2>/dev/null || [ -n "$(git ls-files --others --exclude-standard)" ]; then
echo "Stashing local changes (including untracked files)..."
git stash push -u -m "bootstrap.sh auto-stash $(date +%Y-%m-%d_%H-%M-%S)"
has_changes=true
fi
# Sync with remote
echo "Pulling latest changes..."
git pull --rebase || {
echo "WARNING: Failed to pull changes, repository may have conflicts"
if [ "$has_changes" = true ]; then
echo "Attempting to pop stash..."
git stash pop || echo "WARNING: Could not auto-pop stash, run 'git stash pop' manually later"
fi
cd - >/dev/null
return 1
}
# Pop stash if we created one
if [ "$has_changes" = true ]; then
echo "Restoring stashed changes..."
git stash pop || echo "WARNING: Could not auto-pop stash, changes are still in stash. Run 'git stash list' to see them."
fi
cd - >/dev/null
else
echo "Cloning $repo to $target_dir..."
if [ -d "$target_dir" ]; then
echo "WARNING: Directory exists but is not a git repo, removing..."
/bin/rm -rf "$target_dir"
fi
gh repo clone "$repo" "$target_dir"
fi
}
# Use nix shell to temporarily get gh for authentication and repo management
/nix/var/nix/profiles/default/bin/nix shell nixpkgs#gh nixpkgs#git --command bash -c '
# Only authenticate if not already logged in
if ! gh auth status &>/dev/null; then
gh auth login
fi
# Define the sync_or_clone_repo function in subshell
sync_or_clone_repo() {
local repo=$1
local target_dir=$2
if [ -d "$target_dir/.git" ]; then
echo "Repository exists at $target_dir, syncing..."
cd "$target_dir"
# Check for unmerged paths (merge conflicts)
if ! git diff --check &>/dev/null && git ls-files -u | grep -q .; then
echo "ERROR: Repository has unmerged files (merge conflicts)"
echo "Please resolve conflicts manually in $target_dir"
cd - >/dev/null
return 1
fi
# Check for any changes (tracked, untracked, or modified)
local has_changes=false
if ! git diff-index --quiet HEAD -- 2>/dev/null || [ -n "$(git ls-files --others --exclude-standard)" ]; then
echo "Stashing local changes (including untracked files)..."
git stash push -u -m "bootstrap.sh auto-stash $(date +%Y-%m-%d_%H-%M-%S)"
has_changes=true
fi
# Sync with remote
echo "Pulling latest changes..."
git pull --rebase || {
echo "WARNING: Failed to pull changes, repository may have conflicts"
if [ "$has_changes" = true ]; then
echo "Attempting to pop stash..."
git stash pop || echo "WARNING: Could not auto-pop stash, run '\''git stash pop'\'' manually later"
fi
cd - >/dev/null
return 1
}
# Pop stash if we created one
if [ "$has_changes" = true ]; then
echo "Restoring stashed changes..."
git stash pop || echo "WARNING: Could not auto-pop stash, changes are still in stash. Run '\''git stash list'\'' to see them."
fi
cd - >/dev/null
else
echo "Cloning $repo to $target_dir..."
if [ -d "$target_dir" ]; then
echo "WARNING: Directory exists but is not a git repo, removing..."
/bin/rm -rf "$target_dir"
fi
gh repo clone "$repo" "$target_dir"
fi
}
sync_or_clone_repo shedali/home-manager ~/.config/home-manager
sync_or_clone_repo shedali/nvim ~/.config/nvim
'
cd ~/.config/home-manager && nix run home-manager/master -- switch -b backup
# Clone additional personal repos after home-manager creates directory structure
echo "Cloning additional personal repositories..."
/nix/var/nix/profiles/default/bin/nix shell nixpkgs#gh nixpkgs#git --command bash -c '
# Define the sync_or_clone_repo function in subshell
sync_or_clone_repo() {
local repo=$1
local target_dir=$2
if [ -d "$target_dir/.git" ]; then
echo "Repository exists at $target_dir, syncing..."
cd "$target_dir"
# Check for unmerged paths (merge conflicts)
if ! git diff --check &>/dev/null && git ls-files -u | grep -q .; then
echo "ERROR: Repository has unmerged files (merge conflicts)"
echo "Please resolve conflicts manually in $target_dir"
cd - >/dev/null
return 1
fi
# Check for any changes (tracked, untracked, or modified)
local has_changes=false
if ! git diff-index --quiet HEAD -- 2>/dev/null || [ -n "$(git ls-files --others --exclude-standard)" ]; then
echo "Stashing local changes (including untracked files)..."
git stash push -u -m "bootstrap.sh auto-stash $(date +%Y-%m-%d_%H-%M-%S)"
has_changes=true
fi
# Sync with remote
echo "Pulling latest changes..."
git pull --rebase || {
echo "WARNING: Failed to pull changes, repository may have conflicts"
if [ "$has_changes" = true ]; then
echo "Attempting to pop stash..."
git stash pop || echo "WARNING: Could not auto-pop stash, run '\''git stash pop'\'' manually later"
fi
cd - >/dev/null
return 1
}
# Pop stash if we created one
if [ "$has_changes" = true ]; then
echo "Restoring stashed changes..."
git stash pop || echo "WARNING: Could not auto-pop stash, changes are still in stash. Run '\''git stash list'\'' to see them."
fi
cd - >/dev/null
else
echo "Cloning $repo to $target_dir..."
if [ -d "$target_dir" ]; then
echo "WARNING: Directory exists but is not a git repo, removing..."
/bin/rm -rf "$target_dir"
fi
gh repo clone "$repo" "$target_dir"
fi
}
sync_or_clone_repo shedali/blog ~/dev/shedali/writing/blog
sync_or_clone_repo shedali/text-blog ~/dev/shedali/writing/text-blog
sync_or_clone_repo shedali/citations ~/dev/shedali/writing/citations
'
# Source the new profile to make newly installed packages available
if [ -f ~/.nix-profile/etc/profile.d/hm-session-vars.sh ]; then
. ~/.nix-profile/etc/profile.d/hm-session-vars.sh
fi
echo "Installing neovim plugins..."
nvim --headless +'lua require("lazy").sync({wait=true})' +qall
echo "Setup complete! Opening new terminal for verification..."
osascript -e 'tell application "Terminal" to do script ""'
# Ask if this is a work or personal setup using gum (now available after home-manager switch)
echo ""
echo "DEBUG: About to prompt for setup type..."
# Try gum first, fall back to read if it fails
# Temporarily disable 'set -e' to prevent exit on gum failure
# Redirect both stdin and stderr to /dev/tty so gum UI is visible
set +e
setup_type=$(gum choose "Personal" "Work" </dev/tty 2>/dev/tty)
gum_exit_code=$?
set -e
echo "DEBUG: gum choose returned exit code: $gum_exit_code, selected: '$setup_type'"
if [ $gum_exit_code -eq 0 ] && [ -n "$setup_type" ]; then
echo "DEBUG: Setup type selected via gum: '$setup_type'"
else
echo "DEBUG: gum choose failed, falling back to read prompt"
echo "Select setup type:"
echo " 1) Personal"
echo " 2) Work"
read -p "Enter choice (1 or 2): " choice </dev/tty
case "$choice" in
1) setup_type="Personal" ;;
2) setup_type="Work" ;;
*)
echo "ERROR: Invalid choice" >&2
exit 1
;;
esac
echo "DEBUG: Setup type selected via read: '$setup_type'"
fi
if [ "$setup_type" = "Work" ]; then
echo "DEBUG: Entering Work setup block..."
echo ""
echo "========================================"
echo "Starting Chase work environment setup..."
echo "========================================"
echo ""
# Apply work nix-darwin configuration
echo "Step 1/4: Applying work nix-darwin configuration..."
echo "This will install 1Password, Docker, Ghostty, Chrome, Slack, and VS Code"
echo "You will be prompted for your sudo password..."
# Back up existing /etc files that nix-darwin needs to manage
if [ -f /etc/zshrc ] && [ ! -f /etc/zshrc.before-nix-darwin ]; then
echo "Backing up /etc/zshrc to /etc/zshrc.before-nix-darwin..."
sudo mv /etc/zshrc /etc/zshrc.before-nix-darwin
fi
if [ -f /etc/zprofile ] && [ ! -f /etc/zprofile.before-nix-darwin ]; then
echo "Backing up /etc/zprofile to /etc/zprofile.before-nix-darwin..."
sudo mv /etc/zprofile /etc/zprofile.before-nix-darwin
fi
echo "DEBUG: About to run nix-darwin command..."
if ! sudo nix run nix-darwin -- switch --flake github:shedali/nix-darwin#work --refresh; then
echo "DEBUG: nix-darwin command failed with exit code $?"
echo ""
echo "ERROR: Failed to apply nix-darwin work configuration" >&2
echo "You may need to run this manually later:"
echo " sudo nix run nix-darwin -- switch --flake github:shedali/nix-darwin#work --refresh"
exit 1
fi
echo "✓ nix-darwin work configuration applied successfully"
echo ""
# Setup 1Password
echo "Step 2/4: Setting up 1Password..."
echo "1Password app should be installed via nix-darwin work configuration"
echo "Now you need to sign in to 1Password and authenticate the CLI"
# Check if user wants to setup 1Password
setup_1password=false
set +e
if gum confirm "Open 1Password app to sign in?" </dev/tty 2>/dev/tty; then
setup_1password=true
else
gum_exit=$?
if [ $gum_exit -ne 0 ]; then
# gum failed, fall back to read
read -p "Open 1Password app to sign in? (y/n): " answer </dev/tty
if [[ "$answer" =~ ^[Yy] ]]; then
setup_1password=true
fi
fi
fi
set -e
if [ "$setup_1password" = "true" ]; then
# Check if 1Password app exists
if [ ! -e "/Applications/1Password.app" ]; then
echo "ERROR: 1Password app not found!"
echo "Ensure 1Password is included in your nix-darwin work configuration (github:shedali/nix-darwin#work)"
if ! { gum confirm "Continue without 1Password setup?" </dev/tty 2>/dev/tty || { read -p "Continue without 1Password setup? (y/n): " answer </dev/tty && [[ "$answer" =~ ^[Yy] ]]; }; }; then
exit 1
fi
fi
if [ -e "/Applications/1Password.app" ]; then
open -a "1Password"
echo "Please sign in to 1Password with your Chase account"
if gum confirm "1Password sign-in complete?" </dev/tty 2>/dev/tty || { read -p "1Password sign-in complete? (y/n): " answer </dev/tty && [[ "$answer" =~ ^[Yy] ]]; }; then
echo "1Password app setup confirmed"
# Authenticate op CLI with the 1Password app
echo "Now authenticating 1Password CLI (op)..."
echo "The op CLI will connect to the 1Password app you just signed into"
if op account list &>/dev/null; then
echo "op CLI already authenticated"
else
# The op CLI should automatically connect to the 1Password app
if op account get &>/dev/null 2>&1; then
echo "op CLI connected to 1Password app successfully"
else
echo "op CLI authentication may need manual setup"
echo "If prompted, follow the op CLI authentication flow"
eval $(op signin) || echo "op signin had issues, but continuing..."
fi
fi
fi
fi
fi
# Setup sudo password caching
echo ""
echo "Step 3/4: Setting up sudo password caching with 1Password..."
echo ""
# Select which password to use
echo "Which password should be used for sudo commands throughout this setup?"
set +e
password_choice=$(gum choose \
"Default Password (before MDM/Jamf Connect setup)" \
"System Password (after MDM/Jamf Connect setup)" </dev/tty 2>/dev/tty)
gum_pwd_exit=$?
set -e
if [ $gum_pwd_exit -eq 0 ] && [ -n "$password_choice" ]; then
echo "DEBUG: Password selected via gum: '$password_choice'"
else
echo "DEBUG: gum choose failed for password, falling back to read prompt"
echo " 1) Default Password (before MDM/Jamf Connect setup)"
echo " 2) System Password (after MDM/Jamf Connect setup)"
read -p "Enter choice (1 or 2): " choice </dev/tty
case "$choice" in
1) password_choice="Default Password (before MDM/Jamf Connect setup)" ;;
2) password_choice="System Password (after MDM/Jamf Connect setup)" ;;
*)
echo "WARNING: Invalid choice, skipping sudo caching"
password_choice=""
;;
esac
echo "DEBUG: Password selected via read: '$password_choice'"
fi
if [ -n "$password_choice" ]; then
case "$password_choice" in
"Default Password (before MDM/Jamf Connect setup)")
SUDO_PASSWORD_REF="op://Chase/JPMorgan Chase/Bootstrapping/default password"
;;
"System Password (after MDM/Jamf Connect setup)")
SUDO_PASSWORD_REF="op://Chase/JPMorgan Chase/Bootstrapping/system password"
;;
esac
fi
# Cache the sudo password
if [ -n "$SUDO_PASSWORD_REF" ] && command -v op &>/dev/null && op account get &>/dev/null; then
echo "Retrieving sudo password from 1Password..."
echo "Using: ${SUDO_PASSWORD_REF}"
if op read "$SUDO_PASSWORD_REF" | sudo -S -v 2>/dev/null; then
echo "✓ Sudo credentials cached successfully"
echo "All subsequent sudo commands will use this cached password"
# Keep sudo session alive in background
(
while true; do
sleep 50
sudo -n true 2>/dev/null || break
done
) &
SUDO_REFRESH_PID=$!
# Setup trap to kill background process on exit
trap "kill $SUDO_REFRESH_PID 2>/dev/null || true" EXIT
else
echo "WARNING: Failed to authenticate with 1Password password"
echo "You may need to enter your password manually for sudo commands"
fi
else
if [ -z "$SUDO_PASSWORD_REF" ]; then
echo "WARNING: No password selected, sudo will prompt for password normally"
else
echo "WARNING: 1Password CLI not available, sudo will prompt for password normally"
fi
fi
# Now run chase-setup.sh for Chase-specific configuration
echo ""
echo "Step 4/4: Running Chase-specific setup (chase-setup.sh)..."
~
~ # Check if local copy exists (for development), otherwise download from gist
~ if [ -f ~/.config/home-manager/chase-setup.sh ]; then
~ echo "Using local chase-setup.sh from ~/.config/home-manager/"
~ chase_setup_script=~/.config/home-manager/chase-setup.sh
~ else
~ echo "Downloading chase-setup.sh from gist..."
~ if curl -fsSL https://gist.githubusercontent.com/shedali/7f96ef92ead665e7cfc2f7652cb0b179/raw/chase-setup.sh -o /tmp/chase-setup.sh; then
~ echo "chase-setup.sh downloaded successfully"
~ chmod +x /tmp/chase-setup.sh
~ chase_setup_script=/tmp/chase-setup.sh
else
~ echo "ERROR: Failed to download chase-setup.sh" >&2
~ exit 1
fi
+ fi
+
+ echo "Running chase-setup.sh..."
+ bash "$chase_setup_script"
+ exit_code=$?
+ if [ $exit_code -eq 0 ]; then
+ echo "Chase setup completed successfully!"
+ elif [ $exit_code -eq 130 ]; then
+ echo "Chase setup was cancelled by user"
+ exit 0
else
~ echo "ERROR: chase-setup.sh failed with exit code $exit_code" >&2
~ echo "You can try running it manually: bash $chase_setup_script"
~ exit $exit_code
fi
else
echo "Applying personal nix-darwin configuration..."
echo "You will be prompted for your sudo password..."
# Back up existing /etc files that nix-darwin needs to manage
if [ -f /etc/zshrc ] && [ ! -f /etc/zshrc.before-nix-darwin ]; then
echo "Backing up /etc/zshrc to /etc/zshrc.before-nix-darwin..."
sudo mv /etc/zshrc /etc/zshrc.before-nix-darwin
fi
if [ -f /etc/zprofile ] && [ ! -f /etc/zprofile.before-nix-darwin ]; then
echo "Backing up /etc/zprofile to /etc/zprofile.before-nix-darwin..."
sudo mv /etc/zprofile /etc/zprofile.before-nix-darwin
fi
sudo nix run nix-darwin -- switch --flake github:shedali/nix-darwin#personal --refresh
echo "Personal setup complete!"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment