Dotfiles for macOS and Ubuntu Desktop 26.04 LTS. Includes config for zsh, tmux, nvim, git and more.
Platform and profile are independent: macos or ubuntu, and personal or
work. Platform is detected from the operating system unless explicitly selected.
Personal is the default profile. Both profiles use the same tools and shared
configuration; only work explicitly loads OpenShift zsh completion.
# Personal macOS
./bootstrap.sh --platform macos --profile personal --dry-run
./bootstrap.sh --platform macos --profile personal
# Work macOS
./bootstrap.sh --platform macos --profile work
# Personal Ubuntu 26.04
./bootstrap.sh --platform ubuntu --profile personal --dry-run
./bootstrap.sh --platform ubuntu --profile personalBootstrap saves validated choices under ${XDG_CONFIG_HOME:-$HOME/.config}/dotfiles
as profile and platform. These machine-local files are not committed. Subsequent
configuration deployments retain the saved profile. DOTFILES_PROFILE=work zsh
or DOTFILES_PROFILE=personal zsh overrides it for an individual terminal.
Bootstrap flags take precedence over environment variables and saved defaults.
Settings load in the order shared configuration, platform, profile, then local overrides. Do not select work mode by hostname or username.
common/
shell/ shared Bash/zsh configuration
git/ shared Git config and work-identity example
tmux/ shared tmux config
vim/ shared Vim config
nvim/ shared Neovim config and plugin lock
ghostty/ shared terminal config
platforms/
macos/ Brewfile, AeroSpace, shell and Ghostty settings
ubuntu/ APT packages, pinned tools, shell and desktop setup
profiles/
work/shell.zsh OpenShift completion only
scripts/ pinned plugins, backups, restore and package audit
tests/ offline, native integration and Docker checks
Root dotfile paths, Brewfile, nvim/.config/nvim, and ghostty/.config/ghostty
are compatibility links to the canonical files above, not separate copies.
Deployment dereferences these repository links and installs file contents with
backups. Prefer editing the canonical files when adding new configuration.
Untracked overrides can live in ~/.gitconfig.local, ~/.gitconfig.work, and
${XDG_CONFIG_HOME:-$HOME/.config}/dotfiles/local/: environment.sh, shell.sh,
and ghostty.conf. Bootstrap never copies over those local override files.
Private environment variables belong in ~/.private_env. Bootstrap creates it
from private_env.template with owner-only permissions if it does not exist.
An existing checkout-local .private_env is migrated only when the home copy is
absent; existing home credentials are never overwritten.
git clone https://github.com/TheNewThinkTank/dotfiles.git ~/Documents/projects/dotfiles
cd ~/Documents/projects/dotfiles
./bootstrap.sh --dry-run
./bootstrap.shBootstrap asks for confirmation before installing or changing anything. It:
- Uses Homebrew on macOS or APT plus verified user-local tools on Ubuntu.
- Installs missing platform packages without deleting installed packages or running cleanup. The macOS Brewfile is preserved intact.
- Installs missing Oh My Zsh, Powerlevel10k, Vundle, TPM, and tmux plugins at
verified commits from
plugins.lock.json. - Copies managed dotfiles to
~and Neovim/Ghostty configuration to${XDG_CONFIG_HOME:-$HOME/.config}, skipping AeroSpace on Ubuntu. - Saves replaced files beneath
${XDG_STATE_HOME:-$HOME/.local/state}/dotfiles/backups/and prints the backup directory. Files not managed by this repo are left alone; nothing is deleted.
Open a new terminal afterward. The script does not source zsh configuration from Bash or reload running tmux/AeroSpace sessions.
Edit files in this checkout, then deploy them with backups:
./bootstrap.sh --dry-run --skip-install
./bootstrap.sh --skip-install--dry-run prints the plan without installing packages, contacting remotes, or
writing files. --skip-install only deploys configuration. --force skips the
confirmation prompt, not backups. --update explicitly fast-forwards a clean
checkout from origin/main; bootstrap never pulls automatically.
Bootstrap copies configuration rather than creating symlinks. Existing links
directly to this checkout are preserved. Backups contain the prior home files
under home/ and app configuration under config/nvim/ and config/ghostty/.
The installed layer files and saved selectors are backed up under
config/dotfiles/ as well.
Each deployment writes an owner-only manifest.json beside its backups. It
records destination roots, checksums, file modes, platform/profile, and whether deployment
completed or failed. A failed deployment still records files already replaced.
Use the backup directory printed by bootstrap:
./bootstrap.sh --restore /path/to/backup --dry-run
./bootstrap.sh --restore /path/to/backupRestore verifies payloads, home/config roots, and destination boundaries before
showing its plan. It requires the exact confirmation restore; --force cannot
bypass this prompt. Current files are saved to a new rollback backup before
replacement. Files absent from the saved backup are not deleted, and unrelated
files and private credentials are left alone. Restore rechecks integrity and
scope after confirmation. Backups made before manifests were introduced must be
restored manually.
New shell, Vim, and tmux installations are staged in temporary sibling
directories and published only after their Git revision and entrypoint match
plugins.lock.json. Failed clones discard only their staging directory and can
be retried. Existing plugin installations and local customizations are not
silently updated or deleted; incomplete pre-existing installations stop with a
diagnostic.
To update pins explicitly, review an upstream revision and edit the corresponding
commit in plugins.lock.json, then run make integration. Deploy updates to
existing plugin checkouts deliberately after preserving local changes. Neovim
uses its separate lazy-lock.json, including a staged, verified initial Lazy
installation. Regular configuration deployment does not update dependency pins.
Homebrew paths are discovered rather than assuming Apple Silicon or Intel paths.
Ruby's gem executable directory is obtained from RubyGems, not a fixed Ruby
version. An existing KUBECONFIG is preserved. Otherwise the shared shell config
combines ~/.kube/config, *.yaml, and *.yml files when present, without empty
path entries. Missing directories or either file extension are supported in Bash
and zsh. Work loads OpenShift completion only when oc is available. Personal
does not run oc completion zsh, even if the tool is installed. System-provided
autoload completion files may still exist independently of the profile.
Ubuntu loads its native zsh highlighting/autosuggestion files from /usr/share
and prefers wl-copy for clipboard operations, with X11 alternatives available.
vim +PluginInstall +qallBootstrap provisions Vundle. Vim still loads core settings if Vundle is absent.
Located in common/nvim/. Requires Neovim 0.11.3 or newer. Lazy installs
plugins from the committed lockfile on first startup. Mason installs the
TypeScript, Ruby, HTML, and Lua servers; all four are explicitly enabled using
vim.lsp.enable. Run :checkhealth vim.lsp to inspect server status.
The Brewfile provisions StyLua, isort, Prettier, and RuboCop for formatting, while
buffer completion is provided by cmp-buffer. Prettier can also use a project's
local installation.
Vim and Neovim move lines with Shift-Alt-j/k in normal and visual mode. Alt-j/k remain AeroSpace's window-focus shortcuts. Ghostty uses the left Option key as Alt on macOS. Ghostty loads shared settings first, then the selected platform's native include and an optional local include.
platforms/ubuntu/apt-packages.txt uses explicit native package mappings rather
than translating Homebrew names: examples include fd-find, isort,
tealdeer, and wl-clipboard. Ghostty is available in Ubuntu 26.04's official
archive. Linuxbrew is not installed.
The installer uses APT without package removals, upgrades, or extra recommends.
Non-root users must authorize sudo in their own terminal with sudo -v before
setup; the script only uses noninteractive sudo. Configuration-only deployments
with --skip-install do not require privilege escalation.
If required tools are missing, checksum-pinned Linux assets provide StyLua,
OpenShift/kubectl, and Mike Farah yq, plus Neovim if APT's version is too old.
Prettier is version-pinned under ~/.local; a verified JetBrains Mono Nerd Font
is installed per user. Existing local binaries are not silently replaced.
Assets cover x86_64 and aarch64 in platforms/ubuntu/tools.lock.json.
GNOME desktop shortcuts are separate and opt-in:
ruby platforms/ubuntu/desktop.rb --dry-run
ruby platforms/ubuntu/desktop.rb
ruby platforms/ubuntu/desktop.rb --restore /path/to/settings.json --dry-run
ruby platforms/ubuntu/desktop.rb --restore /path/to/settings.jsonRun changes in your GNOME desktop session and confirm with the exact word
desktop. Prior settings are saved under the dotfiles state directory for
restoration. Workspace shortcuts use Super-h/l and Shift-Super-h/l, leaving
editor Alt bindings free. Ordinary bootstrap never changes desktop keybindings.
All plugins are installed beneath ~/.tmux/plugins: TPM, Catppuccin, tmux-cpu,
and tmux-battery. Missing plugins are skipped rather than breaking startup. The
prefix remains Ctrl-s. Use prefix-r to reload the config after deployment.
# Inspect installed entries without replacing the maintained Brewfile
brew bundle dump --file=-
# Install brew packages
brew bundle install --no-upgrade --file ./BrewfileMerge new installed entries into the Brewfile additively. Keep existing entries
unless their removal has been explicitly approved. Do not use a forced dump or
brew bundle cleanup to synchronize this file.
Run a read-only metadata audit:
make auditIt reports deprecated/disabled formulae and casks, plus Homebrew-provided replacement suggestions. It does not install, uninstall, or edit entries. Metadata lookup failures return an error rather than a false clean report.
The current audit flags deprecated Whisky and python-tk@3.11, and disabled
tldr with tlrc suggested as its replacement. They remain in the Brewfile
pending explicit approval. Disabled entries can prevent a fresh-machine package
installation even when existing installations still work.
Git identity is selected by repository location, not shell profile. Repositories
under ~/work/ or ~/Documents/work/ include ~/.gitconfig.work; other directories
keep your shared personal identity. Copy common/git/work.gitconfig.example to
that home file and fill in your work identity. Additional directory rules can be
kept in ~/.gitconfig.local. Credentials and private work settings stay outside
the repository.
# Tell git to use its built-in language parser for md files
echo '*.md diff=markdown' >> .gitattributes
# Search for changes to the README files' Usage section
git lg -L:Usage:README.mdgit undo [reflog-step] requires a clean worktree, including no untracked files,
and the exact confirmation undo. It uses git reset --keep so edits made while
confirmation is pending are not discarded.
brew install bats-core jq neovim shellcheck tmux yq
make check
# Networked checks with real pinned dependencies in temporary state
brew install stylua isort prettier rubocop
make integration
# Ubuntu 26.04 container: native package setup and both profiles
make ubuntuThe checks cover shell syntax, ShellCheck, Lua/JSON/TOML/YAML configuration, bootstrap dry runs, and isolated Bats regressions for failed-clone recovery, pin verification, backup manifests, restore confirmation and integrity, credentials, kubeconfig, Git undo, LSP activation, tmux paths, and editor shortcuts. ShellCheck's SC2016, SC2030, and SC2031 diagnostics are excluded only for Bats: child-shell expressions are deliberately single-quoted and Bats runs each test with its own status variables. Production shell scripts retain full linting.
make check is offline and does not download plugins. make integration installs
the real locked shell/Vim/tmux/Neovim dependencies into an isolated temporary home,
checks every Neovim commit, exercises actual startup, and verifies real LSP
defaults and completion providers. The integration-only Mason option disables
language-server downloads; normal Neovim startup still provisions its four
servers. Tests do not deploy live dotfiles or reset the working repository.
CI covers personal and work profiles on macOS and in an Ubuntu 26.04 container.
make ubuntu requires a running Docker daemon and mounts a read-only public-file
snapshot; private environment files and local overrides are excluded. It retains
full logs and result metadata in the temporary result directory it prints.
If your Docker network requires host-managed CA trust, explicitly run
DOTFILES_DOCKER_USE_HOST_CA=1 make ubuntu on macOS. This exports public CA
certificates verified by macOS into the disposable container only. TLS and asset
checksum verification remain enabled; host trust settings and credentials are
not changed.
Desktop session behavior still needs a real GNOME/Wayland session; container
checks validate its preview, confirmation, and restore contracts instead.