Canonical interactive bash init for macOS (Darwin 21.6+) and Linux (Ubuntu, CentOS, Rocky through 8.10). Shared via symlink; per-host tool paths are generated locally.
| Read this when… | Go to |
|---|---|
| First setup / refresh / install flags | sections below |
History, Tab completion, ** globs, fzf, bash upgrades |
docs/shell-ux.md |
| iTerm2 font / colors / keys (macOS) | iterm2/README.md (export_iterm_settings / upload_iterm_settings / test_iterm_settings / refresh_iterm_settings) |
| Vim 9 + plugins (cross-platform) | vim/README.md (refresh_vimrc) |
| Agent / platform-isolation rules | AGENTS.md |
| Private config overlay (SSH + user policy) | Private config overlay (SSH) |
- On-disk layout
- Interactive shell (quick taste)
- Process: first-time installation
- Process: refreshing from GitHub
- When to re-run
provision_init_filesvsrefresh_init_files - Process: migrate an existing host
- Platform notes
- Private config overlay (SSH)
| Path | Role |
|---|---|
~/.local/share/init-files |
Git clone of this repo (XDG data). Source of truth for bashrc / provision_init_files / bootstrap_host. |
~/.bashrc |
Symlink → ~/.local/share/init-files/bashrc |
~/.config/init-files/tools.<hostname> |
Absolute tool paths from provision_init_files (NFS-safe; legacy tools still read) |
~/.config/init-files/no-dev.<hostname> |
Presence = this host is non-dev (NFS-safe; plain install/refresh keep it) |
~/.config/init-files/github-https.<hostname> |
Presence = this host uses GitHub HTTPS (no https→ssh insteadOf) |
~/.config/init-files/github-ssh.<hostname> |
Presence = this host prefers SSH even when gh is logged in |
~/.local/state/init-files/ |
Refresh stamp / state (XDG state) |
~/.local/state/bash/ |
Per-session history + history.all archive — see shell UX |
docs/shell-ux.md (in the clone) |
Operator guide: history, completion, globs, fzf, check_tool_versions |
Do not put the clone under ~/.config/ — that tree is for host-local config (tools only). Shared content lives in ~/.local/share/init-files.
<hostname> scope key: same label as the shell prompt and pipx layout (~/.local/opt/pipx/<hostname>/). On macOS that is scutil --get ComputerName (not Bonjour LocalHostName / hostname -s, which can pick up conflict suffixes). On Linux it is the short hostname. provision_init_files migrates preference files and pipx trees from legacy names (LocalHostName, hostname -f/-s) onto this key when they differ.
~/.bashrc ──symlink──► ~/.local/share/init-files/bashrc (tracked)
~/.local/share/init-files/provision_init_files (tracked)
~/.local/share/init-files/docs/shell-ux.md
~/.config/init-files/tools.<hostname> (generated per host; NFS-safe)
~/.config/init-files/no-dev.<hostname> (optional; remembered per host)
~/.config/init-files/github-https.<hostname> (optional; HTTPS GitHub on this host)
~/.config/init-files/github-ssh.<hostname> (optional; force SSH despite gh auth)
~/.config/init-files/nfs-hosts (optional; extra keep names for cleanup)
~/.config/init-files/host-mac/<mac> (auto; MAC→hostname live registry)
~/.config/init-files/host-mac-retired (auto; names this MAC used to claim)
~/.local/opt/pipx/<hostname>/ (per-host pipx; same scope key)
~/.local/state/bash/history.all (shared command history archive)
Editing ~/.bashrc edits the file in the clone. A successful refresh_init_files updates that clone from GitHub; the symlink does not need to be rewritten unless it was replaced by a regular file.
Homebrew-resolved paths are used only on macOS 26+ (Darwin 25+). Older macOS installs use system/Xcode paths only (Homebrew shims are rejected). On those older releases brew install is often impractical: many formulae are no longer supported, and dependency builds can take forever — so init-files never recommends brew there even if Homebrew happens to be installed. On modern macOS, interactive ./provision_init_files can offer to install Homebrew and missing brew packages (required, then optional).
Full examples and behavior: docs/shell-ux.md.
| Feature | What it feels like |
|---|---|
globstar |
rm -rf **/__pycache__ — recursive ** globs |
autocd |
Type a directory name (no cd) to enter it |
| Better Tab | Case-insensitive; lists ambiguous matches on first Tab; optional bash-completion |
| fzf (if installed) | Ctrl-R fuzzy history, Ctrl-T files, Alt-C directories; modern macOS: bat/lsd previews + fif |
| Shared history | New tabs already know commands from other sessions via history.all |
check_tool_versions |
Daily status for bash/git/gh/… plus install hints for missing fzf / bash-completion |
init_files_doctor |
One-shot deploy sanity (symlink, tools, pipx, GitHub transport) |
check_tool_versions # includes bash on a current report
init_files_doctor # OK/WARN/FAIL summary
shopt -p globstar autocd # expect -s on bash ≥ 4Optional shell UX: modern macOS Homebrew bash + bash-completion@2 are required (offered by ./provision_init_files); fzf/bat/lsd/ripgrep are offered as a local (no-admin) install on every tier, falling back to brew install fzf bat lsd ripgrep (macOS) or sudo apt install fzf bat lsd ripgrep (Linux, or dnf) only if declined/failed. Then ./provision_init_files + new shell. Enables fzf previews (bat/lsd) and fif (rg→fzf). prompt_fancy installs starship locally (~/.local/bin) when missing, falling back to this OS's package manager only if that fails.
Do this once per host (or after wiping the clone / tools file). Prefer gh auth login (HTTPS); use --key-from to copy a preferred SSH key for host hops without forcing GitHub SSH; use --github-ssh only when HTTPS is unavailable.
Shared SSH materials + user policy stay in a private config overlay (~/.local/share/config; git URL prompted / INIT_FILES_CONFIG_REPO, remembered in ~/.config/init-files/config-repo). Generic init-files is prepared for a public repo so bootstrap works via curl without auth for the dotfiles themselves.
Exact steps on the new host:
# 1) Download (preferred over curl|bash — clearer errors)
curl -fsSL https://raw.githubusercontent.com/thehcma/init-files/main/bootstrap_host \
-o /tmp/bootstrap_host
chmod +x /tmp/bootstrap_host
# 2) Run — interactive chooser defaults to gh auth (HTTPS)
/tmp/bootstrap_host
# minimal: /tmp/bootstrap_host --no-dev
# preferred SSH key only (GitHub stays HTTPS if gh logged in): /tmp/bootstrap_host --key-from HOST
# force GitHub SSH: /tmp/bootstrap_host --github-sshInteractive prompts (when no transport flag / remembered preference):
- gh auth login (HTTPS) — recommended; may offer
brew install ghon modern macOS, then runsgh auth login - SSH — copy preferred key from a donor host (prompts for
HOST, e.g.user@other-host) — also selects GitHub SSH; if GitHub rejects RSA and a personal ed25519 GitHub key is missing, bootstrap falls back to HTTPS whenghcan authenticate - SSH — key already on this machine
--key-from HOST alone only fetches the preferred SSH key; GitHub transport still follows gh auth / flags / remembered prefs (HTTPS preferred).
When shared SSH materials are desired, bootstrap/provision may also prompt for your private config overlay git URL (remembered; never hardcoded in this repo). Example: https://github.com/OWNER/private-config.git. For GitHub HTTPS overlays, the clone path checks gh auth status, offers gh auth login when needed (2FA/SSO), wires gh auth git-credential, and surfaces clone errors — it does not fall back to username/password prompts.
3) Only after the === bootstrap_host verify === block shows bashrc: … OK:
source ~/.bashrcsource ~/.bashrc before a successful verify does nothing useful (no symlink yet).
Confirm:
ls -l ~/.bashrc # -> …/init-files/bashrc
git -C ~/.local/share/init-files rev-parse --short HEAD
type refresh_init_filesIf a previous attempt already left an SSH key on this host and you want GitHub SSH:
/tmp/bootstrap_host --github-ssh # or choose option 3
# after verify OK:
source ~/.bashrcWhen you change bootstrap_host here, update the public raw URL host so curl bootstrap stays current.
scp ~/.local/share/init-files/bootstrap_host newhost:/tmp/
# on newhost:
chmod +x /tmp/bootstrap_host
/tmp/bootstrap_host # chooser: prefer gh auth
# or: /tmp/bootstrap_host --key-from HOST
# after verify OK:
source ~/.bashrc# modern macOS: brew install gh # if needed
gh auth login
git clone https://github.com/thehcma/init-files.git ~/.local/share/init-files
~/.local/share/init-files/provision_init_files --github-https
source ~/.bashrc# On an already-working host (example):
# scp ~/.ssh/<preferred-or-github-key>{,.pub} newhost:~/.ssh/
# On the new host:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/<private-key>
chmod 644 ~/.ssh/<private-key>.pubBootstrap git to rewrite HTTPS GitHub URLs to SSH:
git config --global url."git@github.com:".insteadOf "https://github.com/"Add GitHub’s host key and a Host github.com block (User git, IdentityFiles for keys that exist). After the clone exists, ./provision_init_files does this from the private config overlay when present; for the first clone you can either:
- run the two commands above, trust github.com on first connect, and ensure
~/.ssh/confighas:
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_github
IdentityFile ~/.ssh/<preferred-key>
IdentitiesOnly yes- or copy overlay
config.githubfrom another host’s~/.local/share/config/.ssh/into~/.ssh/config.d/withInclude ~/.ssh/config.d/*.confenabled.
Unlock the key and verify:
# Prefer the IdentityFile that works on this host (ed25519 often needed on OpenSSH 8 / FIPS):
ssh-add -t 4h ~/.ssh/id_ed25519_github 2>/dev/null \
|| ssh-add -t 4h ~/.ssh/<preferred-key>
ssh -T git@github.com
# success looks like: Hi <user>! You've successfully authenticated...git clone https://github.com/thehcma/init-files.git ~/.local/share/init-files
# equivalent over SSH once insteadOf is set:
# git clone git@github.com:thehcma/init-files.git ~/.local/share/init-filesRequires git and network access. For SSH mode, also the GitHub SSH key from step 0b. Prefer HTTPS + gh auth when available (step 0a / bootstrap_host chooser).
~/.local/share/init-files/provision_init_filesFlags (also accepted by bootstrap_host and forwarded to provision_init_files):
| Flag | Meaning |
|---|---|
-f / --force |
Install even if required tools are missing |
-q / --quiet |
Less status output |
--no-dev |
Do not require development tools (git, python3, make, patch, gdb, colordiff); still record them when present. Core tools (ssh*, curl, vim, gpg, cmp) stay required. Persists this host as non-dev. |
--dev |
Require development tools again; clears the saved non-dev preference. |
--github-https |
Use HTTPS for GitHub git (clear https→ssh insteadOf). Persists for this host (github-https.<hostname>). Also auto-selected when gh is authenticated and SSH was not remembered. Same flag on refresh_init_files. See Where the flags are available. |
--github-ssh |
Use SSH via insteadOf. Persists as github-ssh.<hostname> so gh auth does not flip the host back to HTTPS. Same flag on refresh_init_files. |
--no-iterm |
On macOS, skip merging curated iTerm2 prefs (default: merge when not -q). Same flag on refresh_init_files. |
--iterm |
Merge curated iTerm2 prefs on macOS (default when not -q; kept for compatibility). |
-h / --help |
Usage |
If neither --no-dev nor --dev is passed, provision_init_files honors a previously saved non-dev preference (so re-running plain provision_init_files on a minimal host stays non-dev). Same for --github-https / --github-ssh and the saved GitHub transport preference. When no transport flag is remembered, gh auth status succeeding prefers HTTPS.
What provision_init_files does, in order:
- Detects the OS tier (modern macOS / older macOS / Linux).
- Resolves absolute paths for tools bashrc calls (
gpg,vim,git, OpenSSH,curl,python3, …). - Prints OK / missing lines (with install hints). On modern macOS with a TTY (not
-q/--force), offers Homebrew bootstrap if needed, then required brew packages, then optional brew packages (two Y/n prompts); rediscovers after installs. - Required gaps still abort unless
--force(or the tool is optional under--no-dev). - Writes
~/.config/init-files/tools.<hostname>(shell assignments, not exported; includesinit_files_tools_revision). - Symlinks
~/.bashrc→~/.local/share/init-files/bashrc.- If
~/.bashrcwas a regular file, it is backed up once as~/.bashrc.bak.<timestamp>. - If it already points at the clone, install is a no-op for the link.
- If
- Merges shared SSH materials from private config (
~/.local/share/config/.ssh/). - Installs GitHub SSH snippet from config
.ssh/config.github, ensuresgithub.comknown_hosts, and applies this host’s GitHub transport: SSH hosts get
git config --global url."git@github.com:".insteadOf "https://github.com/"; HTTPS hosts clear that rewrite (warns if the private key is missing only in SSH mode). - Persists or clears
no-dev.<hostname>,github-https.<hostname>, andgithub-ssh.<hostname>according to flags / saved preference / gh auth. - Ensures
~/.profile/~/.bash_profilesources~/.bashrc(login shells). - On macOS (not
-q, not--no-iterm): merges curated terminal prefs — iTerm2 (iterm2/install), Terminal.app font (terminal/install), and Mos per-app scroll overrides (mos/install). - Not
-q: links each personal CLI's own shell completion (blumkin, …) into~/.local/share/bash-completion/completions/so bash-completion lazy-loads it. Nothing vendored — runlink_shell_completionsto refresh on demand; a daily drift check nudges when a tool is newer than its linked script. - On modern macOS (not
-q): if login shell is not the preferred Homebrew Cellar bash, prints/etc/shells+chshsteps.
Full (default): development tools are required. Missing git / python3 / make / … aborts install (unless -f).
Non-dev (--no-dev): those development tools are optional — missing ones do not abort, but are still recorded when present. Core tools (ssh*, curl, vim, gpg, cmp) stay required.
Preference is per hostname, stored as ~/.config/init-files/no-dev.<hostname> (so NFS-shared homes can mix modes — e.g. one host --no-dev, another full). Plain provision_init_files / refresh_init_files with no mode flag keep that host’s mode. Use --dev on that host to clear it.
Legacy unscoped ~/.config/init-files/no-dev or ~/.local/state/init-files/no-dev is migrated onto the current hostname on first use, then removed so it does not affect other NFS clients.
| Goal | Command |
|---|---|
| First setup on a minimal host | ./provision_init_files --no-dev then source ~/.bashrc |
| Switch full install → non-dev | refresh_init_files --no-dev then source ~/.bashrc |
| Switch non-dev → full install | refresh_init_files --dev (or provision_init_files --dev) then source ~/.bashrc |
| Stay non-dev while pulling updates | plain refresh_init_files / provision_init_files (remembered mode) |
# Minimal host (first time, after clone):
~/.local/share/init-files/provision_init_files --no-dev
source ~/.bashrc
# Existing full install → non-dev box:
refresh_init_files --no-dev
source ~/.bashrc
# Non-dev → require the full toolchain again:
refresh_init_files --dev
source ~/.bashrcConfirm mode:
# macOS: ComputerName (same as PS1). Linux: short hostname.
ls -l ~/.config/init-files/no-dev."${init_files_host:-$(scutil --get ComputerName 2>/dev/null || hostname -s)}"
# exists ⇒ this host is non-dev
# or after provision_init_files: look for "Mode: --no-dev" / "Mode: full install" in the outputWithout git, refresh_init_files cannot pull updates until git is available; re-run provision_init_files (with --no-dev if that is still the intent) once git exists.
source ~/.bashrcThat one source is only for the current session (install cannot change an already-running shell). New terminals / SSH logins should load automatically.
On Debian/Ubuntu (and other Linux login shells), bash reads ~/.profile (or ~/.bash_profile if present), not ~/.bashrc, unless those files source it. ./provision_init_files appends an init-files hook so login shells load ~/.bashrc (bashrc is idempotent if sourced twice). If a custom ~/.bash_profile omitted that, re-run provision_init_files after this fix.
refresh_init_files reloads ~/.bashrc in the current interactive shell when it updates the clone or re-runs install — you should not need a manual source after refresh. A one-time source ~/.bashrc is still needed after a bare ./provision_init_files in an already-running shell.
ls -l ~/.bashrc
# … -> …/init-files/bashrc
echo "$init_tool_git"
type refresh_init_filesOptional: check_tool_versions (runs automatically in interactive shells on full installs) should match this host’s OS tier — no brew install / brew upgrade hints on older macOS. Skipped entirely when ~/.config/init-files/no-dev.<hostname> is present.
Use this whenever you want the latest main (bashrc / provision / rules). Full refresh provisions when the clone HEAD is new for this host (or deploy drifted); use -f to always re-run provision_init_files.
refresh_init_config # preview private overlay changes, confirm, pull + provision
refresh_init_files # pull; provision if needed; reload this shell
refresh_init_files -f # pull + always provision + reload
refresh_init_files -q # daily: offer pull if main / private config moved; repair deploy drift
refresh_init_files --no-dev # pull, then provision --no-dev (persist non-dev mode)
refresh_init_files --dev # pull, then full provision (clear non-dev mode)
refresh_init_files --github-https # pull, remember HTTPS GitHub for this host
refresh_init_files --github-ssh # pull, remember SSH (insteadOf) for this host
refresh_init_files --no-iterm # skip curated iTerm2 prefs merge (macOS default: apply when provisioning)What refresh_init_files does (default):
- Clones
init_files_repointoinit_files_dirif the clone is missing. - Otherwise
git fetch origin main, then ff-only merge (falls back toreset --hard origin/main). - Ensures
~/.bashrcis still a symlink to$init_files_dir/bashrc(migrates leftover copies from the old copy-based install). - Updates the daily-check stamp under
~/.local/state/init-files/. - Prints
updated … <old> → <new>(short SHAs) when the clone moved, oralready currentwith the HEAD short SHA. - Runs
./provision_init_filesunless this host’slast-provisioned.<hostname>already matches HEAD and deploy has not drifted (-f/--dev/--no-dev/--github-*/--iterm/--no-itermalways provision). On success, records that HEAD in the stamp. - On macOS when provisioning (not
-q): runsrefresh_iterm_settingsby default (--no-itermto skip). - Reloads
~/.bashrcin the current interactive shell when HEAD or the sourced revision changed. - Applies remembered GitHub transport (clears or sets
insteadOf) before fetch.
On modern macOS, interactive ./provision_init_files (including when started from refresh) may ask:
- Install Homebrew? (only if
brewis missing) - Install N required Homebrew packages?
[Y/n]— formulae and casks installed separately, one package at a time; rediscovers before optional - Install N optional Homebrew packages?
[Y/n]— same; a single failure does not abort the rest. (Meslo Nerd Font is not a brew cask here — see below.) - Missing Meslo for this user? Offer
iterm2/install_meslo_nerd_font(Meslo.zip → ~/Library/Fonts, no sudo)
If this account is not a macOS admin (common on MDM-managed Macs), provision auto-detects that and does not run brew installs or print install: brew … lines. It emits one forwardable admin handoff block (host + user identity, Homebrew installer if needed, exact brew install … lines). Send that block to IT / an admin; after they finish on this Mac, re-run ./provision_init_files as yourself. Admin accounts only see the interactive brew prompts / direct install hints. Meslo fonts and nvm Node still install without an admin (user-local).
Skipped under -q / --force / non-TTY / older macOS / Linux for brew offers (non-admin copy-paste still prints on a TTY when brew packages are missing). Default refresh_init_files / refresh_iterm_settings / iterm2/install offer install_meslo_nerd_font when the curated profile font is missing.
Interactive shells, about once per day (init_files_max_age_seconds / tool_version_max_age_seconds, default 86400):
| Check | Behavior |
|---|---|
| Tool versions | Reprint cached diagnostic every shell (with color); rebuild at most once/day, or sooner when the background latest-* cache updates. No [N]+ Done job noise. Skipped on --no-dev hosts. |
init-files main |
git ls-remote vs local HEAD; if behind, prompt Update now? [Y/n] (TTY) or print Run: refresh_init_files. |
| Private config overlay | Same for ~/.local/share/config origin/main (plus remembered-URL drift); if behind, use refresh_init_config to preview changes, confirm, pull, and provision. |
| Local deploy drift | Compare this host’s deployables to the clone: ~/.bashrc / ~/.vimrc symlinks, retired ~/.gvimrc, login-profile bashrc hook, broken tools.<hostname> paths, and (macOS) curated iTerm prefs vs iterm2/com.googlecode.iterm2.plist. If anything differs, prompt Repair now with …? [Y/n] (TTY) or print Run: …. Narrow fixes use refresh_vimrc / refresh_iterm_settings; otherwise refresh_init_files. Never auto-applies under -q. The once/day throttle is bypassed (this check runs immediately, even mid-day) whenever the clone's checked-out HEAD has moved since the stamp was last written — e.g. a commit landed directly in this clone (it doubles as the live deploy source) — so a same-day local change to iTerm/vim/tools-affecting files is not silently deferred to tomorrow's stamp. |
| Remote check failure | If ls-remote fails (offline/auth/network), print a flag + hint (cache_ssh or gh auth); on a TTY offer Retry … remote check now? [Y/n] (may run cache_ssh on SSH hosts). Still runs the private-config and deploy-drift checks even when the init-files probe fails. Non-TTY prints a later hint. On GitHub HTTPS hosts, if a different gh auth login account previously succeeded for this remote (init-files / private-config) than the one currently active, offers gh auth switch --hostname github.com --user <that account> (identity switch, not a token) before retrying — see below. |
If you're logged into more than one GitHub account with gh (gh auth status lists several), only one is "active" per host and used for all HTTPS git operations (via the gh auth git-credential helper). A remote check can fail with "could not reach … origin/main (offline, auth, or network)" simply because the wrong account is active for that particular repo (e.g. a personal account active while the private config overlay needs a work/org account).
To fix this without ever touching tokens directly:
- Whenever a remote check or fetch succeeds over HTTPS, the currently-active
ghaccount is recorded per remote label at~/.local/state/init-files/gh-account.<label>.<hostname>(init-filesorprivate-config). - On a later auth failure, if the recorded account differs from the currently-active one and is still logged in (
gh auth status), you're offeredSwitch gh to '<account>' for this host? [Y/n]— accepting runsgh auth switch --hostname github.com --user <account>(switches the whole host's active gh identity, same one-active-account model_init_files_ensure_github_https_credsalready relies on) and then retries. - Non-interactively (no TTY), the exact command is printed instead of run automatically.
This targets "right repo, wrong active gh identity" — it does not attempt every logged-in account blindly, and it never generates, stores, or exports a token (GH_TOKEN/GITHUB_TOKEN); gh auth switch only changes which already-authenticated identity is active.
The copilot bash function is a thin wrapper around gh copilot (the installed Copilot CLI). On a host where gh knows about more than one github.com account — including one gh auth status flags as invalid — running copilot from an interactive shell lists every known account (flagging (active) / (gh flags this account)) and asks which one this session should use, separately from the gh auth switch flow above. Picking a non-active, valid account scopes just that one copilot invocation via GH_TOKEN="$(gh auth token --user <account>)"; it never runs gh auth switch and never changes the host's active gh identity.
gh auth status's "invalid" verdict conflates a genuinely revoked/expired token with one that is merely rate-limited (both print the same "Failed to log in ... invalid keyring token" text). Picking a flagged account first probes its real token directly against /rate_limit (free — this endpoint never itself costs API quota): if the token actually works, it's used as-is with a note that gh's warning was a false alarm; if it's rate-limited, you're told so (with the reset time) and the active account is used instead — no reauth offered, since nothing is broken; only a genuine 401 gets the gh auth refresh -h github.com -u <account> offer. Non-interactive shells, and hosts with 0–1 known accounts, skip the prompt and use the active account as-is.
| Variable | Default / role |
|---|---|
init_files_repo |
Override clone URL (default points at this repo’s GitHub remote) |
init_files_dir |
~/.local/share/init-files |
init_files_max_age_seconds |
86400 (1 day) |
init_files_no_dev_flag |
~/.config/init-files/no-dev.<hostname> (presence = this host is non-dev) |
init_files_github_https_flag |
~/.config/init-files/github-https.<hostname> (presence = GitHub HTTPS on this host) |
init_files_github_ssh_flag |
~/.config/init-files/github-ssh.<hostname> (presence = force SSH despite gh auth) |
INIT_FILES_DEFAULT_USERS |
Space-separated quiet-prompt allowlist. Default: from ~/.local/share/config/init-files/default-users.env when config clone present; otherwise unset (no alt-user badge). |
Private bashrc.local |
Optional personal aliases/helpers from ~/.local/share/config/init-files/bashrc.local (sourced after public aliases). |
INIT_FILES_DEFAULT_USER |
Legacy singular: merged into the allowlist (does not replace). Prefer INIT_FILES_DEFAULT_USERS for a full override. |
INIT_FILES_ALT_USER |
Set by bashrc when $USER is not allowlisted (starship reads this; do not set by hand) |
INIT_FILES_SKIP_TOOL_CHECK=1 |
Emergency: skip check_tool_versions on interactive load (e.g. hung package-manager probe) |
INIT_FILES_SKIP_DAILY_REFRESH=1 |
Emergency: skip daily refresh_init_files -q on interactive load |
INIT_FILES_SKIP_ORPHAN_CLEANUP_OFFER=1 |
Emergency: skip weekly leftover prefs/pipx cleanup offer |
INIT_FILES_SKIP_FANCY_PROMPT_OFFER=1 |
Emergency: skip weekly prompt_fancy offer when starship is present but fancy is off |
refresh_init_files provisions after pull when needed (new HEAD for this host, deploy drift, or -f / mode / transport / iterm flags). Use bare ./provision_init_files when you only need to rewrite tool paths / ssh / vim without a git pull (e.g. right after a Homebrew move on an already-current clone).
| Situation | Command |
|---|---|
| First setup on a host | clone + provision_init_files (+ optional --no-dev / --github-https) + source ~/.bashrc |
| Minimal host (no git/python/make/…) | provision_init_files --no-dev or refresh_init_files --no-dev |
| Switch full install → non-dev | refresh_init_files --no-dev (persists) |
| Switch non-dev → full install | refresh_init_files --dev or provision_init_files --dev |
| Prefer GitHub HTTPS on this host | provision_init_files --github-https or refresh_init_files --github-https |
| Prefer GitHub SSH on this host | provision_init_files --github-ssh or refresh_init_files --github-ssh |
New commits on main (bashrc / docs / rules) |
refresh_init_files (provisions + reloads current shell) |
| Moved / upgraded tools (new git, gpg, python, brew Cellar bump, …) | provision_init_files or refresh_init_files |
| OS upgrade that changes the macOS tier (e.g. into Darwin 25+) | provision_init_files or refresh_init_files, then validate hints |
~/.bashrc accidentally replaced by a regular file |
provision_init_files or refresh_init_files (both repair the symlink) |
| Merge curated iTerm2 prefs (macOS) | default refresh_init_files / provision_init_files; skip with --no-iterm; or refresh_iterm_settings |
Sanity checks: init_files_doctor and check_tool_versions warn when recorded init_tool_* paths are missing.
Hosts that still have a regular-file ~/.bashrc (pre-symlink install) should:
- Ensure GitHub
mainhas the symlink-eraprovision_init_files/bashrc(push from the authoring machine first). - Update the clone:
git -C ~/.local/share/init-files fetch origin main && git -C ~/.local/share/init-files reset --hard origin/main(or clone if missing). - Run
~/.local/share/init-files/provision_init_filesthensource ~/.bashrc. - Confirm
ls -l ~/.bashrcshows a symlink into the clone.
Agents: follow the fuller checklist in AGENTS.md (“Migrate an existing host”).
- Rocky Linux 8.1: skips development tool version checks and omits the git commit id from the prompt.
- macOS 26+ (Darwin 25+): Homebrew is supported (GNU userland via
*/libexec/gnubin, brew tool paths inprovision_init_files). Install withbrew install coreutils gnu-sed grep(and optionallyfindutils gawk gnu-tar). - Older macOS: system/BSD userland only — Homebrew paths are not used or recommended. Brew is often infeasible here (unsupported formulae, multi-hour from-source dependency builds); prefer Xcode CLT, MacGPG2, app bundles, or
~/.localGitHub releases. - macOS: MacVim remote tabs, volume helpers, ChromeCast/Globo aliases,
cache_sshwithout Keychain auto-unlock. Linux-only helpers are not defined. - Linux: ssh-agent via
~/.ssh/environment, VNC server helpers, terminator/kwin aliases. macOS-only helpers are not defined.
See .cursor/rules/platform-isolation.mdc and AGENTS.md for agent guidance when changing bashrc / install. Interactive shell operator docs: docs/shell-ux.md.
User-specific SSH materials and policy live in a private config overlay (not in this public repo):
| Path | Role |
|---|---|
~/.local/share/config |
Git clone of your private overlay (INIT_FILES_CONFIG_REPO / remembered URL; prompted with an example when missing) |
Interactive clone of a GitHub HTTPS overlay URL uses gh auth (checks status, offers gh auth login for 2FA/SSO, configures gh auth git-credential) and prints git’s stderr on failure — never username/password prompts.
| ~/.local/share/config/.ssh/ | Templates provision installs into ~/.ssh |
| ~/.ssh/ | Live OpenSSH home: private keys, authorized_keys, config, known_hosts |
| Overlay file | Installed to / effect |
|---|---|
authorized_keys.shared |
Merged into ~/.ssh/authorized_keys (adds lines; does not remove others) |
config.hosts |
~/.ssh/config.d/init-files-hosts.conf |
config.github |
Parsed for IdentityFile order → ~/.ssh/config.d/init-files-github.conf (only keys that exist on this host) |
Legacy names authorized_keys.house / config.house / init-files-house.conf are still read for one release; prefer the canonical names above.
Provision also ensures Include ~/.ssh/config.d/*.conf at the top of ~/.ssh/config.
- Private keys stay on each host under
~/.ssh/(never commit them to init-files or the overlay). - Public keys you want every host to accept go in overlay
authorized_keys.shared. - Host aliases / per-host
IdentityFilepaths go in overlayconfig.hosts. - GitHub SSH IdentityFile preference order goes in overlay
config.github.
Prefer passphrase-protected keys. Load with cache_ssh before scripted / BatchMode hops. For interactive logins after the agent lifetime expires, use cssh (ssh), cmsh (mosh — sleep/IP roaming; needs mosh-server), or cesh (et / Eternal Terminal — reconnectable like mosh but a normal pty so CSI-u / Shift+Enter work; needs etserver on the remote). Each resolves the destination through ssh -G and caches that host's effective IdentityFile; SSH_CACHE_KEY remains an explicit global override. With no arguments, all three fuzzy-pick from SSH config + cleartext known_hosts via fzf when available. None replace the underlying binaries.
- On each host that should use it, place the key under
~/.ssh/:
ssh-keygen -t ed25519 -f ~/.ssh/<name> -C 'you@example'
chmod 600 ~/.ssh/<name>
chmod 644 ~/.ssh/<name>.pubOr copy an existing key pair onto the host (scp, or bootstrap_host --key-from HOST).
- Optional — if other hosts should accept this key for login, append the public line to the overlay:
# edit in the private overlay repo:
# ~/.local/share/config/.ssh/authorized_keys.shared-
Optional — for host aliases, add a
Hostblock in overlayconfig.hostswithIdentityFile ~/.ssh/<name>(andIdentitiesOnly yeswhen you want only that key). -
For GitHub SSH, add an
IdentityFileline to overlayconfig.github(order = preference). Missing files are omitted when provision writesinit-files-github.conf. -
Never commit private key material to the overlay repo.
-
Push the overlay; on each host:
refresh_init_configOverrides: INIT_FILES_SSH_KEY (absolute path), INIT_FILES_SSH_KEY_BASENAME (for --key-from), remembered ~/.config/init-files/ssh-key-basename. Legacy INIT_FILES_HOUSE_KEY* still work.
| Trigger | Behavior |
|---|---|
./provision_init_files |
Always merges/installs overlay SSH materials when present |
refresh_init_config |
Fetches and previews incoming commits/files, confirms, fast-forwards the overlay (or initializes a clean clone with an unborn HEAD from origin/main), provisions it, and reloads the current shell |
refresh_init_files (full) |
Provisions after pull when HEAD is new / drifted / forced; records last-provisioned.<hostname> |
refresh_init_files -q (daily) |
When the private overlay moved, invokes the same refresh_init_config preview/confirm/update flow |
bootstrap_host |
May prompt for overlay git URL, clone it, then provision |
| Surface | How |
|---|---|
| On disk (NFS-safe, per host) | ~/.config/init-files/github-https.<hostname> — HTTPS preferred |
~/.config/init-files/github-ssh.<hostname> — SSH opt-out (wins over gh auto-HTTPS) |
|
bootstrap_host |
Interactive chooser (default: gh auth login); --github-https / --github-ssh; --key-from HOST fetches preferred SSH key only (does not force GitHub SSH) |
./provision_init_files |
--github-https / --github-ssh (see install flags) |
refresh_init_files |
--github-https / --github-ssh (same persistence; re-runs provision_init_files with the flag) |
| Auto (no flag file yet) | If gh auth status succeeds and github-ssh.<hostname> is absent → write github-https.<hostname> and use HTTPS |
<hostname> is the same scope key as no-dev.<hostname> / PS1 (macOS ComputerName, Linux short hostname). See On-disk layout.
Confirm on this host:
ls -l ~/.config/init-files/github-{https,ssh}."${init_files_host:-$(scutil --get ComputerName 2>/dev/null || hostname -s)}"
git config --global --get url.git@github.com:.insteadof # empty ⇒ HTTPS; https://github.com/ ⇒ SSH rewrite
git config --global --get-regexp 'credential\.https://github.com' # HTTPS: !gh auth git-credentialAuto HTTPS: if gh auth status succeeds and this host has no github-ssh.<hostname> opt-out, install/refresh prefer HTTPS (clear insteadOf, point credential.https://github.com.helper at gh auth git-credential) and remember github-https.<hostname>.
SSH (when gh is not logged in, or with --github-ssh): a key registered on GitHub + insteadOf so documented https://github.com/… remotes speak SSH. Prefer gh auth login / HTTPS when possible. New hosts without gh:
- Copy the private key onto the host (never commit it).
git config --global url."git@github.com:".insteadOf "https://github.com/"Host github.comwithUser git+ IdentityFiles from private overlayconfig.github(applied by./provision_init_fileswhen the overlay is present).cache_sshthenssh -T git@github.com.
Then git clone https://github.com/thehcma/init-files.git … and refresh_init_files use SSH under the hood.
Force HTTPS (also happens automatically when gh is logged in, and is the interactive bootstrap_host default):
install --github-https # or: refresh_init_files --github-https
# remembers ~/.config/init-files/github-https.<hostname>
# clears the https→ssh insteadOf rewrite on this machineForce SSH despite gh auth:
install --github-ssh # or: refresh_init_files --github-ssh
# remembers ~/.config/init-files/github-ssh.<hostname>Plain later provision_init_files / refresh_init_files keep remembered prefs (or re-detect gh when neither flag exists).
Note: preference flags are NFS-safe per hostname; the insteadOf setting lives in shared ~/.gitconfig, so the last host to apply transport wins on NFS-shared homes.
Shared homes correctly keep per-host tools.* / no-dev.* / github-*.* / pipx/<host>/. Over time, retired names leave leftovers (ComputerName renames, legacy unscoped tools).
Each interactive shell registers this machine’s primary MAC → hostname under ~/.config/init-files/host-mac/<mac>. That registry is the keep set for NFS peers — other live hosts are never treated as stale just because their hostname differs. When a MAC’s hostname changes, the previous name is appended to host-mac-retired and becomes eligible for cleanup (unless another MAC still claims it).
| Keep | Safe to prune (after confirming) |
|---|---|
*.<hostname> for every MAC still registered under host-mac/ |
Prefs / pipx for names in host-mac-retired with no live MAC claim |
~/.config/init-files/nfs-hosts / --keep (optional extras) |
Legacy unscoped tools once tools.<host> exists |
init_files_doctor # deploy sanity (symlink, tools, pipx wrapper, …)
init_files_cleanup_orphans # list MAC-retired leftovers + legacy tools (dry run)
init_files_cleanup_orphans --apply
# Rare: also list host keys that never registered a MAC (not offered weekly):
init_files_cleanup_orphans --include-unregisteredInteractive shells offer init_files_cleanup_orphans --apply about once a week when retired leftovers are present (stamp: ~/.local/state/init-files/last-orphan-cleanup-offer). Emergency skip: INIT_FILES_SKIP_ORPHAN_CLEANUP_OFFER=1.
provision_init_files / interactive bashrc migrate legacy pipx dir names and Bonjour-scoped prefs onto the canonical host key; they never delete foreign hosts’ state. Cleanup is always explicit (--apply) or confirmed at the weekly prompt.
./provision_init_files also:
- Merges overlay
authorized_keys.sharedinto~/.ssh/authorized_keys(does not remove other keys). - Installs overlay
config.hosts→~/.ssh/config.d/init-files-hosts.conf. - Installs GitHub snippet from overlay
config.github→~/.ssh/config.d/init-files-github.conf. - Ensures
Include ~/.ssh/config.d/*.confis at the top of~/.ssh/config. - Sets or clears the GitHub
insteadOfrewrite according to this host’s preference.
After changing shared/GitHub SSH materials in the private overlay: pull the overlay, then on each host refresh_init_files / ./provision_init_files.