Personal cross-machine config managed by chezmoi. Works the same on macOS (Apple Silicon) and Omarchy/Arch Linux.
.chezmoi.toml.tmpl Bootstrap config — prompts for identity values on
first init, generates ~/.config/chezmoi/chezmoi.toml.
.chezmoiignore Files in this repo that should NOT be copied to $HOME
(per-OS exclusions live here too).
dot_config/ Mirrors ~/.config/. Subdirectories below.
├── ghostty/ Terminal emulator
├── git/ Templated. Identity from chezmoi.toml + [includeIf]
│ rules for ~/github/personal/ vs ~/github/work/.
├── ntm/ Named Tmux Manager config (templated)
├── nvim/ LazyVim setup
├── starship.toml Prompt config (Omarchy default — Everforest)
├── tmux/ tmux.conf
├── zed/ Zed editor settings
├── zellij/ Zellij config + layouts (ai-coding.kdl)
└── zsh/aliases.zsh Cross-platform zsh aliases (sourced from ~/.zshrc)
dot_zshrc.tmpl Main shell rc, OS-templated for Mac brew paths vs
Arch yay paths.
scripts/ NOT applied to $HOME (.chezmoiignore'd).
├── Brewfile macOS package list (brew bundle)
├── install-mac.sh Installs everything in Brewfile + chezmoi + extra CLIs
├── install-omarchy.sh Same packages via yay (handles official + AUR)
└── setup-tailscale.sh Enables Tailscale SSH so the machine is reachable
remotely (macOS + Linux; see "Remote SSH access")
Files prefixed dot_ map to . in $HOME (chezmoi convention). Files ending
in .tmpl are run through Go's text/template at apply time.
The repo can live anywhere — ~/github/personal/dotfiles is just my
convention. Substitute whatever path you cloned to in the commands below;
the install scripts and chezmoi init will work from any location.
# Clone the repo wherever you want
git clone https://github.com/Scarletbobcat/dotfiles ~/github/personal/dotfiles
cd ~/github/personal/dotfiles
# Install Homebrew (if missing), all packages from Brewfile (incl. chezmoi),
# and extra CLIs (Claude Code, Codex, beads, agent mail). Idempotent.
./scripts/install-mac.sh
# Initialize chezmoi against this repo (prompts for name + email, populates
# ~/.config/chezmoi/chezmoi.toml, then applies all dotfiles to $HOME)
chezmoi init --apply -S "$(pwd)" \
https://github.com/Scarletbobcat/dotfiles.git
# Restart your terminal (or `exec zsh`) to pick up the new shell setupThe -S flag tells chezmoi to use this clone as its source directory
instead of cloning a fresh copy into ~/.local/share/chezmoi/. The path
gets persisted into ~/.config/chezmoi/chezmoi.toml (via
{{ .chezmoi.sourceDir }} in the template), so subsequent chezmoi
commands don't need -S.
git clone https://github.com/Scarletbobcat/dotfiles ~/github/personal/dotfiles
cd ~/github/personal/dotfiles
./scripts/install-omarchy.sh
chezmoi init --apply -S "$(pwd)" \
https://github.com/Scarletbobcat/dotfiles.git
# Log out and back in (or `exec zsh`) so the shell change takes effect.The install script also runs chsh -s $(which zsh) if zsh isn't your
default shell yet, and (on re-runs after chezmoi has applied at least
once) drops you straight into a login zsh.
To reach a machine over SSH from anywhere — another laptop, or a phone — without
exposing it to the public internet or managing SSH keys, use Tailscale SSH.
The tailscale package is already in the Brewfile / yay list, so after a normal
bootstrap just run, on the machine you want to reach:
./scripts/setup-tailscale.shIdempotent and OS-aware (uname-based, so it works on both macOS and Omarchy).
On that target machine it:
- macOS — installs the headless
tailscaleddaemon (the menu-bar app can't run an SSH server) as a root launchd daemon, and sets the Mac to never sleep on AC power so it stays reachable (revert withsudo pmset -c sleep 1). - Linux — installs
tailscaleand enables thetailscaledsystemd service. - Then runs
tailscale up --ssh(prints a browser login URL on first run) so the machine authenticates to your tailnet and accepts Tailscale SSH.
Auth is your tailnet identity, not SSH keys. New tailnets ship with a default policy that lets you SSH to your own devices, so it works out of the box; the first connection may pop a quick browser re-auth "check" (switch to "accept" in the admin console to skip it).
Any device that's also on your tailnet (signed into the same account) can reach
it. Install the Tailscale app there, then SSH to the machine's name or
100.x.y.z IP (find them with tailscale status):
ssh <user>@<machine-name> # no key setup — Tailscale authenticates youFrom a phone: install the Tailscale app (puts the phone on your tailnet) plus any SSH client app — Termius is the easy pick (iOS + Android) — and connect to the same name/IP. No key needs to live on the phone.
The daemon is a launchd (macOS) / systemd (Linux) service, so it auto-starts at boot, restarts itself on crash, and reconnects across reboots, sleep/wake, and network changes. Two things worth doing/knowing:
- Disable key expiry for the machine in the admin console (Machines → device → ⋮ → Disable key expiry). Otherwise its key expires (~180 days) and it drops off the tailnet until you re-login — exactly when you're away and can't.
- Sleep is the usual "can't reach it" cause, not the daemon.
setup-tailscale.shsets the Mac to never sleep on AC, so keep it plugged in with the lid open. It reconnects automatically on wake. Revert that withsudo pmset -c sleep 1.
The tsd shell helper (in dot_config/zsh/aliases.zsh, OS-detected at runtime)
wraps daemon control:
tsd # tailnet status (peers, online/offline) — same as `tailscale status`
tsd restart # restart the daemon — do this after `brew upgrade tailscale`
tsd stop # stop the daemon (machine goes offline)
tsd start # start it againFor a lighter logical disconnect that leaves the daemon running, use the CLI
directly: sudo tailscale down / sudo tailscale up --ssh. Raw service
equivalents if you ever need them: macOS sudo launchctl kickstart -k system/com.tailscale.tailscaled; Linux sudo systemctl restart tailscaled.
Per-machine values live in ~/.config/chezmoi/chezmoi.toml, which is not
in this repo. The init step generates it from .chezmoi.toml.tmpl. Looks
like:
sourceDir = "/Users/tienhoang/github/personal/dotfiles" # whatever you passed to -S
[data]
name = "tienhoang-k2vp" # work identity (Mac default)
email = "tien@k2vp.com"
git_dir_personal = "~/github/personal/" # gitdir prefix for personal identity
git_dir_work = "~/github/work/" # gitdir prefix for work identity
theme_ghostty = "Everforest Dark Hard" # Mac theme (Linux uses omarchy)
theme_nvim = "everforest"
theme_nvim_bg = "soft"Edit this file and chezmoi apply to change identity or theme on Mac.
Git identity auto-switches based on what directory you're in:
| Path | Identity |
|---|---|
| Anywhere not matching below | chezmoi.toml defaults (name / email from prompts) |
Under git_dir_personal (default ~/github/personal/) |
personal (Scarletbobcat / yahoo email) |
Under git_dir_work (default ~/github/work/) |
work (tienhoang-k2vp / k2vp email) |
This is [includeIf "gitdir:..."] in ~/.config/git/config, with the
prefixes templated from the git_dir_personal and git_dir_work chezmoi
data variables. Override them per machine by editing
~/.config/chezmoi/chezmoi.toml and running chezmoi apply. The override
files (config-personal, config-work) live in this repo and apply to
both machines. The [includeIf] blocks no-op silently when their target
gitdir doesn't exist on a given machine.
Theme is set by chezmoi data variables. Switch by editing
~/.config/chezmoi/chezmoi.toml:
theme_ghostty = "Tokyo Night" # any Ghostty built-in theme name
theme_nvim = "tokyonight" # any colorscheme loaded by all-themes.lua
theme_nvim_bg = "moon" # if applicableThen chezmoi apply and restart Ghostty / nvim. Available nvim themes are
declared in dot_config/nvim/lua/plugins/all-themes.lua.
Themes are managed dynamically by Omarchy itself. Switching theme via the
omarchy menu updates symlinks at ~/.config/omarchy/current/theme/... and
all themed apps follow. The Mac-only theme variables are unused here.
chezmoi apply # apply pending changes from source to $HOME
chezmoi diff # show what apply would change
chezmoi cd # cd into the source directory
chezmoi edit ~/path/to/file # edit the source file (use this, not direct edits)
chezmoi add ~/path/to/file # start tracking a new file
chezmoi forget ~/path/... # stop tracking; leaves $HOME copy in place
chezmoi update # pull from origin and applyThe most common gotcha: don't edit a managed file directly in $HOME and
expect it to persist. Either edit the source via chezmoi edit, or chezmoi re-add after a direct edit.
~/.claude.json,~/.claude/projects/, history files — session state- SSH private keys, GPG keys, AWS credentials — anything secret
~/.zsh_history,.viminfo— per-machine usage history- Per-project app data (browsers,
.git/directories, etc.)
For per-machine secrets, source ~/.localrc from ~/.zshrc (already wired
up) and put env vars there. That file is untracked.