One Command. Any Machine. The Story Behind nvim-config.

There is a ritual that every developer performs on a new machine.

You install your package manager. You clone your dotfiles. You open your editor and immediately notice that something is broken — a plugin is missing, a key binding does nothing, the session picker you spent an afternoon configuring is gone. You spend an hour reconstructing the environment you thought you had.

Then you do it again six months later, on a different machine. Or on a remote server at midnight when something in production needs urgent attention.

This is not development. This is setup tax. And it compounds.


What I actually wanted

I use vim and neovim daily. Not because I am nostalgic. Because after fifteen years, they are the editors where my hands move without thinking.

What I wanted was simple:

  1. A single command that installs everything on any machine
  2. Session management that works — open the editor, see your sessions, pick one
  3. Identical key bindings between vim and neovim
  4. A configuration I actually understand and can modify
  5. Something I can hand to someone else and they can install without asking me anything

What I had was a dotfiles repo with a pile of symlinks, a install.sh that half-worked, and vim-plug and lazy.nvim fighting over who owned which directories.


The architecture

The insight that fixed everything: separate the configuration from the installer.

nvim-config/
├── install.sh           ← The one command
├── vim/                 ← Vim config files (symlinked from dotfiles)
│   ├── .vimrc
│   ├── .vimrc.maps      ← Shared key bindings
│   ├── .vimrc.plugin
│   └── ...
├── nvim/                ← Neovim config (symlinked from dotfiles)
│   ├── init.lua
│   └── lua/
│       ├── plugins/init.lua
│       └── sessions/picker.lua
└── test/
    ├── test-install-macos.sh
    └── test-install-linux.sh

The vim and nvim directories contain symlinks that point back to the canonical files in ~/dotfiles. The nvim-config repo exists purely to provide a clean, standalone installer — it does not own the files, it just knows where they are and how to link them.

This means:

  • Editing ~/.vimrc updates the file in place (the symlink goes to dotfiles)
  • The nvim-config repo stays current automatically
  • You can clone nvim-config on a machine that does not have dotfiles and get a sane default

Session management that actually works

The feature I spent the most time on — and the one I am most glad I built properly.

The problem: opening neovim without arguments used to give me a blank buffer. I would think "what was I working on?" and spend a minute reconstructing context that the editor had already forgotten.

The solution: when neovim opens without arguments, it immediately shows a telescope session picker.

  ★  hyperdrift-main         (saved 2 hours ago)
  ★  nvim-config             (saved yesterday)
     typerx-development      (saved 3 days ago)
     web3-capital-hotfix     (saved last week)

  [CR] load   [s] save   [r] rename   [d] delete   [f] ★ favourite   [R] refresh

Sessions are sorted: favourites first, then most recently modified.

The implementation sits in nvim/lua/sessions/picker.lua — a custom telescope picker that reads from persisted.nvim's session files and provides full management (save, rename, delete, favourite) without leaving the picker.

-- Opens automatically on bare launch
vim.api.nvim_create_autocmd("VimEnter", {
  callback = function()
    if vim.fn.argc() == 0 and not vim.g.started_with_stdin then
      vim.schedule(function()
        require("sessions.picker").open()
      end)
    end
  end,
})

The key bindings inside the picker are consistent with the rest of the config — they follow the same logic, no surprises.


The installer: one curl pipe, any machine

bash <(curl -fsSL https://raw.githubusercontent.com/hyperdrift-io/nvim-config/main/install.sh)

What happens:

  1. Detects your OS (macOS, Ubuntu, Debian, Fedora, Alpine, Arch)
  2. Installs vim and/or neovim via the appropriate package manager
  3. Symlinks all config files (backing up any existing ones with a timestamp)
  4. Bootstraps vim-plug and lazy.nvim
  5. Installs plugins headlessly
  6. Installs telescope dependencies (ripgrep, fd)
  7. Runs smoke tests

The full install on a fresh macOS machine takes about three minutes. On Alpine in a Docker container, about two.

Flags:

./install.sh --vim          # vim only
./install.sh --nvim         # neovim only (default)
./install.sh --both         # vim + neovim
./install.sh --remote       # server mode: no GUI plugins, no Copilot
./install.sh --yes          # non-interactive for CI
./install.sh --dry-run      # preview only

The --remote flag is the one I reach for most on servers. No Copilot (requires auth), no Powerline fonts (requires a patched terminal), fast startup. The config loads and you can edit files immediately.


Vim/neovim parity

The key bindings in .vimrc.maps are designed to work identically in both editors. This is not an accident — it required some deliberate choices:

  • Session commands (SessionOpen, SessionSave, SessionDelete) are defined as user commands in init.lua that mirror the vimscript implementations
  • The fuzzy find bindings use fzf.vim in vim and telescope.nvim in neovim — both triggered by \f
  • The leader key is \ in both, consistently

The reason this matters: I use neovim locally and vim on servers. If I had different muscle memory for each, I would constantly type the wrong thing in the wrong place.


dotfiles integration

If you use a dotfiles repo, nvim-config slots in as a submodule:

# In your dotfiles repo
git submodule add https://github.com/hyperdrift-io/nvim-config nvim-config

Then in your dotfiles/install.sh:

install_vim_nvim() {
    bash "$SCRIPT_DIR/nvim-config/install.sh" --both --yes
}

One submodule. One call. The full editor setup is handled by a repo that is versioned, tested, and maintained separately.


The tests

A setup script without tests is a setup script that breaks silently.

# macOS smoke test (runs after ./install.sh --both --yes)
bash test/test-install-macos.sh

# Linux (Docker)
docker run --rm -v $(pwd):/repo ubuntu:24.04 bash /repo/test/test-install-linux.sh

The tests check: binary presence, symlink correctness, lazy.nvim loads cleanly, telescope dependencies installed. GitHub Actions runs them on every push against macOS, Ubuntu, and Alpine.

The CI is the thing that gives you confidence to change the installer without worrying you have broken it on some platform you do not have access to right now.


Try it

# Install (neovim, default)
bash <(curl -fsSL https://raw.githubusercontent.com/hyperdrift-io/nvim-config/main/install.sh)

# Clone and inspect first
git clone https://github.com/hyperdrift-io/nvim-config
cd nvim-config && ./install.sh --dry-run

The repository is at github.com/hyperdrift-io/nvim-config. Issues and PRs welcome, especially for Linux distributions I do not regularly test.


The hour you spend setting up your editor is an hour you do not spend building. The goal is to make that hour happen once, not on every machine, not at midnight, not when something in production is broken and you need to focus.

One command. Any machine. Back to work.


This is part of the Hyperdrift open toolchain — a set of scripts, patterns, and configurations designed to reduce the overhead of building and shipping software.

The installer was built using typerx patterns — cross-platform, self-testing, flag-driven.

Get weekly intel — courtesy of intel.hyperdrift.io