From 714e3e0683827a4b54d8ba0887f67745d48bbc27 Mon Sep 17 00:00:00 2001 From: Christoph Urlacher Date: Tue, 10 Feb 2026 17:57:31 +0100 Subject: [PATCH] Config/Neovim: Add bundler script to generate portable neovim config --- .gitignore | 1 + config/neovim/bundle.py | 204 ++++++++++++++++++++++++++++++++++++++++ config/neovim/info.md | 6 ++ 3 files changed, 211 insertions(+) create mode 100644 config/neovim/bundle.py create mode 100644 config/neovim/info.md diff --git a/.gitignore b/.gitignore index 3a644d8e..06c50c9a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ config/neovim/store home/modules/ags/config/types home/modules/ags/config/tsconfig.json system/modules/agenix/secrets.nix +config/neovim/nvim_bundle diff --git a/config/neovim/bundle.py b/config/neovim/bundle.py new file mode 100644 index 00000000..7531e01a --- /dev/null +++ b/config/neovim/bundle.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python3 + +import argparse +import os +import re +import shutil +import subprocess +from typing import cast +from urllib.request import urlretrieve + +INIT_LUA: str = "/home/christoph/.config/nvim/init.lua" + + +def patch_paths(text: str, mappings: dict[str, str]) -> str: + """Patches /nix/store paths in init.lua""" + + patched = text + + for old, new in mappings.items(): + print(f"Patching init.lua: {old} -> {new}") + patched = patched.replace(old, new) + + return patched + + +def patch_various(text: str) -> str: + """Patches various incompatibilities with NixVim init.lua""" + + # Install lazy + print("Patching init.lua: Bootstrap lazy.nvim") + patched = ( + """-- Bootstrap lazy.nvim +local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" +if not (vim.uv or vim.loop).fs_stat(lazypath) then + local lazyrepo = "https://github.com/folke/lazy.nvim.git" + local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath }) + if vim.v.shell_error ~= 0 then + vim.api.nvim_echo({ + { "Failed to clone lazy.nvim:\\n", "ErrorMsg" }, + { out, "WarningMsg" }, + { "\\nPress any key to exit..." }, + }, true, {}) + vim.fn.getchar() + os.exit(1) + end +end +vim.opt.rtp:prepend(lazypath) + +""" + + text + ) + + # print("Patching init.lua: Disabling vim.loader") + # patched = patched.replace("vim.loader.enable(true)", "vim.loader.enable(false)") + + return patched + + +def copy_plugins(text: str, path: str) -> dict[str, str]: + """Copies NeoVim plugins from the Nix Store""" + + os.makedirs(path, exist_ok=True) + + plugins_path: str = re.findall(r"\"(/nix/store/.*-lazy-plugins)\"", text)[0] + print(f"Copying: {plugins_path} -> {path}/plugins") + _ = shutil.copytree(plugins_path, f"{path}/plugins") + + treesitter_path: str = re.findall( + r"\"(/nix/store/.*-vimplugin-nvim-treesitter.*)\"", text + )[0] + print(f"Copying: {treesitter_path} -> {path}/treesitter") + _ = shutil.copytree(treesitter_path, f"{path}/treesitter") + + parsers_path: str = re.findall(r"\"(/nix/store/.*-treesitter-parsers)\"", text)[0] + print(f"Copying: {parsers_path} -> {path}/parsers") + _ = shutil.copytree(parsers_path, f"{path}/parsers") + + return { + plugins_path: "./plugins", + treesitter_path: "./treesitter", + parsers_path: "./parsers", + } + + +def write_file(text: str, path: str) -> None: + """Write text to a file""" + + with open(path, "w") as file: + _ = file.write(text) + + +# TODO: Could add etc. nvim/lsp/formatter/linter binaries here +# TODO: Needs "install recipe", as in most cases the download will be an archive +DOWNLOADS: list[tuple[str, str]] = [ + # ( + # "https://github.com/neovim/neovim/releases/download/v0.11.6/nvim-linux-x86_64.tar.gz", + # "nvim", + # ), +] + + +def download_binaries(path: str, urls: list[tuple[str, str]]) -> None: + """Download required binaries""" + + os.makedirs(f"{path}/bin", exist_ok=True) + + def download(url: str, path: str) -> None: + """Download from URL""" + + print(f"Downloading: {url}") + _ = urlretrieve(url, path) + + for url, name in urls: + download(url, f"{path}/bin/{name}") + + +def build_nvim(path: str) -> None: + """Builds a static nvim binary against musl""" + + # TODO: Build etc. is working, but on the target system there are + # lua-ffi errors from noice.nvim with the static binary. + # This does not happen with nvim from system package repository. + + def run(command: list[str]) -> None: + """Run a subprocess""" + + print(f"Running: {' '.join(command)}") + _ = subprocess.run(command) + + os.makedirs(f"{path}/nvim-build", exist_ok=True) + + with open(f"{path}/nvim-build/build-nvim.sh", "w") as file: + _ = file.write( + "\n".join( + [ + "#!/bin/sh", + "git clone https://github.com/neovim/neovim", + "cd neovim", + "git checkout stable", + 'make -j$(nproc) CMAKE_BUILD_TYPE=Release CMAKE_EXTRA_FLAGS="-DSTATIC_BUILD=1"', + "make CMAKE_INSTALL_PREFIX=/workdir/install install", + ] + ) + ) + + run( + [ + "docker", + "run", + "--rm", + "-it", + "-v", + f"{os.path.abspath(path)}/nvim-build:/workdir", + "-w", + "/workdir", + "alpine:3.23.3", + "/bin/sh", + "-c", + "apk add build-base cmake coreutils curl gettext-tiny-dev git && chmod +x ./build-nvim.sh && ./build-nvim.sh", + ] + ) + + _ = shutil.copytree(f"{path}/nvim-build/install/bin", f"{path}/bin") + _ = shutil.copytree(f"{path}/nvim-build/install/lib", f"{path}/lib") + _ = shutil.copytree(f"{path}/nvim-build/install/share", f"{path}/share") + + _ = shutil.rmtree(f"{path}/nvim-build") + + +def bundle() -> None: + """Creates a standalone NeoVim bundle from the NixVim configuration""" + + parser = argparse.ArgumentParser() + _ = parser.add_argument( + "--config", + type=str, + default=INIT_LUA, + help="init.lua or other config file", + ) + _ = parser.add_argument( + "--out", + type=str, + default="./nvim_bundle", + help="destination folder", + ) + + args = parser.parse_args() + args.config = cast(str, args.config) + args.out = cast(str, args.out) + + with open(args.config, "r") as file: + patched_init_lua: str = file.read() + + path_mappings = copy_plugins(patched_init_lua, args.out) + patched_init_lua = patch_paths(patched_init_lua, path_mappings) + patched_init_lua = patch_various(patched_init_lua) + write_file(patched_init_lua, f"{args.out}/init.lua") + + # build_nvim(args.out) + # download_binaries(args.out, DOWNLOADS) + + +if __name__ == "__main__": + bundle() diff --git a/config/neovim/info.md b/config/neovim/info.md new file mode 100644 index 00000000..610d9630 --- /dev/null +++ b/config/neovim/info.md @@ -0,0 +1,6 @@ +# NeoVim Portable + +## Requirements + +- Base packages: `sudo apt install neovim git direnv ripgrep curl fzf` +- Link or copy the generated `nvim_bundle` to `~/.config/nvim` on the target machine