Created
July 5, 2025 23:51
-
-
Save rachtsingh/4eb5aaf7a7dffbb49c0f24cab75702ab to your computer and use it in GitHub Desktop.
setup macOS
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
#!/usr/bin/env zsh | |
set -euo pipefail | |
# 0. Install uv (fast Python+venv manager) 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" # uv installer puts it here by default | |
fi | |
# 1. Run the Python setup (deps resolved ad-hoc) | |
exec uv run --with click ./setup_computer.py "$@" |
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
#!/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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment