#!/bin/sh # Install Miguel F. Serna's Neovim config. Documentation: https://www.mfserna.dev/nvim # # curl -fsSL https://www.mfserna.dev/nvim/install.sh | sh # curl -fsSL https://www.mfserna.dev/nvim/install.sh | sh -s -- --install-nvim # curl -fsSL https://www.mfserna.dev/nvim/install.sh | sh -s -- --minimal # # Flags # --install-nvim If nvim is missing or older than 0.11, install the official # release tarball into ~/.local/opt/nvim (no sudo, no package # manager) and link ~/.local/bin/nvim. # --minimal Install init.min.lua only: no plugins, no git, no network # after the download. # --no-sync Write the files but skip the headless plugin install. # --dry-run Print every step without touching the filesystem. # --help Show this text. # # Environment # NVIM_CONFIG_URL Base URL for the files (default https://www.mfserna.dev/nvim). # XDG_CONFIG_HOME Config root (default ~/.config). The config lands in $XDG_CONFIG_HOME/nvim. # # Writes only to: $XDG_CONFIG_HOME/nvim, $XDG_DATA_HOME/nvim (plugins), and, # with --install-nvim, ~/.local/opt/nvim and ~/.local/bin/nvim. An existing # config that differs from this one is moved to nvim.bak., never deleted. set -eu BASE="${NVIM_CONFIG_URL:-https://www.mfserna.dev/nvim}" CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/nvim" DATA_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/nvim" NVIM_PREFIX="$HOME/.local/opt/nvim" NVIM_LINK="$HOME/.local/bin/nvim" MIN_MAJOR=0 MIN_MINOR=11 INSTALL_NVIM=0 MINIMAL=0 SYNC=1 DRY_RUN=0 usage() { cat <<'EOF' Install Miguel F. Serna's Neovim config. Documentation: https://www.mfserna.dev/nvim curl -fsSL https://www.mfserna.dev/nvim/install.sh | sh curl -fsSL https://www.mfserna.dev/nvim/install.sh | sh -s -- --install-nvim curl -fsSL https://www.mfserna.dev/nvim/install.sh | sh -s -- --minimal Flags --install-nvim If nvim is missing or older than 0.11, install the official release tarball into ~/.local/opt/nvim (no sudo) and link ~/.local/bin/nvim. --minimal Install init.min.lua only: no plugins, no git, no network after the download. --no-sync Write the files but skip the headless plugin install. --dry-run Print every step without touching the filesystem. --help Show this text. Environment NVIM_CONFIG_URL Base URL for the files (default https://www.mfserna.dev/nvim). XDG_CONFIG_HOME Config root (default ~/.config). The config lands in $XDG_CONFIG_HOME/nvim. EOF } while [ $# -gt 0 ]; do case "$1" in --install-nvim) INSTALL_NVIM=1 ;; --minimal) MINIMAL=1 ;; --no-sync) SYNC=0 ;; --dry-run) DRY_RUN=1 ;; -h|--help) usage; exit 0 ;; *) printf 'unknown flag: %s\n' "$1" >&2; exit 2 ;; esac shift done log() { printf '%s\n' "$*"; } die() { printf 'error: %s\n' "$*" >&2; exit 1; } have() { command -v "$1" >/dev/null 2>&1; } run() { if [ "$DRY_RUN" -eq 1 ]; then log "+ $*"; else "$@"; fi } TMP=$(mktemp -d 2>/dev/null || mktemp -d -t nvimcfg) trap 'rm -rf "$TMP"' EXIT INT TERM fetch() { # fetch mkdir -p "$(dirname "$2")" if have curl; then curl -fsSL "$BASE/$1" -o "$2" elif have wget; then wget -qO "$2" "$BASE/$1" else die "need curl or wget to download $BASE/$1" fi } nvim_ok() { # Succeeds when the nvim on PATH is at least $MIN_MAJOR.$MIN_MINOR. have nvim || return 1 version=$(nvim --version 2>/dev/null | sed -n '1s/^NVIM v\([0-9][0-9]*\)\.\([0-9][0-9]*\).*/\1 \2/p') [ -n "$version" ] || return 1 major=${version% *} minor=${version#* } [ "$major" -gt "$MIN_MAJOR" ] || { [ "$major" -eq "$MIN_MAJOR" ] && [ "$minor" -ge "$MIN_MINOR" ]; } } install_nvim() { os=$(uname -s) arch=$(uname -m) case "$os-$arch" in Linux-x86_64) asset=nvim-linux-x86_64.tar.gz ;; Linux-aarch64|Linux-arm64) asset=nvim-linux-arm64.tar.gz ;; Darwin-arm64) asset=nvim-macos-arm64.tar.gz ;; Darwin-x86_64) asset=nvim-macos-x86_64.tar.gz ;; *) die "no official Neovim tarball for $os/$arch; install nvim >= $MIN_MAJOR.$MIN_MINOR yourself and rerun" ;; esac url="https://github.com/neovim/neovim/releases/download/stable/$asset" log "Installing Neovim stable from $url" if [ "$DRY_RUN" -eq 1 ]; then log "+ extract to $NVIM_PREFIX and link $NVIM_LINK" return fi have tar || die "need tar to unpack $asset" if have curl; then curl -fsSL "$url" -o "$TMP/$asset"; else wget -qO "$TMP/$asset" "$url"; fi rm -rf "$NVIM_PREFIX" mkdir -p "$NVIM_PREFIX" "$(dirname "$NVIM_LINK")" tar -xzf "$TMP/$asset" --strip-components=1 -C "$NVIM_PREFIX" ln -sf "$NVIM_PREFIX/bin/nvim" "$NVIM_LINK" bin_dir=$(dirname "$NVIM_LINK") case ":$PATH:" in *":$bin_dir:"*) ;; *) log "note: $bin_dir is not on PATH; add it in your shell profile" ;; esac PATH="$bin_dir:$PATH" export PATH nvim_ok || die "installed $NVIM_PREFIX/bin/nvim but it did not report >= $MIN_MAJOR.$MIN_MINOR (old glibc? try the AppImage from https://github.com/neovim/neovim/releases)" } # 1. Neovim if nvim_ok; then log "Found $(nvim --version | sed -n 1p) at $(command -v nvim)" elif [ "$INSTALL_NVIM" -eq 1 ]; then install_nvim else die "nvim >= $MIN_MAJOR.$MIN_MINOR not found. Rerun with --install-nvim to fetch the official tarball into ~/.local, or install it with your package manager." fi # 2. Download if [ "$MINIMAL" -eq 1 ]; then FILES="init.min.lua" else have git || die "git is required: init.lua bootstraps lazy.nvim with git clone. Use --minimal for a plugin-free config." FILES="init.lua lazy-lock.json syntax/baml.vim" fi for file in $FILES; do log "Fetching $BASE/$file" fetch "$file" "$TMP/config/$file" done if [ "$MINIMAL" -eq 1 ]; then mv "$TMP/config/init.min.lua" "$TMP/config/init.lua" fi # 3. Back up, then install if [ -e "$CONFIG_DIR" ]; then if [ -f "$CONFIG_DIR/init.lua" ] && cmp -s "$CONFIG_DIR/init.lua" "$TMP/config/init.lua" 2>/dev/null; then log "Existing config at $CONFIG_DIR already matches; refreshing in place" else backup="$CONFIG_DIR.bak.$(date +%Y%m%d%H%M%S)" log "Moving existing $CONFIG_DIR to $backup" run mv "$CONFIG_DIR" "$backup" fi fi run mkdir -p "$CONFIG_DIR" if [ "$DRY_RUN" -eq 1 ]; then log "+ copy $FILES into $CONFIG_DIR" else (cd "$TMP/config" && find . -type f) | while IFS= read -r path; do mkdir -p "$CONFIG_DIR/$(dirname "$path")" cp "$TMP/config/$path" "$CONFIG_DIR/$path" done fi log "Installed config to $CONFIG_DIR" # 4. Plugins if [ "$MINIMAL" -eq 0 ] && [ "$SYNC" -eq 1 ]; then log "Installing plugins pinned in lazy-lock.json (headless, may take a minute)" run nvim --headless "+Lazy! restore" +qa if [ "$DRY_RUN" -eq 0 ]; then expected=$(grep -c '"branch"' "$CONFIG_DIR/lazy-lock.json") installed=$(ls -1 "$DATA_DIR/lazy" 2>/dev/null | wc -l | tr -d ' ') if [ "$installed" -ge "$expected" ]; then log "Plugins: $installed/$expected present in $DATA_DIR/lazy" else log "warning: $installed/$expected plugins present; open nvim and run :Lazy restore" fi fi fi # 5. Report optional tooling if [ "$MINIMAL" -eq 0 ]; then log "" log "Optional tools (missing ones only reduce features, nothing fails):" for tool in rg cc make tree-sitter node cargo go java python3; do if have "$tool"; then state=ok; else state=missing; fi log " $tool: $state" done log "Language servers install through :Mason on first interactive launch." fi log "" log "Done. Verify with: nvim --headless +checkhealth +qa"