A carefully designed cross-platform dotfiles configuration that powers my development environments across macOS and Linux 🐧 systems. This repository represents years of refinement to create a consistent, modular, and reliable setup.
I run this configuration on at least 10 machines, including arm64 macOS, x86_64 and aarm64 versions of Ubuntu, Debian, DietPi, Raspberry Pi OS, NixOS, Pop!_OS, and even on my iPhone via iSH which emulates i386 Linux.
My main goal is to have consistency and a super smooth bootstrapping experience for new machines, and to have a consistent setup across all my devices.
Try out my setup in Docker without installing anything! 🐳 See this section.
Tip
I have written several blog posts about my shell setup and the tools I use. See Open Sourcing My Dotfiles: A Practical, Cross-Platform Terminal Setup 🏠, Be a Ninja in the Terminal 🥷, dotbins: Managing Binary Tools in Your Dotfiles 🧰, and Combining Keychain and 1Password CLI for SSH Agent Management 🔑 for more details.
Note
I have maintained this repository since 2019-04 but started a new commit history when I made it public in 2025-04.
Note
Nearly all code snippets in this README are auto-generated by markdown-code-runner. Therefore, the code should be up-to-date.
- Shell agnostic - Works with both
zshandbash - Cross-platform - Supports macOS and Linux
- Modular design - Organized in independent, composable configuration files
- Easy installation - Uses dotbot for automated symlink management
- Binary management - Uses dotbins for CLI tools with automatic shell integration
- Remote syncing - Includes scripts to sync dotfiles across machines
- macOS bootstrap - Uses Homebrew Bundle for repeatable Apple Silicon setup
There is a lot of stuff in this repository, but things I won't go without are:
- I clone this repository and run
./install, and everything is set up automatically! - oh-my-zsh for all of the convenient default keybindings and plugins (yes, I know it's bloated and slow)
- zsh-autosuggestions for command completion
- starship for a beautiful prompt
- dotbins for managing binaries
- dotbot for managing symlinks and installing Python tools with
uv - keychain for SSH key management
- direnv for managing environment variables (especially for Python (
uvandmicromamba)) - zoxide for jumping around directories (alternative to
zsh-z) - Keyboard Maestro for keyboard shortcuts to switch between applications
- zsh-syntax-highlighting for syntax highlighting
- Homebrew Bundle for declarative macOS package and app installation
Why not?
- I don't use
fishbecause I want to be fully compatible withbash, so therefore I usezshas my main shell. - Why not X? I go over my design goals and decisions in this blog post.
First, you need to set up SSH authentication for the repository and its SSH-based submodules.
Using 1Password
Install 1Password and set up the SSH agent:
export SSH_AUTH_SOCK=~/Library/Group\ Containers/2BUA8C4S2C.com.1password/t/agent.sock# Clone the repository
git clone git@github.com:basnijholt/dotfiles.git
cd dotfiles
git submodule update --init --recursive --remote --jobs 8
# Run the installation script
./installClone and install from the WSL filesystem, not /mnt/c. The installer refuses
Windows-mounted checkouts so WSL home-directory links never target the Windows
drive.
git clone --recurse-submodules git@github.com:basnijholt/dotfiles.git ~/git/dotfiles
cd ~/git/dotfiles
./installNote
Check out how minimal the Dockerfile really is, it only requires a barebones Ubuntu image and Git!
If you want to quickly try out this shell environment without installing it on your main system, you can use the provided Dockerfile:
# Build the Docker image
docker build -t dotfiles-env .
# Run the container and drop into the configured shell
docker run -it --rm dotfiles-envThis will give you an interactive Zsh session within an Ubuntu container, configured using these dotfiles.
# Sync dotfiles to all configured remote hosts
./scripts/sync-dotfiles.sh
# Or install new configuration on remotes
./scripts/sync-dotfiles.sh install.
├── configs # Configuration files for various tools
│ ├── git # Git configuration
│ ├── atuin # Shell history management
│ ├── bash # Bash-specific configuration
│ ├── conda # Conda/Mamba configuration
│ ├── dask # Dask distributed computing
│ ├── direnv # Directory-specific environment setup
│ ├── iterm # iTerm2 profiles
│ ├── karabiner # Keyboard customization for macOS
│ ├── keyboard-maestro # Keyboard Maestro macros and configurations
│ ├── mamba # Mamba package manager settings
│ ├── macos # macOS bootstrap packages and defaults
│ ├── shell # Shell-agnostic configurations
│ ├── starship # Cross-shell prompt
│ ├── syncthing # File synchronization
│ └── zsh # Zsh-specific configuration
├── Dockerfile # Docker container that runs this dotfiles configuration
├── LICENSE
├── README.md # You are here
├── install # Installation script
├── install.conf.yaml # Dotbot configuration
├── submodules # Git submodules for external tools
│ ├── dotbot # Dotfiles installation
│ ├── oh-my-zsh # Zsh framework
│ ├── tmux # oh-my-tmux configuration
│ ├── zsh-autosuggestions # Zsh autosuggestions plugin
│ ├── zsh-syntax-highlighting # Zsh syntax highlighting
│ └── zsh-z
└── uninstall.py # Uninstallation scriptThe shell configuration is structured in a modular way under configs/shell/. The main entry point is main.sh which sources other shell-specific files in a specific order:
configs/shell
├── 00_prefer_zsh.sh # ZSH auto-switching
├── 05_zsh_completions.sh # ZSH completions setup
├── 10_aliases.sh # Shell aliases
├── 20_exports.sh # Environment variables
├── 30_misc.sh # Miscellaneous settings
├── 40_keychain.sh # SSH key management
├── 50_python.sh # Python environment setup
├── 60_slurm.sh # HPC cluster integration
├── 70_zsh_plugins.sh # ZSH plugins setup
└── main.sh # Main shell configuration fileThis modular approach makes it easy to understand, maintain, and customize each aspect of the shell environment.
This setup allows my .zshrc to be as simple as:
# zmodload zsh/zprof # Uncomment for profiling
source ~/dotfiles/configs/shell/main.sh
# zprof # Uncomment for profilingand .bash_profile to be:
source ~/dotfiles/configs/shell/main.sh- Zsh - Primary shell with Oh-My-Zsh, custom theme, and plugins
- Bash - Fallback shell with compatible configuration
- Automatic shell detection - Switches to Zsh automatically if available
- Git - Comprehensive Git configuration with signing, aliases, and more
- Python - Support for conda/mamba/micromamba environments
- Direnv - Directory-specific environment variables
- SSH - Key management with keychain integration
- Homebrew Bundle - Declarative package and app installation for Apple Silicon Macs
- macOS defaults - Repeatable keyboard, Finder, Dock, and screenshot preferences
- Rosetta support - Optional compatibility setup for Intel-only macOS tools
- Karabiner and iTerm2 - Installed through the shared Brewfile
The repository includes several useful utility scripts:
scripts
├── install-macos-packages.sh # Bootstrap Homebrew, macOS packages, and defaults
├── install-slurm-client.sh
├── nbviewer.sh # Script to share Jupyter notebooks via nbviewer
├── run.sh # Run any command from the .dotbins directory without having PATH set up
├── setup-atuin-daemon.sh # Setup atuin daemon with systemd
├── sync-dotfiles.sh # Sync dotfiles to remote machines
├── sync-local-dotfiles.sh # Update dotfiles on the local machine
├── sync-uv-tools.sh # Globally install uv tools I frequently use
└── upload-file.sh # Share files via various file hosting servicesThis repository uses dotbins to manage CLI tools across platforms. The dotbins.yaml configuration defines both the tools to install and their shell integration:
tools_dir: ~/.dotbins
platforms:
linux:
- amd64
- arm64
macos:
- arm64
tools:
delta: dandavison/delta
duf: muesli/duf
dust: bootandy/dust
fd: sharkdp/fd
git-lfs: git-lfs/git-lfs
hyperfine: sharkdp/hyperfine
rg: BurntSushi/ripgrep
yazi: sxyazi/yazi
bat:
repo: sharkdp/bat
shell_code:
bash,zsh: |
alias bat="bat --paging=never"
alias cat="bat --plain --paging=never"
direnv:
repo: direnv/direnv
shell_code:
bash,zsh: |
eval "$(direnv hook __DOTBINS_SHELL__)"
# ... and moredotbins automatically:
- Downloads binaries for your platform
- Organizes them by OS and architecture
- Creates shell integration scripts with your custom aliases and initialization code
- Updates all tools with a single command
The generated shell script at ~/.dotbins/shell/zsh.sh is sourced in your shell configuration, making all tools immediately available with their proper setup.
See the output of dotbins status below:
✅ Loading configuration from: ~/.config/dotbins/config.yaml
✅ Installed Tools Summary
┏━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Tool ┃ Version(s) ┃ Platforms ┃ Last Updated ┃
┡━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ atuin │ 18.6.1 │ linux/amd64, linux/arm64, macos/arm64 │ 1d11h │
│ bat │ 0.25.0 │ linux/amd64, linux/arm64, macos/arm64 │ 40d11h │
│ delta │ 0.18.2 │ linux/amd64, linux/arm64, macos/arm64 │ 56d2h │
│ direnv │ 2.36.0 │ linux/amd64, linux/arm64, macos/arm64 │ 41d3h │
│ duf │ 0.8.1 │ linux/amd64, linux/arm64, macos/arm64 │ 41d3h │
│ dust │ 1.2.0 │ linux/amd64, linux/arm64, macos/arm64 │ 41d3h │
│ eza │ 0.21.3 │ linux/amd64, linux/arm64 │ 25d5h │
│ fd │ 10.2.0 │ linux/amd64, linux/arm64, macos/arm64 │ 41d3h │
│ fzf │ 0.62.0 │ linux/amd64, linux/arm64, macos/arm64 │ 22d8h │
│ git-lfs │ 3.6.1 │ linux/amd64, linux/arm64, macos/arm64 │ 41d3h │
│ hyperfine │ 1.19.0 │ linux/amd64, linux/arm64, macos/arm64 │ 41d3h │
│ keychain │ 2.9.5 │ linux/amd64, linux/arm64, macos/arm64 │ 1d11h │
│ lazygit │ 0.51.1 │ linux/amd64, linux/arm64, macos/arm64 │ 1d11h │
│ micromamba │ 2.1.1-0 │ linux/amd64, linux/arm64, macos/arm64 │ 21d2h │
│ rg │ 14.1.1 │ linux/amd64, linux/arm64, macos/arm64 │ 56d2h │
│ starship │ 1.23.0 │ linux/amd64, linux/arm64, macos/arm64 │ 28d22h │
│ uv │ 0.7.8 │ linux/amd64, linux/arm64, macos/arm64 │ 1d11h │
│ yazi │ 25.4.8 │ linux/amd64, linux/arm64, macos/arm64 │ 41d3h │
│ zoxide │ 0.9.7 │ linux/amd64, linux/arm64, macos/arm64 │ 41d3h │
└────────────┴────────────┴───────────────────────────────────────┴──────────────┘
❌ Missing Tools (defined in config but not installed)
┏━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Tool ┃ Repository ┃ Platform ┃ Architecture ┃
┡━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ eza │ eza-community/eza │ macos │ arm64 │
└──────┴───────────────────┴──────────┴──────────────┘
Tip: Run dotbins sync to install missing toolsOn a new Apple Silicon MacBook Air, clone this repo and run the regular installer:
# Use HTTPS for the first clone if 1Password SSH is not configured yet.
git clone https://github.com/basnijholt/dotfiles.git ~/dotfiles
cd ~/dotfiles
git submodule update --init --recursive --remote --jobs 8
DOTFILES_MACOS_COMPUTER_NAME="wecker-macbook-air" ./installThe macOS installer runs before Dotbot so a fresh machine gets the prerequisites first. It installs Xcode Command Line Tools if needed, bootstraps Homebrew, applies configs/macos/Brewfile, enables Rosetta on Apple Silicon, installs a default Rust toolchain when Cargo is missing, and applies configs/macos/defaults.sh.
Set DOTFILES_INSTALL_SYSTEM_PACKAGES=0 to skip Homebrew/system package setup, DOTFILES_BREW_BUNDLE=0 to skip the Brewfile, DOTFILES_APPLY_MACOS_DEFAULTS=0 to skip macOS defaults, or omit DOTFILES_MACOS_COMPUTER_NAME to leave the hostname unchanged.
For a beginner-friendly first-boot checklist, see MACBOOK_AIR_SETUP.md.
For Linux systems, the configuration automatically adapts to the available environment and provides compatibility with various distributions.
The repository includes scripts to easily sync your dotfiles to remote machines:
# Sync to all configured remote hosts
./scripts/sync-dotfiles.sh
# Install configuration on remotes (re-run dotbot)
./scripts/sync-dotfiles.sh installTo customize these dotfiles for your own use:
- Fork this repository
- Update Git configurations with your information in
configs/git/, specificallygitconfig-personal - Modify shell configurations in
configs/shell/ - Adjust the
install.conf.yamlto match your needs - Update the dotbins.yaml configuration with your preferred tools
- Remove or modify platform-specific configurations as necessary
Commands to get started with the current setup:
# Clone the repository and initialize submodules (optionally fork and replace username)
git clone https://github.com/basnijholt/dotfiles.git ~/dotfiles
cd ~/dotfiles
git submodule update --init --recursive --remote --jobs 8
# Overwrite my personal gitconfig (Add your name in `gitconfig-personal` later)
mv ~/dotfiles/configs/git/gitconfig-personal.example ~/dotfiles/configs/git/gitconfig-personal
# Move existing files to backup
mv ~/.zshrc ~/.zshrc.bak
mv ~/.bash_profile ~/.bash_profile.bak
mv ~/.bashrc ~/.bashrc.bak
mv ~/.gitconfig ~/.gitconfig.bak
# Run the installation script
./installOn Ubuntu, ./install installs the local Sudoku Rust app from $HOME/git/languages/rust/Sudoku into ~/.cargo/bin when that checkout and Cargo are available. It also writes a desktop launcher for the GUI.
On macOS, ./install runs scripts/install-macos-packages.sh first so a new MacBook Air has Homebrew, GUI apps, CLI prerequisites, Rust, and macOS defaults before the shared dotfiles are linked.
On Windows, run .\install.ps1. The installer now bootstraps PowerShell 7 with winget when pwsh.exe is missing, syncs the shared Zellij config, installs Codex with OpenAI's standalone installer, and installs the local WLM Rust app from $HOME\git\languages\rust\WLM into ~\.cargo\bin when that checkout and cargo.exe are available. It also installs the local Sudoku Rust app from $HOME\git\languages\rust\Sudoku. Set WLM_SOURCE or SUDOKU_SOURCE before running the installer to use a different checkout path.
Codex is installed outside dotbins so Windows and WSL use OpenAI's standalone installers directly. ./install runs scripts/install-codex.sh on Unix-like systems, including WSL, and adds bubblewrap to apt-based Linux setups for the Codex Linux sandbox. Set DOTFILES_INSTALL_CODEX=0 before running the installer to skip Codex.
Qwen Code remains available as an optional CLI and its configuration is linked by
./install. Its wrapper is retained for legacy Qwen services; use qwen --local
for a local endpoint or qwen --remote for the SSH-over-Cloudflare path.
./install also installs OpenCode on Unix-like clients. Run opencode-muse in a
Git repository to open a private SSH tunnel to the agents router and start
OpenCode with Muse Glimmer 30B + DFlash as the coding model. OpenCode executes
file and shell tools locally on the client.
The local coding workflow is Aider architect mode against a local GLM-4.7-Flash
llama.cpp endpoint. It replaces Vibe/Devstral as the default while preserving
existing Vibe, Devstral, and remote-model configuration. aider verifies the
local endpoint, requires manual architect acceptance, avoids loading an entire
repository, and applies project-specific checks after edits. See
configs/local-ai/README.md for installation,
automatic service startup, operation, GPU tuning, model replacement, and
removal.
This project is open-source and available under the MIT License.