Use this guide when installing or refreshing a Mac from uinaf/dotfiles.
The repo has two install profiles:
personalfor a human-operated Mac.devboxfor a shared SSH-first agent host.
Run commands from the repo root unless a step says otherwise.
Install Apple Command Line Tools:
xcode-select --installInstall Homebrew:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"Install the minimum tools needed to clone the repo:
brew install git gh
gh auth loginClone the repo:
mkdir -p ~/projects/uinaf
gh repo clone uinaf/dotfiles ~/projects/uinaf/dotfiles
cd ~/projects/uinaf/dotfiles
mise trustmise trust is a local approval for this checkout's mise.toml. Run it before
mise install, mise tasks, or mise run ...; otherwise mise refuses to load
the repo config.
Use this only when a fresh Mac cannot run git or gh yet. macOS ships enough
tools to fetch a public GitHub archive, which lets a human or agent inspect the
bootstrap files before running anything:
mkdir -p ~/projects/uinaf
curl -fL https://github.com/uinaf/dotfiles/archive/refs/heads/main.zip \
-o /tmp/uinaf-dotfiles-main.zip
ditto -x -k /tmp/uinaf-dotfiles-main.zip ~/projects/uinaf
mv ~/projects/uinaf/dotfiles-main ~/projects/uinaf/dotfiles
cd ~/projects/uinaf/dotfilesArchive checkouts are disposable. They are acceptable for reading docs and
running the first public bootstrap scripts, and scripts/bootstrap/install.sh
can install files from an archive checkout. After Homebrew, git, and gh
are installed, replace the archive with a real clone so updates, diffs, hooks,
and contribution checks work normally:
cd ~/projects/uinaf
mv dotfiles dotfiles.archive.$(date +%Y%m%d%H%M%S)
gh repo clone uinaf/dotfiles dotfiles
cd dotfilesDo not run identity, signing-key, or secret setup from guessed values just
because the repo was fetched this way. Keep using the personal or devbox
profile steps below.
Install Homebrew dependencies:
./scripts/bootstrap/brew-bundle.sh personalInstall personal Mac App Store apps and remove bundled apps this setup does not use:
./scripts/app-store/personal.shThis uses mas, requires the interactive user to be signed into the App Store,
and may ask for the local account password during install or uninstall.
Install optional external tools:
./scripts/bootstrap/install-blacksmith.sh
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"Editor and terminal defaults prefer Berkeley Mono Variable. This repo does
not install it because it is a licensed font; ask the human to provide and
install it when available. The shared Brewfile installs FiraCode Nerd Font as
the free fallback for Ghostty and Zed.
Codex app appearance is manual app state, not repo-managed config. After installing the Codex app, open its settings and set:
- code font:
Berkeley Mono Variable - UI font size:
14 px - code font size:
14 px - Font Smoothing: on
./scripts/bootstrap/install.sh configures Codex defaults in
~/.codex/config.toml, including forced_login_method = "chatgpt" so Codex
uses ChatGPT subscription access instead of API-key billing. It does not manage
Codex auth tokens, sessions, approvals, or app state.
The same install step applies mise's trusted Codex and Claude worktree roots and runs the agent worktree mise trust helper. Use the matching task in Mise tasks to refresh local trust after new worktrees are created.
Remote Codex connections are also manual user config. If the machine should use
them, ask the human to add this to ~/.codex/config.toml:
[features]
remote_connections = trueApply dotfiles and configure local state:
./scripts/bootstrap/install.sh
./scripts/bootstrap/configure-git.sh --profile personal
./scripts/bootstrap/configure-power.sh --profile personal
./scripts/bootstrap/configure-spotlight.sh
mise trust
mise install
./scripts/bootstrap/pull-repos.shThe shared mise config pins Node, enables Corepack's pnpm shim, selects the
default pnpm release, and installs the exact shared npm, Playwright CLI, and
Vite+ versions. install.sh also repairs the Corepack shim for an existing
Node installation; mise install remains required for a fresh runtime or CLI
version change.
The dotfile step applies the repo-local chezmoi source state from chezmoi/.
Preview it with ./scripts/bootstrap/apply-dotfiles.sh --dry-run --verbose
when changing source-state files. The power step disables system, display, and
disk sleep only while the Mac is plugged in. Battery settings stay under macOS
defaults so laptops still sleep normally when unplugged. It prompts for sudo;
install.sh remains a user-level dotfile and Codex-defaults step.
configure-spotlight.sh is the same host-wide baseline for personal and
devbox Macs: it disables indexing on mounted volumes without deleting existing
index data.
Chrome vertical tabs are a local browser preference. Quit Chrome first, then:
./scripts/bootstrap/configure-chrome.shPersonal Macs may use an existing 1Password SSH key for autonomous Git signing
without depending on the 1Password SSH agent at commit time. This is a one-time
human bootstrap: open the SSH Key item in 1Password, export its private key in
OpenSSH format without a passphrase, and save it to an owner-only path such as
~/.ssh/personal_ed25519. Leaving the export unencrypted is required for
unattended signing and means any process running as the local user can use the
key. Keep 1Password as the recovery copy and do not copy the exported file into
this repository.
Derive the public key and lock the file permissions before configuration:
chmod 0600 ~/.ssh/personal_ed25519
ssh-keygen -y -f ~/.ssh/personal_ed25519 > ~/.ssh/personal_ed25519.pub
chmod 0644 ~/.ssh/personal_ed25519.pubConfigure the exported key for commit signing and, when the same key is already registered for GitHub SSH authentication, for GitHub pushes:
GIT_SIGNING_KEY="$HOME/.ssh/personal_ed25519" \
GIT_SSH_IDENTITY_FILE="$HOME/.ssh/personal_ed25519" \
./scripts/bootstrap/configure-git.sh --profile personalGitHub tracks authentication and signing registrations separately even when
they contain the same public key. The key must already be present in both roles
for SSH pushes and Verified commits to work. This dual-use setup is convenient,
but revoking or rotating the local key affects both operations.
The generated Git config signs directly from the unencrypted local private key.
The dotfile install provides an owner-only signing program under
~/.local/libexec/uinaf/; Git uses it to clear SSH_AUTH_SOCK before invoking
ssh-keygen, so ambient agents are never part of commit signing. Encrypted,
public-key-only, and 1Password-backed signing are unsupported. The generated
~/.ssh/github.config block uses the same key for github.com without routing
through the 1Password agent. The tracked SSH entrypoint includes that dedicated
file before the untouched ~/.ssh/config.local, so local global directives and
host-specific configuration keep their original scope. Setup migrates only the
old marker-delimited GitHub block out of config.local; if an unmarked
Host github.com entry already exists, it stops before changing Git state and
asks you to resolve the local configuration explicitly. It also stops if
~/.ssh/github.config already exists without the uinaf managed markers; move
that file aside or migrate its directives to ~/.ssh/config.local before
rerunning. OpenSSH keeps
file-backed IdentityFile values additive, so wildcard local identities may
still appear after the exported key; the exported key remains first and
IdentityAgent none prevents agent-backed keys, including 1Password keys, from
being used for GitHub.
Verify:
./scripts/verify/bootstrap.sh --profile personal
./scripts/audit/host.sh
./scripts/audit/personal.shThe human owner profile may opt into the compact desktop baseline. It is not part of the shared agent-user bootstrap:
./scripts/bootstrap/configure-desktop.sh
./scripts/verify/bootstrap.sh --profile devbox --desktopThis keeps the built-in black system wallpaper, hidden desktop icons and widgets, an auto-hiding compact Dock, no recent apps, and Google Chrome as the only persistent Dock app. Run it only from the logged-in owner account.
Install shared plus devbox Homebrew dependencies:
./scripts/bootstrap/brew-bundle.sh devboxApply dotfiles:
./scripts/bootstrap/install.sh
./scripts/bootstrap/configure-power.sh --profile devbox
./scripts/bootstrap/configure-spotlight.sh
mise trust
mise installThe shared mise config pins Node, enables Corepack's pnpm shim, selects the
default pnpm release, and installs the exact shared npm, Playwright CLI, and
Vite+ versions. install.sh repairs the Corepack shim for an existing Node
installation, while mise install installs missing runtimes and CLIs.
The power step keeps plugged-in devboxes awake for agents, remote access, and
always-on dashboards. It leaves battery settings untouched and prompts for sudo
instead of hiding system changes inside install.sh.
The Spotlight step is the same host-wide baseline used by personal Macs.
Configure local Git identity from explicit values. Do not invent these for the user. On headless devboxes, prefer a human-provisioned local SSH key file over GUI SSH agents:
GIT_USER_NAME='Devbox Name' \
GIT_USER_EMAIL='devbox@example.com' \
GIT_SIGNING_KEY="$HOME/.ssh/devbox-key" \
./scripts/bootstrap/configure-git.sh --profile devbox --non-interactiveCommit signing requires a human-provisioned, owner-only, unencrypted local SSH private key. Agent-backed signing is unsupported.
Devbox Git config writes identity and /opt/homebrew Git safe-directory state
to ~/.gitconfig.local, not to the tracked shared config. When
GIT_SIGNING_KEY is a local private key path, devbox setup also writes a
managed Host github.com block to ~/.ssh/github.config so normal
git@github.com:... remotes work over SSH in headless sessions without relying
on a GUI agent socket. Use GIT_SSH_IDENTITY_FILE when GitHub SSH auth should
use a different local key path than commit signing.
If the devbox runs long-lived workspace or agent services, follow
Devbox setup. The short version: run
./scripts/secrets/configure-infisical-devbox.sh once with a human-supplied
Universal Auth client ID/secret, keep human Infisical CLI sessions off agent
devboxes, keep long-lived tokens out of default shells and process-compose YAML,
and run routine secret-aware commands through
./scripts/secrets/infisical-devbox-run.sh.
Verify each devbox user:
./scripts/verify/bootstrap.sh --profile devbox
./scripts/audit/host.sh
./scripts/verify/devbox-services.sh
./scripts/audit/devbox.shPull the repo and rerun the relevant profile:
cd ~/projects/uinaf/dotfiles
git pull --ff-only
./scripts/bootstrap/brew-bundle.sh personal
./scripts/bootstrap/install.sh
./scripts/bootstrap/configure-power.sh --profile personal
./scripts/bootstrap/configure-spotlight.sh
mise trust
mise install
./scripts/verify/bootstrap.sh --profile personalUse devbox instead of personal on shared agent hosts.
Xcode tvOS simulators, Android SDK, Android TV system images, CocoaPods, and Fastlane are per-machine state set up by hand. See React Native for the manual steps.
Tizen certificates, profiles, archives, and device keys are local secrets. They do not belong in Git.
Helpers live under scripts/tizen/:
./scripts/tizen/install.sh
./scripts/tizen/pack.sh
./scripts/tizen/restore.sh
./scripts/tizen/restore-from-1password.shscripts/tizen/install.sh verifies tizen, sdb, and
package-manager-cli show-info. It skips package catalog listing by default;
use --show-pkgs only when needed because Samsung's extension catalog download
can hang.
- If
brew bundle checkfails, run the matchingbrew-bundle.shprofile and retry verification. - If
chezmoiis missing, rerun./scripts/bootstrap/brew-bundle.shfor the correct profile before./scripts/bootstrap/install.sh. - If Git reports dubious ownership under
/opt/homebrew, rerunconfigure-git.shfor the correct profile. - If
git@github.comfails on a devbox but the key is present, rerunconfigure-git.sh --profile devbox --non-interactivewithGIT_SIGNING_KEYorGIT_SSH_IDENTITY_FILEpointing at the owner-only local private key file. - If shared env access is missing over SSH, check the Infisical/devbox contract in Devbox setup instead of exporting service tokens in shell startup.
- If
codexis not installed yet,install.shskips Codex defaults; rerun it after installing the shared Brewfile.