A Rust-powered dotfiles bootstrapper for a personal developer environment with first-class support for macOS and Ubuntu.
| Layer | Tool | Details |
|---|---|---|
| Bootstrapper | dotup |
Rust CLI for install, dry-run, doctor, and sync workflows |
| Shell | Zsh + Oh-My-Zsh + Starship | Shared shell config with optional local overrides |
| Editor | Neovim (from scratch) | nvim/ in this repo, symlinked to ~/.config/nvim; built-in LSP, built-in completion, vim.pack for 5 plugins |
| Multiplexer | Tmux and Zellij | Both installed and configured; pick per session. Plugin-free on both sides |
| Terminal | WezTerm | Personal defaults with documented font expectations |
| Package management | Homebrew on macOS, APT on Ubuntu | Other Linux package managers remain best-effort |
| IDE integration | IdeaVim | JetBrains Vim motions via vim/ideavimrc.vim |
git clone https://github.com/toandaominh1997/dotfiles.git "$HOME/.dotfiles/tool"
cd "$HOME/.dotfiles/tool"
cargo run -- --autoRestart your terminal, then open nvim once — vim.pack installs its plugins
and nvim-treesitter builds parsers on first launch. Tmux needs no bootstrap.
- First-class platforms: macOS and Ubuntu
- Best-effort platforms: other Linux distributions supported through existing package-manager adapters
- GUI app installation: macOS-only
- Setup bias: personal machine defaults first, with explicit local overrides for machine-specific differences
cargo run -- --auto # full install
cargo run -- --auto --upgrade --verbose
cargo run -- --auto --dry-run # show what would change
cargo run -- --doctor # health check
cargo run -- --sync # commit + push this repo
cargo run -- --auto --profile work
cargo run -- # no flag: print help, change nothingProfiles are defined in dotup.toml:
default— personal baseline with shell, editor, tmux, and core CLI toolswork— adds common work tooling such as AWS CLI, Docker, Kubernetes, Node, Rust, and Terraformminimal— smaller shell and terminal setup
dotup orchestrates setup in this order:
- Initialize the platform package manager
- Install required packages from the selected profile
- Install optional packages from the selected profile
- Install macOS casks when running on macOS
- Install fonts
- Configure zsh
- Configure tmux
- Configure Vim and Neovim
Existing configs are backed up with a timestamp before being overwritten.
The generated ~/.zshrc sources ~/.dotfiles/tool/zsh/config.zsh.
Highlights:
- Starship prompt
- Oh-My-Zsh with plugin-based completion and UX improvements
- macOS Homebrew shellenv bootstrapping
- optional machine-local additions via
~/.zshrc.local
Update Oh-My-Zsh:
omz updatePrefix: Ctrl+b
tmux/config.tmux is ~120 lines of built-in tmux, with no plugin manager and no
helper scripts.
- clipboard via native OSC 52 (
set-clipboard on) — works locally and over SSH Ctrl+h/j/k/lmoves between panes, and passes through to Neovim when the pane is running it (vim-tmux-navigator behaviour, no plugin on either side)- vi copy mode; mouse drag, double-click and triple-click all copy
prefix + ?opens a keybinding cheatsheet popupprefix + rreloads (idempotent — re-sourcing does not duplicate options)- optional machine-local additions via
~/.tmux.conf.local
Nothing to install — the config is live as soon as tmux starts.
Two things break by default and are fixed here: Shift+Enter submits instead of inserting a newline, and desktop notifications / the progress bar never reach the outer terminal.
set -s extended-keys always # `on` is not enough — see CLAUDE.md
set -s extended-keys-format csi-u
set -as terminal-features "xterm*:extkeys"
set -g allow-passthrough all # `on` only works while the pane is visibleCtrl+J and \ + Enter insert a newline in any terminal with no config. Run
/terminal-setup in the host terminal, never inside tmux.
Installed alongside tmux, not instead of it — ~/.config/zellij/config.kdl is
a symlink to zellij/config.kdl here. Use whichever multiplexer you feel like;
they never run at the same time and own separate config files.
The config layers on zellij's defaults rather than replacing them
(no clear-defaults=true), so it stays short. Two things worth knowing:
- tmux muscle memory already works.
Ctrl benters zellij's built-in tmux mode:"and%split,zzooms,cnew tab,h/j/k/lmove focus,ddetaches,xcloses. That is a zellij built-in, not something configured here. Ctrl+his unbound. Zellij binds it globally to Move mode, which would swallow the key before Neovim — which mapsC-h/j/k/lto window navigation — could see it.Ctrl+j/k/lare not bound by zellij at all, so unbinding that one key makes all four reach Neovim. Zellij's own pane focus stays onAlt+h/j/k/l; Move mode moved toCtrl pthenm.
Clipboard is OSC 52 (no copy_command), same reasoning as everywhere else here.
Zellij is a best-effort package, not a required one: it is not in Ubuntu's
apt repositories, so dotup will warn rather than fail there. On Linux install
it with cargo install --locked zellij or from a
GitHub release. tmux remains
required on every platform, which is part of why both are kept.
Unlike tmux, zellij speaks the kitty keyboard protocol natively, so Claude Code's Shift+Enter needs no configuration at all under zellij.
~/.config/nvim is a symlink to nvim/ in this repo, so editing the config
here edits the live config. Requires Neovim 0.12+.
Built on Neovim's own machinery rather than a framework:
- Plugin manager:
vim.pack(built in) — no lazy.nvim, no NvChad - LSP:
vim.lsp.config/vim.lsp.enabledirectly — no nvim-lspconfig - Completion:
vim.lsp.completionwith autotrigger — no nvim-cmp, no blink - Plugins (5): tokyonight, nvim-treesitter, gitsigns, fzf-lua, conform
fzf-lua and conform are deferred and sourced on first use.
| File | Holds |
|---|---|
nvim/init.lua |
entry point, provider + loader settings |
nvim/lua/options.lua |
editor options |
nvim/lua/plugins.lua |
vim.pack specs, treesitter, deferred loaders |
nvim/lua/lsp.lua |
server definitions, diagnostics, native completion |
nvim/lua/keymaps.lua |
keymaps |
nvim/nvim-pack-lock.json |
plugin revisions (committed) |
Language servers are enabled only when their binary is on PATH:
basedpyright and ruff for Python, lua_ls for Lua.
:lua vim.pack.update() " update plugins
:lua vim.pack.get() " list installed
:checkhealthRun the config's test suite:
nvim --headless -c 'luafile tests/nvim_config_test.lua'
bash tests/tmux_config_test.sh
bash tests/zellij_config_test.shConfig lives at wezterm/wezterm.lua.
- Preferred font:
AestheticIosevka Nerd Font Mono - color scheme: Aesthetic Night
- pane and tab bindings are tuned to match tmux and editor navigation where possible
Copy vim/ideavimrc.vim to ~/.ideavimrc for JetBrains IDEs.
.
├── src/ # dotup Rust orchestration
├── dotup.toml # package profiles
├── zsh/ # shared zsh config
├── tmux/ # shared tmux config
├── nvim/ # neovim config (symlinked to ~/.config/nvim)
├── zellij/ # zellij config (symlinked to ~/.config/zellij)
├── vim/ # plain vim + ideavim configs
├── wezterm/ # terminal config
├── starship/ # prompt config
├── fish/ # fish shell config
├── tests/ # integration + nvim config tests
└── .github/workflows/ # CI workflows
Useful commands while iterating on the repo:
cargo test
cargo run -- --auto --dry-run --verbose
cargo run -- --doctor
bash tests/integration_test.sh
nvim --headless -c 'luafile tests/nvim_config_test.lua'
bash tests/tmux_config_test.sh
bash tests/zellij_config_test.shCI runs on macOS and Ubuntu to keep the documented support model honest.
