Personal development environment managed with Chezmoi: Homebrew on macOS, native apt packages on Ubuntu 26.04. Shell, Git, editor, and XDG configuration are shared across both systems.
| Area | Configuration |
|---|---|
| Terminal | Ghostty native panes and tabs, optional tmux sessions, and SSH helpers |
| Shell | Zsh, Starship, fzf, mise, and direnv |
| Editors | Zed, VS Code, Vim, and Neovim |
| Shortcuts | Hyper shortcuts for terminal panes and tabs |
| Packages | Homebrew profiles on macOS; common native apt packages on Ubuntu |
| SSH | Portable client config; keys stay local to each machine |
| macOS | Dock, Finder, keyboard, Safari, and general defaults |
On a fresh macOS or Linux machine, one command installs chezmoi into
~/.local/bin, clones this repository, and applies it:
sh -c "$(curl -fsSL https://raw.githubusercontent.com/froppa/dotfiles/master/bootstrap.sh)"Run it through sh -c rather than piping into sh so the Git identity prompts
can read the terminal; GIT_NAME and GIT_EMAIL in the environment skip them
and WORK=true selects the work profile. On macOS it then offers the defaults
script. With the repository already cloned, the equivalent is:
git clone https://github.com/froppa/dotfiles.git ~/.local/share/chezmoi
cd ~/.local/share/chezmoi
./init.sh --macosUse WORK=true ./init.sh --macos for the work package profile. An existing
~/.gitconfig is left unchanged and any detected global identity is reused. If
the file does not exist, bootstrap prompts for a missing Git name or email
before creating the default config. Put machine-specific overrides or signing
settings in ~/.gitconfig.local. On new machines, the root config includes
repository defaults from the managed ~/.config/git/dotfiles.gitconfig. Omit
--macos to apply the dotfiles without changing macOS defaults.
Install the bootstrap prerequisites and Chezmoi, then preview before applying:
sudo apt-get update
sudo apt-get install -y git curl ca-certificates
sh -c "$(curl -fsLS get.chezmoi.io)" -- -b "$HOME/.local/bin"
export PATH="$HOME/.local/bin:$PATH"
git clone https://github.com/froppa/dotfiles.git ~/.local/share/chezmoi
cd ~/.local/share/chezmoi
chezmoi init --source="$PWD" --promptString 'name=YOUR_NAME'
chezmoi diff
chezmoi apply -vIf already cloned, use that checkout instead of cloning again. Ubuntu setup installs missing packages with apt and never installs or updates Linuxbrew. An existing Linuxbrew installation is left on disk, but managed Zsh startup no longer adds it to PATH. Personal and work profiles currently share the same Ubuntu package set. Raycast and Hammerspoon configuration is macOS-only.
The native set covers shell, editor, Git, and common build tools. It does not
add third-party repositories or install mise, Codex, Claude Code, Docker,
OpenTofu, AWS CLI, lazygit, Ghostty, GUI editors, or Nerd Fonts. These remain
separate bootstrap steps; Ubuntu's yq is also not assumed equivalent to the
Homebrew formula. Once mise is installed, mise install installs the declared
runtimes; copying its configuration alone does not install them. Some Neovim
plugins need those runtimes before their first launch.
After applying, choose Zsh as your login shell if desired:
chsh -s "$(command -v zsh)"Log out and back in. fd and bat aliases handle Ubuntu's executable names,
and fzf uses an executable path that also works in its subprocesses. The Linux
update alias uses apt. Mac-specific Hyper shortcuts require separate desktop
key bindings on Ubuntu.
Authenticate GitHub, Codex, and Claude freshly on Ubuntu as your normal user. Do not copy tokens, private keys, or provider credential stores from another machine. SSH access and the SSH server belong to the machine's infrastructure setup; these dotfiles manage only portable client configuration. A remote tmux session continues when you disconnect, but not when the Ubuntu VM shuts down.
After applying the native package configuration, install Claude Code and mise through their signed apt repositories, and Codex through its official standalone installer:
./scripts/bootstrap-ubuntu-ai.sh
codex login
claude auth login
gh auth login --webThis does not authenticate automatically or copy credentials from your Mac. Codex is pinned to the validated CLI version in the script. Runtime installation is separate; install the required runtimes and build tools before running source-building plugin installers.
SSH keys are never managed by this repository. Generate a new key on each machine when needed:
./scripts/ssh-keygen.shSSH behavior is config-driven: AddKeysToAgent and macOS Keychain integration
load the key on first use. Shell startup does not call ssh-add.
| Component | Once per machine |
|---|---|
| Raycast | Configure Caps Lock as Hyper (Control+Option+Command, no Shift) |
| Neovim | Open once to finish Lazy, Mason, and tree-sitter setup |
| Ghostty | Launched from the Dock or Raycast it has launchd's PATH, so name tmux's absolute path in ~/.config/ghostty/config.local: command = direct:/opt/homebrew/bin/tmux new-session -A -s main |
Raycast exports are intentionally not managed because they can contain private local data and did not reliably restore settings. To add a Ghostty launcher, open Raycast's Applications extension, add a Command for Ghostty, and assign the hotkey there. tmux plugins install through TPM; Continuum and Resurrect handle session restoration.
Configure Caps Lock as Hyper (Ctrl+Option+Command) in Raycast. Ghostty uses
these chords for native panes and tabs; Right Option remains available for
keyboard symbols.
| Shortcut | Action |
|---|---|
Command+K |
Clear the active application screen |
Shift+Enter |
Let the active application handle newlines |
Caps+C |
New tab |
Caps+I |
Side-by-side pane |
Caps+- |
Stacked pane |
Caps+Arrow |
Move between panes |
Caps+Shift+Arrow |
Resize pane |
Caps+= |
Spread the panes evenly |
Caps+1…9 |
Select tab |
Caps+Z |
Toggle pane zoom |
Caps+X |
Close the active surface |
Caps+, |
Rename tab |
Caps+Shift+, / Caps+Shift+. |
Move tab left / right |
Caps+N / Caps+P |
Next / previous tab |
tmux uses the prefix Ctrl-a. Its cheat sheet lists window, pane, and copying
commands. Windows are named after their directory or SSH host; a manual name
persists. Mouse drag, double-click, and triple-click copy without leaving copy
mode. Hold Shift to let Ghostty select text itself. tm attaches a persistent
session from another terminal.
ssht <host> [session] attaches a persistent tmux session on a remote machine
inside the current pane. Use tmux's prefix commands to control its windows
and panes. Run outside tmux, ssht instead tints
Ghostty's background in the host's colour and titles the tab after it until
the connection ends.
ssht also forwards the Mac's clipboard socket, which the
local.dotfiles.clip-serve LaunchAgent serves. The wl-paste shim on the remote
host reads it, so a screenshot copied on the Mac (Cmd+Ctrl+Shift+4) pastes
into a remote Claude Code with Ctrl+V. Only images cross; text still pastes
with Cmd+V. Plain ssh does not carry it.
When a program exits without restoring the terminal (a remote tmux whose connection dropped, a crashed TUI), the next prompt switches mouse, focus, and extended-key reporting off again, so clicks stop typing escape sequences.
The hooks also record each session's state under
~/.cache/agents/claude-<session id> for terminal status consumers.
Outside tmux, the same hooks
retitle the Ghostty tab with the session's state and directory (⟳ repo working, ? repo waiting for you,
· repo idle), Claude Code rings the terminal bell when it needs attention,
and Ghostty marks the tab. Starship's right prompt shows the
ssh host, running agents (with how many are working or waiting for you), and
remaining limits from the same caches the tmux status bar uses, refreshed in
the background; it is cleared once a command runs so history stays clean.
Row one is the context badge and the window tabs: ⌂ local, or ⇅ host in a
colour derived from the host name when the active pane runs ssh (or when tmux
itself runs on a machine reached over ssh). The bar tint and the active pane
border follow the same colour. A tab whose pane runs claude or codex
carries a ✦ or ◆ icon in that agent's colour. Claude Code hooks in
~/.claude/settings.json call agent-state.sh, so the ✦ is orange while the
session works, yellow ✦? when it waits for a permission or answer, and grey
once its turn is finished; a split window shows |
(side by side), - (stacked) or |-, and Z marks a zoomed one. Row two shows running Claude (✦) and Codex (◆)
CLI sessions, the remaining 7-day limit for
Claude Code and Codex read from their OAuth usage endpoints with the tokens the
CLIs already store (cached 5 min), the git branch, and RAM. Claude Code's own
status line keeps only model, effort, and context left.
Claude Code keeps ~/.claude/settings.json itself, so the tab-state hooks are
installed once per machine with scripts/claude-tmux-hooks.sh, which merges
them idempotently. Two local configuration files under ~/.config/tmux/
customize SSH status:
| File | Content |
|---|---|
hosts.local |
<host> <accent> <tint> hex colours overriding the derived ones |
agent-hosts.local |
ssh aliases, one per line, whose agent counts are fetched over ssh every minute |
chezmoi diff # inspect changes
chezmoi apply -v # apply source state
chezmoi update -v # pull and apply
chezmoi edit --apply ~/.zshrc # edit through Chezmoi
chezmoi re-add ~/.zshrc # capture a target change
chezmoi cd # open the source repositoryMachine-specific overrides use unmanaged *.local files such as
.zshrc.local, .exports.local, .aliases.local, .functions.local, and
.gitconfig.local.
| Path | Purpose |
|---|---|
home/dot_config/ghostty/ |
Ghostty and Hyper mappings |
home/dot_config/tmux/ |
tmux, status scripts, and plugins |
home/dot_local/bin/ |
clip-serve (Mac) and the wl-paste shim (Linux) behind the ssht clipboard bridge |
home/dot_config/zed/ |
Zed settings and keybindings |
home/dot_config/Code/User/ |
VS Code XDG settings |
home/dot_config/nvim/ |
Neovim configuration |
home/.chezmoidata/ |
Packages, profiles, and VS Code extensions |
home/.chezmoiscripts/ |
Ordered and change-triggered setup |
home/.chezmoiexternal.toml |
Pinned shell and TPM sources |
macos-scripts/ |
Auditable macOS defaults |
scripts/ |
Local SSH key generation and Claude Code hook install |
macos-scripts/macos-defaults.sh --audit # show differences
macos-scripts/macos-defaults.sh # apply defaults
macos-scripts/macos-defaults.sh --update # apply and update macOS./test.shThe same ShellCheck, template, profile, and Chezmoi dry-run checks run in CI. Neovim reference: docs/nvim-cheat-sheet.md.