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
zsh
andbash
- 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
- nix-darwin integration - Uses Nix for declarative macOS configuration
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 (
uv
andmicromamba
)) - zoxide for jumping around directories (alternative to
zsh-z
) - Keyboard Maestro for keyboard shortcuts to switch between applications
- zsh-syntax-highlighting for syntax highlighting
- nix-darwin for declarative macOS configuration
Why not?
- I don't use
fish
because I want to be fully compatible withbash
, so therefore I usezsh
as 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 to access private 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 with submodules
git clone --recurse-submodules -j8 git@github.com:basnijholt/dotfiles.git
cd dotfiles
# Run the installation script
./install
Note
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-env
This 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
│ ├── nix-darwin # Nix configuration for macOS
│ ├── 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
│ ├── autoenv # Directory-based environments
│ ├── dotbins # Binaries manager in dotfiles
│ ├── dotbot # Dotfiles installation
│ ├── mydotbins # CLI tool binaries managed by dotbins
│ ├── oh-my-zsh # Zsh framework
│ ├── rsync-time-backup # Time-Machine style backup with rsync
│ ├── syncthing-resolve-conflicts
│ ├── tmux # oh-my-tmux configuration
│ ├── truenas-zfs-unlock
│ ├── zsh-autosuggestions # Zsh autosuggestions plugin
│ ├── zsh-fzf-history-search # Fuzzy history search
│ ├── zsh-syntax-highlighting # Zsh syntax highlighting
│ └── zsh-z
└── uninstall.py # Uninstallation script
The 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 file
This 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 profiling
and .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
- nix-darwin - Declarative system configuration
- Homebrew - Package management via Nix
- Karabiner - Keyboard customization
- iTerm2 - Terminal customization
The repository includes several useful utility scripts:
scripts
├── apt-update.sh
├── eqMac.py
├── eqMac.sh # Poor man's Supervisord/Launchd/Systemd for eqMac because it keeps crashing
├── nbviewer.sh # Script to share Jupyter notebooks via nbviewer
├── pypi-sha256.sh # Generate the commands to update a conda-forge feedstock
├── rclone.sh # Scheduled backups to B2 cloud storage
├── rpi
├── rsync-time-machine.sh # Create incremental Time Machine-like backups using rsync
├── run.sh # Run any command from the .dotbins directory without having PATH set up
├── setup-atuin-daemon.sh # Setup atuin daemon with systemd
├── signature.html
├── sync-dotfiles.sh # Sync dotfiles to remote machines
├── sync-local-dotfiles.sh # Update dotfiles on the local machine
├── sync-photos-to-truenas.sh # Sync photos to TrueNAS server
├── sync-uv-tools.sh # Globally install uv tools I frequently use
└── upload-file.sh # Share files via various file hosting services
This 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 more
dotbins 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 tools
Before running Nix-darwin, set the hostname:
NAME="basnijholt-macbook-pro"
sudo scutil --set HostName $NAME
sudo scutil --set LocalHostName $NAME
sudo scutil --set ComputerName $NAME
dscacheutil -flushcache
The repository includes nix-darwin configuration for a reproducible macOS setup:
# Install Nix
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
# Apply nix-darwin configuration
nixswitch # Alias for darwin-rebuild switch --flake ~/dotfiles/configs/nix-darwin
The nix-darwin configuration manages Homebrew packages declaratively.
See the configs/nix-darwin/homebrew.nix
file for the list of packages.
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 install
Sensitive information is stored in a separate private repository with additional encryption using GPG and git-secret. The structure is as follows:
secrets/ # Private git submodule
└── install # Installation script for secrets
This submodule requires SSH authentication to access, which is why setting up SSH keys as described in the prerequisites is essential.
To customize these dotfiles for your own use:
- Fork this repository
- Remove the
secrets
submodule - Update Git configurations with your information in
configs/git/
, specificallygitconfig-personal
- Modify shell configurations in
configs/shell/
- Adjust the
install.conf.yaml
to 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
# Remove the secrets submodule
git rm -fr secrets
git submodule update --init --recursive --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
./install
This project is open-source and available under the MIT License.