New Mac setup script

July 7, 2025

One of the best things about LLMs is that the cost of writing bash or zsh has gone to 0. Here's a way to get started on a new Mac by setting up uv then using it to drive a bunch of macOS automation:

#!/usr/bin/env zsh
set -euo pipefail

# 0. Install uv if missing
if ! command -v uv >/dev/null; then
  echo "→ Installing uv…"
  curl -LsSf https://astral.sh/uv/install.sh | sh
  export PATH="$HOME/.cargo/bin:$PATH"
fi

which can then just run this Python script:

#!/usr/bin/env python
from __future__ import annotations
import shutil, subprocess, sys, os, platform
from pathlib import Path
import inspect

def run(cmd: list[str] | str, sudo: bool = False) -> None:
    if sudo:
        cmd = ["sudo", "-E"] + (cmd if isinstance(cmd, list) else [cmd])
    print("·", " ".join(cmd) if isinstance(cmd, list) else cmd)
    subprocess.run(cmd, check=True, shell=isinstance(cmd, str))

def ensure_in_path(brew_bin: str) -> None:
    incantation = f'eval "$({brew_bin} shellenv)"'
    shellrc = Path.home() / ".zshrc"
    text = shellrc.read_text() if shellrc.exists() else ""
    if incantation not in text.splitlines():
        shellrc.write_text(text + f"\n# Homebrew\n{incantation}\n")

def install_homebrew() -> None:
    if shutil.which("brew"):
        return
    run(
        '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"',
        sudo=False,
    )
    # apple-silicon vs intel
    brew_bin = "/opt/homebrew/bin/brew" if Path("/opt/homebrew/bin").exists() else "/usr/local/bin/brew"
    ensure_in_path(brew_bin)

def install_mas() -> None:
    run(["brew", "install", "mas"])

def is_mas_installed(app_id: str) -> bool:
    out = subprocess.run(["mas", "list"], capture_output=True, text=True, check=True).stdout
    return any(line.split()[0] == app_id for line in out.splitlines())

def mas_apps() -> None:
    app_id = "1475387142"          # Tailscale
    if is_mas_installed(app_id):
        return                     # already installed – skip prompt & install
    input("Sign in to the Mac App Store now, then press <Enter>...")
    run(["mas", "install", app_id])

def brew_casks() -> None:
    casks = [
        "ghostty", "alt-tab", "zed", "visual-studio-code",
        "slack", "1password", "notion-calendar", "obsidian",
        "linear-linear", "raycast",
    ]
    run(["brew", "install", "--quiet", "--cask"] + casks)

def brew_cli() -> None:
    run(["brew", "install", "starship", "tmux"])

def keyboard_tweaks() -> None:
    cmds = [
        'defaults write NSGlobalDomain NSAutomaticSpellingCorrectionEnabled -bool false',
        'defaults write NSGlobalDomain ApplePressAndHoldEnabled -bool false',
        'defaults write NSGlobalDomain InitialKeyRepeat -int 10',
        'defaults write NSGlobalDomain KeyRepeat -int 1',
    ]
    for c in cmds:
        run(c, sudo=True)

def setup_starship() -> None:
    # need to add this command to ~/.zshrc idempotently if it's not already there
    starship_init_command = 'eval "$(starship init zsh)"'
    zshrc_path = Path.home() / ".zshrc"
    if zshrc_path.exists():
        with zshrc_path.open("r") as f:
            content = f.read()
        if starship_init_command not in content:
            with zshrc_path.open("a") as f:
                f.write(f"\n{starship_init_command}\n")
    else:
        with zshrc_path.open("w") as f:
            f.write(f"# Zsh configuration file\n{starship_init_command}\n")
    command = "starship preset no-nerd-font -o ~/.config/starship.toml"
    if not Path("~/.config/starship.toml").expanduser().exists():
        # make sure the ~/.config folder exists
        config_dir = Path.home() / ".config"
        config_dir.mkdir(parents=True, exist_ok=True)
        run(command, sudo=False)

def customize_zshrc() -> None:
    block = inspect.cleandoc("""
        export HISTFILE="$HOME/.zsh_history"
        export HISTSIZE=1000000000
        export SAVEHIST=1000000000
        setopt EXTENDED_HISTORY
    """) + "\n"
    zshrc = Path.home() / ".zshrc"
    content = zshrc.read_text() if zshrc.exists() else ""
    if "EXTENDED_HISTORY" not in content:
        zshrc.write_text(content + block)


def setup_ghostty() -> None:
    """make a file (and folders) for $HOME/.config/ghostty/config and write to it"""
    config_dir = Path.home() / ".config" / "ghostty"
    config_dir.mkdir(parents=True, exist_ok=True)
    config_file = config_dir / "config"
    config_contents = inspect.cleandoc("""
    copy-on-select = clipboard
    font-family = "SFMono"
    theme = "tokyonight"
    """).strip() + "\n"
    if not config_file.exists():
        config_file.write_text(config_contents)

def set_up_ssh_keys_and_github() -> None:
    """
    Ensure ~/.ssh/id_ed25519 exists, is loaded into the system agent,
    and (optionally) stored in the macOS keychain.
    """
    ssh_dir = Path.home() / ".ssh"
    ssh_dir.mkdir(mode=0o700, exist_ok=True)

    key_path = ssh_dir / "id_ed25519"
    pub_path = key_path.with_suffix(".pub")

    # 1. Generate a key exactly once
    if not key_path.exists():
        email = input("Email address for the new SSH key: ").strip()
        run(["ssh-keygen", "-t", "ed25519", "-f", str(key_path), "-C", email], sudo=False)

    # 2. Check if the key is already in the agent
    def key_loaded() -> bool:
        res = subprocess.run(["ssh-add", "-l"], capture_output=True, text=True)
        return res.returncode == 0 and key_path.name in res.stdout

    if not key_loaded():
        # macOS runs an ssh-agent under launchd by default; just add the key.
        run(["ssh-add", "--apple-use-keychain", str(key_path)], sudo=False)

    # 3. Ensure SSH config adds keys automatically (once)
    cfg = ssh_dir / "config"
    stanza = inspect.cleandoc(f"""
    # ~/.ssh/config
    # This file is automatically generated by setup_computer.py
    # It configures SSH to use the key and add it to the agent.
    Host *
        AddKeysToAgent yes
        UseKeychain yes
        IdentityFile ~/.ssh/id_ed25519
    """).strip() + "\n"
    if not cfg.exists() or "AddKeysToAgent yes" not in cfg.read_text():
        cfg.write_text(cfg.read_text() + stanza if cfg.exists() else stanza, encoding="utf-8")

    # 4. Remind the user to register the key with GitHub
    print(
        f"\n   SSH key ready → copy it with:\n"
        f"    pbcopy < {pub_path}\n"
        "Then add it to https://github.com/settings/keys\n"
    )


def main() -> None:
    if platform.system() != "Darwin":
        sys.exit("This script is intended for macOS.")

    install_homebrew()
    install_mas()
    mas_apps()
    brew_casks()
    brew_cli()
    keyboard_tweaks()
    setup_starship()
    customize_zshrc()
    set_up_ssh_keys_and_github()
    setup_ghostty()

    print("Setup complete. Log out/in (or reboot) for keyboard changes to apply.")

if __name__ == "__main__":
    try:
        main()
    except subprocess.CalledProcessError as exc:
        print(f"Command failed with exit code {exc.returncode}", file=sys.stderr)
        sys.exit(exc.returncode)