A unified development environment: a provisioning script plus the actual dotfiles it manages, for macOS, Red Hat, and Ubuntu. Clone this repo anywhere and run setup-env.sh to get the same nvim config, tmux setup, and project launcher on a new machine.
MIT licensed, see LICENSE.
- macOS: Homebrew must already be installed. If it isn't,
setup-env.shjust warns and skips package installation entirely, nothing else in the script depends on Homebrew, so dotfile linking anddev-stackstill work. - RHEL / Ubuntu:
dnf/apt-get, plussudoaccess to install system packages. Without sudo, see No sudo access. gitandbashon all three.
setup-env.sh Provisioning script - installs packages, links dotfiles below
nvim/ LazyVim config (dotfile source of truth, symlinked to ~/.config/nvim)
tmux/tmux.conf tmux config (dotfile source of truth, symlinked to ~/.tmux.conf)
nvim/ and tmux/tmux.conf are the real, live config files, not templates. Edit them here, commit, and re-run setup-env.sh on any machine to pick up the change (or just reload tmux/nvim directly, since the symlink means the edit already took effect).
setup-env.sh is idempotent, safe to re-run any time:
- Detects your OS (macOS / RHEL / Ubuntu) and installs the right packages with the right package manager (
brew,dnf,apt-get). - Follows the Perl Principle, applied equally to Python: never forces a system-wide Perl or Python reinstall if a working binary is already on
PATH(important if you manage either with something like perlbrew, pyenv, or conda, since the OS package manager installing "on top" can silently change whatperl/python3resolve to, or even overwrite existing symlinks viabrew link). Only installsPerl::TidyandPerl::Criticviacpanmif they're missing, sincenvim/lua/plugins/perl.luaalready wires upperlnavigator,perltidy, andperlcritic. - Installs global web tooling (
prettier,live-server) to a user-owned npm prefix, not root. - Symlinks
~/.config/nvimto this repo'snvim/, and~/.tmux.conftotmux/tmux.conf. If a real (non-symlink) config already exists at either location, it's renamed to a timestamped.baknext to it first - never deleted, so a first run on a machine with its own hand-built config is safe; nothing is lost, you just end up with a.bakdirectory to compare against and discard once you're happy. - Keeps tmux on the factory-default
Ctrl+bprefix intentionally, so it never collides with LazyVim'sCtrl+h/j/k/lpane navigation fromvim-tmux-navigator, plus installstmux-persist,tmux-yank, andextrakto. - Installs
~/.local/bin/dev-stack, a project launcher (see below), withdev-startkept as a symlink to it. - Adds shell completion for
dev-stackto both~/.bashrcand~/.zshrc(dev-startis kept for compatibility only, without completion).
Windows is not yet supported. See the stub at the bottom of the script.
One-liner, no manual clone needed:
curl -fsSL https://raw.githubusercontent.com/rshingleton/dev-stack/main/install.sh | bash
install.sh is the only thing safe to pipe into bash sight-unseen (it does nothing but clone/update the repo to ~/dev-stack and hand off); everything else - packages, symlinks - lives in setup-env.sh, which install.sh runs for you. Re-running the one-liner later just pulls the latest and re-runs it, same idempotent behavior as running it locally.
Override where it's cloned or which repo it pulls from with env vars:
DEV_STACK_DIR=~/code/dev-stack DEV_STACK_REPO_URL=https://github.com/rshingleton/dev-stack.git \
curl -fsSL https://raw.githubusercontent.com/rshingleton/dev-stack/main/install.sh | bash
Or clone it yourself and skip the bootstrap entirely:
git clone https://github.com/rshingleton/dev-stack.git ~/dev-stack
cd ~/dev-stack
chmod +x setup-env.sh
./setup-env.sh
setup-env.sh finds its own location (SCRIPT_DIR), so both paths behave identically, there are no hardcoded machine-specific paths. If you're setting this up for yourself rather than forking it, you'll likely want to replace nvim/ and tmux/tmux.conf with your own config before running it, the LazyVim setup here (Perl LSP, debugger, test runner) is tailored to a Perl-centric workflow.
dev-stack launches (or reattaches to) a tmux session for a project, with nvim, Claude Code, and a small shell all laid out for you. No project list is hardcoded in the script; aliases are registered per machine. dev-start is kept as an alias, so every command below works under either name.
dev-stack init <name> <path> Register an alias for a project path
dev-stack list Show registered aliases
dev-stack status Show which aliases are running, restorable, or neither
dev-stack pick Fuzzy-pick a registered alias and launch it (uses fzf if installed)
dev-stack remove <name> Remove an alias
dev-stack export [file] Print (or save) aliases, to move to another machine
dev-stack import <file> Merge aliases from a file (fix paths afterward)
dev-stack [name-or-path] [session-name]
Aliases live in ~/.config/dev-start/projects, a machine-local file (project paths differ from machine to machine, so this is deliberately not synced or committed). Use export/import to move the list between machines and fix the paths by hand.
Each session gets one window with nvim, Claude Code, and an idle shell for git/cmd-line ops:
- Vertical (default): nvim on top, Claude Code in the middle, shell sliver at the bottom.
- Horizontal: nvim on the left (full height), Claude Code top-right, shell sliver bottom-right.
Set DEV_START_LAYOUT=horizontal to switch, e.g. DEV_START_LAYOUT=horizontal dev-stack myproject.
dev-stack also knows about tmux-persist: since persist auto-restores a session's saved layout the instant a session with a matching name is created, dev-stack checks for an existing snapshot first and steps out of the way (just creating a bare session) rather than racing persist's restore with its own layout-building commands.
chmod +x setup-env.sh
./setup-env.sh
Re-run any time; every step checks before it acts.
On machines where you can't install system packages yourself, setup-env.sh still does everything that doesn't need root (dotfile links, dev-stack, completion, npm tools, tmux plugins) and hands the rest off instead of failing:
- It checks once up front: root, passwordless sudo, or a sudo password prompt if there's a terminal. With no way to get sudo (or when Homebrew on macOS is owned by another user), missing packages are collected rather than installed.
- At the end it prints the exact install command(s) and writes them to
~/dev-stack-prereqs.sh. Hand that to an admin to run as root (sudo bash ~/dev-stack-prereqs.sh), or on macOS to the user that owns Homebrew. - Re-run
./setup-env.shonce they're installed to finish the setup. The hand-off file is removed once nothing is missing. - If the system Perl's library isn't writable,
Perl::TidyandPerl::Criticare installed into~/perl5, withPATH/PERL5LIBadded to~/.profile.
The hand-off installs one package at a time, so one the repos don't carry doesn't block the rest; it reports any that failed. On RHEL, neovim, fd-find and ripgrep come from outside the base repos, and the hand-off notes that EPEL needs enabling first.
lazygit never needs root on RHEL/Ubuntu (it isn't in EPEL or every Ubuntu release): it's built with go install into ~/.local/bin when Go is on PATH, and otherwise (or if that fails) the checksum-verified release binary is installed there instead.
Options:
./setup-env.sh --no-sudo Don't attempt installs; always produce the hand-off (or DEV_STACK_NO_SUDO=1)
./setup-env.sh --print-prereqs Only print what's missing, change nothing
Both pass through the one-liner too: curl -fsSL .../install.sh | bash -s -- --no-sudo.