diff options
author | Linnnus <[email protected]> | 2024-12-21 15:47:33 +0100 |
---|---|---|
committer | Linnnus <[email protected]> | 2024-12-21 16:19:04 +0100 |
commit | 8353554315564b89dfe27d5130080ed04a0a65ad (patch) | |
tree | 896de88b6ea2701692b3ffe977c23947a786318e /shared | |
parent | 9b7ed17b27157a25e57a6e38be3bcfeaa70ec9e8 (diff) |
Move to a profile-based configuration where common
This patch makes it so common configuration is now located in shared/
and each host basically just becomes a bunch of import statements.
The exception here is host-specific configuration like the `*.linus.onl`
that live inside `hosts/ahmed/`.
Specifically I have:
- moved common configuration `shared/{nixos,home-manager,nix-darwin}`.
- moved `hosts/common.nix` to `shared/nixos/common-{nix,shell}-settings.nix`.
- split `hosts/muhammed/{dev-utils,neovim}` into per-language
configuration.
This patch was done in preparation for the arrival of a new host, which
will need to share a lot of configuration with Muhammed.
Diffstat (limited to 'shared')
29 files changed, 1180 insertions, 0 deletions
diff --git a/shared/home-manager/C/default.nix b/shared/home-manager/C/default.nix new file mode 100644 index 0000000..b4d42a5 --- /dev/null +++ b/shared/home-manager/C/default.nix @@ -0,0 +1,22 @@ +# This module configures development tools for C. +{pkgs, ...}: { + home.packages = with pkgs; [ + clang + cscript + ]; + + programs.neovim.extraLuaConfig = '' + require("lspconfig")["clangd"].setup({ + cmd = { "${pkgs.clang-tools}/bin/clangd", "--background-index", "--clang-tidy" }, + on_attach = function(_, bufnr) + vim.keymap.set("n", "<leader>s", function() + vim.cmd [[ClangdSwitchSourceHeader]] + end, { + noremap=true, + silent=true, + buffer=bufnr, + }) + end, + }) + ''; +} diff --git a/shared/home-manager/development-full/default.nix b/shared/home-manager/development-full/default.nix new file mode 100644 index 0000000..162edff --- /dev/null +++ b/shared/home-manager/development-full/default.nix @@ -0,0 +1,15 @@ +# This module pulls in everything development related. Including it will give a +# fully featured development environment with all the bells and whistles. It +# will also explode the closure size, so this shouldn't be included on every +# host! +{...}: { + imports = [ + ../C + ../development-minimal + ../javascript + ../nix + ../noweb + ../python + ../rust + ]; +} diff --git a/shared/home-manager/development-minimal/default.nix b/shared/home-manager/development-minimal/default.nix new file mode 100644 index 0000000..30ef972 --- /dev/null +++ b/shared/home-manager/development-minimal/default.nix @@ -0,0 +1,28 @@ +# This module pulls in other HM modules which together form a minimal +# development enviroment. It does so while taking care not to balloon the +# closure size too much. +{ + pkgs, + lib, + ... +}: { + imports = [ + ../zsh + ../shell-utils + + ../git + ../neovim + ../networking-utils + ]; + + home.packages = with pkgs; [ + rlwrap + devenv + ]; + + # Add system manual pages to the search path on Darwin. + home.sessionVariables.MANPATH = lib.optionalString pkgs.stdenv.isDarwin "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/share/man:/Applications/Xcode.app/Contents/Developer/usr/share/man:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/share/man:$MANPATH"; + + # Add local executables/scripts to path. + home.sessionVariables.PATH = "$HOME/.local/bin:$PATH"; +} diff --git a/shared/home-manager/git/aliases.nix b/shared/home-manager/git/aliases.nix new file mode 100644 index 0000000..03e586c --- /dev/null +++ b/shared/home-manager/git/aliases.nix @@ -0,0 +1,25 @@ +# This module defines my personal git aliases. Some of these are +# pseudo-subcommands which are easier to remember while others simply save me +# some keystrokes. +{...}: { + programs.git.aliases = { + unstage = "restore --staged"; # remove file from staging area + undo = "reset --soft HEAD~"; # undo last commit + }; + + home.shellAliases = { + gs = "git status"; + gd = "git diff --"; + gl = "git log --oneline HEAD~10..HEAD --"; + + gc = "git commit"; + gcp = "git commit --patch"; + gf = "git commit --amend --no-edit --"; + gfp = "git commit --amend --no-edit --patch --"; + + ga = "git add --"; + gan = "git add -N"; + gap = "git add --patch"; + gu = "git unstage"; + }; +} diff --git a/shared/home-manager/git/default.nix b/shared/home-manager/git/default.nix new file mode 100644 index 0000000..748025d --- /dev/null +++ b/shared/home-manager/git/default.nix @@ -0,0 +1,49 @@ +{ + pkgs, + lib, + ... +}: let + inherit (lib) optional; + inherit (pkgs.stdenv.hostPlatform) isDarwin; +in { + imports = [ + ./ignore.nix + ./aliases.nix + ]; + + programs.git = { + enable = true; + + # Set privacy-respecting user information. + userName = "Linnnus"; + userEmail = "[email protected]"; + + extraConfig = { + init.defaultBranch = "master"; + + help.autoCorrect = "prompt"; + + # Make sure we don't accidentally update submodules with changes that are only available locally. + # See: https://git-scm.com/book/en/v2/Git-Tools-Submodules + push.recurseSubmodules = "check"; + + # It seems like a de facto standard to have a file with this name in the + # project root containing all the commits that should be ignored when + # running `git blame`. + blame.ignoreRevsFile = ".git-blame-ignore-revs"; + + credential = { + "https://github.com/" = { + username = "linnnus"; + helper = "${pkgs.gh}/bin/gh auth git-credential"; + }; + helper = (optional isDarwin "osxkeychain") ++ ["cache"]; + }; + }; + }; + + home.packages = with pkgs; [ + # Add the GitHub CLI for authentication. + gh + ]; +} diff --git a/shared/home-manager/git/ignore.nix b/shared/home-manager/git/ignore.nix new file mode 100644 index 0000000..8d1da2f --- /dev/null +++ b/shared/home-manager/git/ignore.nix @@ -0,0 +1,37 @@ +# This module defines the contents of `~/.config/git/ignore`. It fetches the +# templates for different gitignores and compiles them into one. +{ + pkgs, + lib, + ... +}: let + gitignore = ignores: + pkgs.stdenv.mkDerivation { + name = (lib.concatStringsSep "+" ignores) + ".gitignore"; + + src = pkgs.fetchFromGitHub { + owner = "toptal"; + repo = "gitignore"; + rev = "7e72ecd8af69b39c25aedc645117f0dc261cedfd"; + hash = "sha256-Ln3w6wx+pX4UFLY2gGJGax2/nxgp/Svrn0uctSIRdEc="; + }; + + inherit ignores; + buildPhase = '' + for i in $ignores; do + cat ./templates/$i.gitignore >>$out + done + ''; + }; + + targets = + [ + "Node" + "Deno" + "C" + ] + ++ (lib.optional pkgs.stdenv.isDarwin "MacOS") + ++ (lib.optional pkgs.stdenv.isLinux "Linux"); +in { + xdg.configFile."git/ignore".source = gitignore targets; +} diff --git a/shared/home-manager/iterm2/auto_theme.py b/shared/home-manager/iterm2/auto_theme.py new file mode 100644 index 0000000..0970304 --- /dev/null +++ b/shared/home-manager/iterm2/auto_theme.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +import asyncio +import iterm2 + +async def update(connection, theme): + # Themes have space-delimited attributes, one of which will be light or dark. + parts = theme.split(" ") + if "dark" in parts: + preset = await iterm2.ColorPreset.async_get(connection, "Dark Background") + else: + preset = await iterm2.ColorPreset.async_get(connection, "Light Background") + + # Update the list of all profiles and iterate over them. + profiles=await iterm2.PartialProfile.async_query(connection) + for partial in profiles: + # Fetch the full profile and then set the color preset in it. + profile = await partial.async_get_full_profile() + await profile.async_set_color_preset(preset) + +async def main(connection): + app = await iterm2.async_get_app(connection) + await update(connection, await app.async_get_variable("effectiveTheme")) + async with iterm2.VariableMonitor(connection, iterm2.VariableScopes.APP, "effectiveTheme", None) as mon: + while True: + # Block until theme changes + theme = await mon.async_get() + await update(connection, theme) + + +iterm2.run_forever(main) diff --git a/shared/home-manager/iterm2/default.nix b/shared/home-manager/iterm2/default.nix new file mode 100644 index 0000000..b6a9ecc --- /dev/null +++ b/shared/home-manager/iterm2/default.nix @@ -0,0 +1,22 @@ +# This file configures iterm2. Note that the actual definition of iTerm2 for +# home-manager is in `modules/home-manager/iterm2`. *That* file declares +# `options.programs.iterm2.enable`. +{pkgs, ...}: { + config = { + home.packages = with pkgs; [imgcat]; + + # Install a script which automatically makes iTerm2 match the system-wide light/dark mode. + home.file."/Library/Application Support/iTerm2/Scripts/AutoLaunch/auto_theme.py".text = builtins.readFile ./auto_theme.py; + + programs.iterm2 = { + enable = true; + # config = { + # # Use the minimal tab style. + # # See: https://github.com/gnachman/iTerm2/blob/bd40fba0611fa94684dadf2478625f2a93eb6e47/sources/iTermPreferences.h#L29 + # TabStyleWithAutomaticOption = 5; + # }; + + shellIntegration.enableZshIntegration = true; + }; + }; +} diff --git a/shared/home-manager/javascript/default.nix b/shared/home-manager/javascript/default.nix new file mode 100644 index 0000000..2beaf12 --- /dev/null +++ b/shared/home-manager/javascript/default.nix @@ -0,0 +1,54 @@ +# This module configures development tools for JavaScript/TypeScript. +{pkgs, ...}: { + home.packages = with pkgs; [ + unstable.deno + unstable.nodejs_latest + ]; + + programs.neovim.extraLuaConfig = '' + require("lspconfig")["denols"].setup({ + init_options = { + enable = true, + unstable = true, + lint = true, + nodeModulesDir = true, + }, + cmd = { "${pkgs.unstable.deno}/bin/deno", "lsp" }, + root_dir = function(startpath) + if util.find_package_json_ancestor(startpath) then + -- This is a Node project; let ts_ls handle this one. + -- This exactly mirrors how typescript-langauge-server yields to this server for Deno projects. + return nil + else + -- Otherwise, we try to find the root or + -- default to the current directory. + return util.root_pattern("deno.json", "deno.jsonc", ".git")(startpath) + or util.path.dirname(startpath) + end + end, + }); + + require("lspconfig")["ts_ls"].setup({ + cmd = { "${pkgs.nodePackages_latest.typescript-language-server}/bin/typescript-language-server", "--stdio" }, + root_dir = function(startpath) + local find_deno_root_dir = util.root_pattern("deno.json", "deno.jsonc") + if find_deno_root_dir(startpath) then + -- This is a Deno project; let deno-lsp handle this one. + -- This exactly mirrors how deno-lsp yields to this server for Node projects. + return nil + else + -- Otherwise fall back to the usual resolution method. + -- See: https://github.com/neovim/nvim-lspconfig/blob/056f569f71e4b726323b799b9cfacc53653bceb3/lua/lspconfig/server_configurations/ts_ls.lua#L15 + return util.root_pattern("tsconfig.json", "jsconfig.json", "package.json", ".git")(startpath) + end + end, + -- We also have to disallow starting in without a root directory, as otherwise returning + -- nil from find_root will just cause the LSP to be spawned in single file mode instead of yielding to deno-lsp. + -- + -- This has the side effect that Deno LSP will be preferred in a single file context which is what we want! + -- + -- See: https://github.com/neovim/nvim-lspconfig/blob/056f569f71e4b726323b799b9cfacc53653bceb3/lua/lspconfig/manager.lua#L281-L286 + single_file_support = false, + }) + ''; +} diff --git a/shared/home-manager/neovim/completion.nix b/shared/home-manager/neovim/completion.nix new file mode 100644 index 0000000..3776674 --- /dev/null +++ b/shared/home-manager/neovim/completion.nix @@ -0,0 +1,47 @@ +# This module sets up auto completion for Neovim. +{pkgs, ...}: { + programs.neovim.plugins = with pkgs.vimPlugins; [ + # This is the actual completion engine. + { + plugin = nvim-cmp; + type = "lua"; + config = '' + local cmp = require("cmp") + + cmp.setup({ + mapping = cmp.mapping.preset.insert({ + ["<C-b>"] = cmp.mapping.scroll_docs(-4), + ["<C-f>"] = cmp.mapping.scroll_docs(4), + ["<C-j>"] = cmp.mapping.select_next_item(), + ["<C-k>"] = cmp.mapping.select_prev_item(), + ["<C-Space>"] = cmp.mapping.complete(), + ["<C-e>"] = cmp.mapping.abort(), + ["<Tab>"] = cmp.mapping.confirm({ select = true }), -- Accept currently selected item. Set `select` to `false` to only confirm explicitly selected items. + }), + sources = cmp.config.sources({ + { name = "nvim_lsp" }, + { name = "calc" }, + { name = "path" }, + { name = "buffer" }, + }), + -- disable completion in comments + enabled = function() + local context = require("cmp.config.context") + -- keep command mode completion enabled when cursor is in a comment + if vim.api.nvim_get_mode().mode == "c" then + return true + else + return not context.in_treesitter_capture("comment") + and not context.in_syntax_group("Comment") + end + end + }) + ''; + } + # The following are plugins for the... completion plugin. + cmp-nvim-lsp + cmp-calc + cmp-buffer + cmp-path + ]; +} diff --git a/shared/home-manager/neovim/default.nix b/shared/home-manager/neovim/default.nix new file mode 100644 index 0000000..63a7564 --- /dev/null +++ b/shared/home-manager/neovim/default.nix @@ -0,0 +1,28 @@ +# This file contains the HM configuration options for Neovim. +{...}: { + imports = [ + ./completion.nix + ./editing-plugins.nix + ./lsp.nix + ]; + + programs.neovim = { + enable = true; + + # Import my existing config. I've been working on this for years and when + # my enthusiasm for Nix eventually dies off, I want to take it with me. + extraConfig = builtins.readFile ./init.vim; + + # Typing `vi`, `vim`, or `vimdiff` will also run neovim. + viAlias = true; + vimAlias = true; + vimdiffAlias = true; + }; + + # Set Neovim as the default editor. + home.sessionVariables.EDITOR = "nvim"; + home.sessionVariables.VISUAL = "nvim"; + + # Use neovim as man pager. + home.sessionVariables.MANPAGER = "nvim +Man!"; +} diff --git a/shared/home-manager/neovim/editing-plugins.nix b/shared/home-manager/neovim/editing-plugins.nix new file mode 100644 index 0000000..23c7d63 --- /dev/null +++ b/shared/home-manager/neovim/editing-plugins.nix @@ -0,0 +1,26 @@ +# This module sets up and configures various miscellaneous plugins. +# TODO: I fear this file will become the utils.lua of my Neovim configuration. Remove it! +{pkgs, ...}: { + programs.neovim.plugins = [ + { + plugin = pkgs.vimPlugins.vim-localvimrc; + type = "viml"; + config = '' + let g:localvimrc_persistent = 1 + let g:localvimrc_name = [ "local.vim", "editors/local.vim" ] + ''; + } + { + plugin = pkgs.vimPlugins.vim-sneak; + type = "viml"; + config = '' + let g:sneak#s_next = 1 + let g:sneak#use_ic_scs = 1 + map f <Plug>Sneak_f + map F <Plug>Sneak_F + map t <Plug>Sneak_t + map T <Plug>Sneak_T + ''; + } + ]; +} diff --git a/shared/home-manager/neovim/init.vim b/shared/home-manager/neovim/init.vim new file mode 100644 index 0000000..eeb6920 --- /dev/null +++ b/shared/home-manager/neovim/init.vim @@ -0,0 +1,274 @@ +" Settings +""""""""""""""""""""""""""" + +" Leave boomer mode +set nocompatible + +set history=1000 + +" Backspace in insert mode +set backspace=indent,eol,start + +" Hide buffer when abandoned (you can gd away, etc) +set hid + +" Searching +" NOTE: ignorecase and smartcase must be used together (see :h 'smartcase') +set incsearch gdefault ignorecase smartcase nohlsearch + +" Only auto-continue comments when i_<cr> is pressed (not n_o) +" Must be set after :filetype-plugin-on +filetype plugin indent on +au FileType * setlocal fo-=o fo+=r + +" Enable syntax highlighting +syn on + +" Colorscheme +" au VimEnter * ++nested colorscheme ansi_linus + +" Persistent undo +set undofile + +" Give me some thinking time, jesus! +set timeout timeoutlen=2000 + +" Line numbers +set number relativenumber + +" Improve macro performance +set lazyredraw + +" Show matching brackets +set showmatch +set matchtime=2 + +set listchars=tab:>-,eol:$,space:.,trail:@,nbsp:% + +" Enable mouse input for all modes but visual. +" +" I disable mouse in visual mode so I can select text in the terminal using +" the mouse. This is useful when copying text from a remote instance of vim +" SSH session where "* doesn't work. +set mouse=nicr + +" sussy sus the sussy sus +set nowrap + +" Mappings +""""""""""""""""""""""""""" + +let g:mapleader = "\<space>" +let g:maplocalleader = "\<space>" + +" Some keys are hard to press with the Danish layout. Luckily, we have some +" spare keys! Note that ctrl and esc are swapped at the OS level. +nnoremap æ $ +nnoremap Æ 0 + +" Switching windows +" TODO: make this work with iTerm2 panes +" nnoremap <c-h> <c-w><c-h> +" nnoremap <c-j> <c-w><c-j> +" nnoremap <c-k> <c-w><c-k> +" nnoremap <c-l> <c-w><c-l> +" tnoremap <c-h> <c-\><c-n><c-w><c-h> +" tnoremap <c-j> <c-\><c-n><c-w><c-j> +" tnoremap <c-k> <c-\><c-n><c-w><c-k> +" tnoremap <c-l> <c-\><c-n><c-w><c-l> + +" Resize windows +nnoremap + <c-w>+ +nnoremap - <c-w>- + +" Switching tabs +nnoremap <silent> <leader>tt :tabnext<CR> +nnoremap <silent> <leader>tn :tabnew<CR> +nnoremap <silent> <leader>to :tabonly<CR> +nnoremap <silent> <leader>tc :tabclose<CR> +nnoremap <silent> <leader>tm :tabmove +" Just use gt and gT +" nnoremap <silent> <leader>tl :tabn<CR> +" nnoremap <silent> <leader>th :tabN<CR> +nnoremap <silent> <leader>t<S-L> :tabl<CR> +nnoremap <silent> <leader>t<S-H> :tabr<CR> + +" Fast macros (qq to record) +nnoremap Q @q +vnoremap Q :norm! @q<cr> + +" Make Y act like C and D +nnoremap <s-y> y$ +vnoremap <s-y> $y + +" Indent using tab key +nnoremap <Tab> >l +nnoremap <S-Tab> <l +vnoremap <Tab> >gv +vnoremap <S-Tab> <gv + +noremap! <C-j> <down> +noremap! <C-k> <up> +noremap! <C-h> <left> +noremap! <C-l> <right> + +" Toggle showing 'listchars' +nnoremap <silent> <leader>l :set list!<cr> + +" Escape in terminal mode +tnoremap <esc><esc> <c-\><c-n> + +" Seamlessly enter/leave terminal buffer. +tnoremap <c-w> <c-\><c-n><c-w> +au BufEnter term://* norm! i + +" Join to end of line below +" This is already used by the window switching mappings +nnoremap <c-j> ddpkJ + +" Move window to the left and switch to the eastern window. +" I do this move pretty frequently. +nnoremap <c-w><c-w> <c-w>L<c-w>h + +" If the fzf executable is available, assume that the fzf plugin is going to +" be loaded. In that case we want an easy way to load a file. +if executable("fzf") + nnoremap <leader><leader> <CMD>FZF<CR> +else + nnoremap <leader><leader> <CMD>echo "FZF not found!"<CR> +endif + +" Define go-to-definition help pages, otherwise see the mappings in the hook +" for LSP configuration. +autocmd Filetype help nnoremap <buffer> gd <C-]> + +" Commands +""""""""""""""""""""""""""" + +" Create a temporary buffer +" NOTE: relied on by other commands +command Temp new | setlocal buftype=nofile bufhidden=wipe noswapfile nomodified nobuflisted +command TempTab tabnew | setlocal buftype=nofile bufhidden=wipe noswapfile nomodified nobuflisted + +" Reverse lines +command! -bar -range=% Reverse <line1>,<line2>g/^/m<line1>-1|nohl + +" Redraw screen +" CTRL-L mapping is used in other thing +command Redraw norm! + +" Run buffer contents as vimscript +command! -bar -range=% Run execute 'silent!' . <line1> . ',' . <line2> . 'y|@"' + +" Output the result of a command to the buf +command! -nargs=+ -complete=command Output + \ redir => output | + \ silent execute <q-args> | + \ redir END | + \ tabnew | + \ setlocal buftype=nofile bufhidden=wipe noswapfile nobuflisted nomodified | + \ silent put=output | + \ if <q-args> =~ ':!' | + \ silent 1,2delete _ | + \ else | + \ silent 1,4delete _ | + \ endif + +" Copy buffer to system clipboard +command! Copy silent w !pbcopy + +" Copy the location of the current file +command! CopyDir !echo %:r | pbcopy + +" View in-memory changes before writing to disk +command! DiffOnDisk + \ let orig_filetype=&ft | + \ vert new | + \ read ++edit # | 0d_ | + \ setlocal bt=nofile bh=wipe nobl noswf ro | + \ let &l:filetype = orig_filetype | + \ diffthis | + \ wincmd p | + \ diffthis + +command WrapItUp setlocal wrap + \| nnoremap <buffer> j gj + \| nnoremap <buffer> k gk + \| nnoremap <buffer> 0 g0 + \| nnoremap <buffer> $ g$ + +" Miscellaneous +""""""""""""""""""""""""""" + +" Show the color column only if insert mode (and only if cc is set) +augroup ShowCCInInsertMode + au! + au InsertEnter * if &tw != 0 | let &cc = &tw + 1 | endif + au InsertLeave * let &cc = 0 +augroup END + +" Auto-refresh vim config +" au BufWritePost $XDG_CONFIG_HOME/*.{vim,lua} so % + +" Jump to last editing location when opening files +au BufReadPost * + \ if line("'\"") > 0 && line("'\"") <= line("$") | + \ exe "normal! g'\"" | + \ endif + +augroup Sus + au! + + " Add syntax groups if relevant. This conditional is compensating for + " the lack of negative matches in :au. + " + " See: https://vim.fandom.com/wiki/Highlight_unwanted_spaces + " See: https://stackoverflow.com/questions/6496778/vim-run-autocmd-on-all-filetypes-except + fun! s:AddSyntax() + if bufname() !~ 'term://\|man://' + " Any trailing whitespace at the end of lines. + syn match SusWhitespace /\s\+$/ containedin=ALL + + " Any non-breaking spaces. These are generated by + " CMD+SPACE and deeply annoying. + syn match SusWhitespace /\%u00A0/ containedin=ALL + + " Any characters beyond the maximum width of the text. + if &tw > 0 + let reg = '\%' . (&tw + 1) . 'v.\+' + exe 'syn match SusWhitespace /'.reg.'/ containedin=ALL' + endif + endif + endfun + + " Remove highligt group. + " + " Note that we have to do abit more work since the the syntax rules + " have changed under Vim's nose. Hopefully the perfomance + " characteristics don't come back to haunt us. + fun! s:RemoveSyntax() + syn clear SusWhitespace + syn sync fromstart + endfun + + " Add a persistent highligt group, which matches are going to use. + au VimEnter,ColorScheme * hi SusWhitespace ctermbg=red guibg=red + + " Create some persistent syntax highlighting groups. + au Syntax * call s:AddSyntax() + + " When 'textwidth' changes, we may need to recalculate. + au OptionSet textwidth call s:RemoveSyntax() + \ | call s:AddSyntax() + + " Temporarily remove the groups when in insert mode. + au InsertEnter * call s:RemoveSyntax() + au InsertLeave * call s:AddSyntax() +augroup END + +" Allow for quick prototyping outside of NixOS/Home-Manager by loading some +" extra configuration if relevant. +let extra_vimrc = expand("~/extra-temporary.vimrc") +if filereadable(extra_vimrc) + execute "source " . extra_vimrc +endif diff --git a/shared/home-manager/neovim/lsp.nix b/shared/home-manager/neovim/lsp.nix new file mode 100644 index 0000000..bc10487 --- /dev/null +++ b/shared/home-manager/neovim/lsp.nix @@ -0,0 +1,59 @@ +# Installs and configures lspconfig. The actual LSPs are added in the per-language profiles. +{pkgs, ...}: { + programs.neovim.plugins = [ + { + plugin = pkgs.vimPlugins.nvim-lspconfig; + type = "lua"; + config = '' + local lspconfig = require("lspconfig") + local util = require("lspconfig.util") + + -- Mappings. + -- See `:help vim.diagnostic.*` for documentation on any of the below functions + local opts = { noremap=true, silent=true } + vim.keymap.set('n', '<leader>e', vim.diagnostic.open_float, opts) + vim.keymap.set('n', '[d', vim.diagnostic.goto_prev, opts) + vim.keymap.set('n', ']d', vim.diagnostic.goto_next, opts) + vim.keymap.set('n', '<leader>q', vim.diagnostic.setloclist, opts) + + -- Use an on_attach function to only map the following keys + -- after the language server attaches to the current buffer + vim.api.nvim_create_autocmd("LspAttach", { + callback = function(args) + local bufnr = args.buf + local client = vim.lsp.get_client_by_id(args.data.client_id) + + -- Enable completion triggered by <c-x><c-o> + if client.server_capabilities.completionProvider then + vim.bo[bufnr].omnifunc = "v:lua.vim.lsp.omnifunc" + end + + -- XXX: What does this do? + if client.server_capabilities.definitionProvider then + vim.bo[bufnr].tagfunc = "v:lua.vim.lsp.tagfunc" + end + + -- Mappings. + -- See `:help vim.lsp.*` for documentation on any of the below functions + local bufopts = { noremap=true, silent=true, buffer=bufnr } + vim.keymap.set('n', 'gD', vim.lsp.buf.declaration, bufopts) + vim.keymap.set('n', 'gd', vim.lsp.buf.definition, bufopts) + vim.keymap.set('n', 'K', vim.lsp.buf.hover, bufopts) + vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, bufopts) + vim.keymap.set('n', '<C-k>', vim.lsp.buf.signature_help, bufopts) + vim.keymap.set('n', '<leader>wa', vim.lsp.buf.add_workspace_folder, bufopts) + vim.keymap.set('n', '<leader>wr', vim.lsp.buf.remove_workspace_folder, bufopts) + vim.keymap.set('n', '<leader>wl', function() + print(vim.inspect(vim.lsp.buf.list_workspace_folders())) + end, bufopts) + vim.keymap.set('n', '<leader>D', vim.lsp.buf.type_definition, bufopts) + vim.keymap.set('n', '<leader>rn', vim.lsp.buf.rename, bufopts) + vim.keymap.set('n', '<leader>ca', vim.lsp.buf.code_action, bufopts) + vim.keymap.set('n', 'gr', vim.lsp.buf.references, bufopts) + vim.keymap.set('n', '<leader>f', function() vim.lsp.buf.format { async = true } end, bufopts) + end, + }) + ''; + } + ]; +} diff --git a/shared/home-manager/networking-utils/default.nix b/shared/home-manager/networking-utils/default.nix new file mode 100644 index 0000000..70802a9 --- /dev/null +++ b/shared/home-manager/networking-utils/default.nix @@ -0,0 +1,10 @@ +# This module adds some networking utilities to my home managed environment. +{pkgs, ...}: { + home.packages = with pkgs; + [ + nmap + inetutils + socat + ] + ++ lib.optional (!pkgs.stdenv.isDarwin) netcat; +} diff --git a/shared/home-manager/nix/default.nix b/shared/home-manager/nix/default.nix new file mode 100644 index 0000000..dcdff2a --- /dev/null +++ b/shared/home-manager/nix/default.nix @@ -0,0 +1,14 @@ +# This module configures development tools for Nix. +{pkgs, ...}: { + programs.neovim = { + plugins = with pkgs.vimPlugins; [ + vim-nix + ]; + + extraLuaConfig = '' + require("lspconfig")["nixd"].setup({ + cmd = { "${pkgs.nixd}/bin/nixd" }, + }) + ''; + }; +} diff --git a/shared/home-manager/noweb/default.nix b/shared/home-manager/noweb/default.nix new file mode 100644 index 0000000..1506431 --- /dev/null +++ b/shared/home-manager/noweb/default.nix @@ -0,0 +1,16 @@ +{pkgs, ...}: { + home.packages = with pkgs; [ + noweb + texliveFull + yalafi-shell + ]; + + programs.neovim.plugins = with pkgs; [ + vim-noweb + ]; + + # Prepend nowebs STY files to the search path. I chose to do it globally, + # rather than using `makeWrapper` because I sometimes want to manually invoke + # `pdflatex` and the like on the output of `noweave`. + home.sessionVariables.TEXINPUTS = "${pkgs.noweb.tex}/tex/latex/noweb/:$TEXINPUTS"; +} diff --git a/shared/home-manager/python/default.nix b/shared/home-manager/python/default.nix new file mode 100644 index 0000000..380d352 --- /dev/null +++ b/shared/home-manager/python/default.nix @@ -0,0 +1,16 @@ +# This module configures development tools for Python. +{pkgs, ...}: { + home.packages = with pkgs; [ + (python311Full.withPackages (ps: + with ps; [ + virtualenv + tkinter + ])) + ]; + + programs.neovim.extraLuaConfig = '' + require("lspconfig")["pyright"].setup({ + cmd = { "${pkgs.pyright}/bin/pyright-langserver", "--stdio" }, + }) + ''; +} diff --git a/shared/home-manager/qbittorrent/default.nix b/shared/home-manager/qbittorrent/default.nix new file mode 100644 index 0000000..0b7e2b1 --- /dev/null +++ b/shared/home-manager/qbittorrent/default.nix @@ -0,0 +1,5 @@ +# Adds qbittorrent. +# FIXME: Configuration is still stored mutably. +{pkgs, ...}: { + home.packages = [pkgs.qbittorrent]; +} diff --git a/shared/home-manager/rust/default.nix b/shared/home-manager/rust/default.nix new file mode 100644 index 0000000..3ca301c --- /dev/null +++ b/shared/home-manager/rust/default.nix @@ -0,0 +1,13 @@ +# This module configures development tools for Rust. +{pkgs, ...}: { + home.packages = with pkgs; [ + rustc + cargo + ]; + + programs.neovim.extraLuaConfig = '' + require("lspconfig")["rust_analyzer"].setup({ + cmd = { "${pkgs.rust-analyzer}/bin/rust-analyzer" }, + }) + ''; +} diff --git a/shared/home-manager/shell-utils/default.nix b/shared/home-manager/shell-utils/default.nix new file mode 100644 index 0000000..8635ceb --- /dev/null +++ b/shared/home-manager/shell-utils/default.nix @@ -0,0 +1,34 @@ +# This module adds some common shell utilities to my home managed environment. +{pkgs, ...}: let + isLinux = pkgs.stdenv.isLinux; + isDarwin = pkgs.stdenv.isDarwin; +in { + home.packages = with pkgs; + [ + human-sleep + ripgrep + jc + jq + nowrap + echoargs + ] + ++ lib.optionals isLinux [ + file # File is not included in NixOS, but *is* included in Darwin. + ] + ++ lib.optionals isDarwin [ + pbv + trash + disable-sleep + + # Unlike the `stat` command on Linux (from coreutils or whatever), OSX's + # `stat` does not automatically use the nicer format when stdout is a + # sterminal. + (pkgs.writeShellScriptBin "stat" '' + if [ -t 1 ]; then + /usr/bin/stat -x "$@" + else + /usr/bin/stat "$@" + fi + '') + ]; +} diff --git a/shared/home-manager/svelte/default.nix b/shared/home-manager/svelte/default.nix new file mode 100644 index 0000000..7b76041 --- /dev/null +++ b/shared/home-manager/svelte/default.nix @@ -0,0 +1,15 @@ +# This module configures development tools for Svelte. +{pkgs, ...}: { + programs.neovim = { + plugins = with pkgs.vimPlugins; [ + vim-svelte + ]; + + extraLuaConfig = '' + require("lspconfig")["svelte"].setup({ + cmd = { "${pkgs.nodePackages_latest.svelte-language-server}/bin/svelteserver", "--stdio" }, + root_dir = util.root_pattern("package.json", ".git", "deno.json", "deno.jsonc"), + }) + ''; + }; +} diff --git a/shared/home-manager/zsh/default.nix b/shared/home-manager/zsh/default.nix new file mode 100644 index 0000000..31796a9 --- /dev/null +++ b/shared/home-manager/zsh/default.nix @@ -0,0 +1,31 @@ +{config, ...}: { + imports = [ + ./plugins.nix + ./editing.nix + ]; + + programs.zsh = { + enable = true; + + # Feeble attempt at cleaning up home directory. + # TODO: dotDir = (pathRelativeTo config.xdg.configHome config.home) + "/zsh"; + dotDir = ".config/zsh"; + history.path = config.xdg.cacheHome + "/zsh/history"; + + initExtra = '' + set -o PROMPTSUBST + if [ -v NVIM -o -v VIM ]; then + # smol prompt + PROMPT='%# ' + else + # loong looooong prooooompt – Nagāi Sakeru Gumi + PROMPT='%B%(2L.LVL%L .)%b%F{red}%(?..E%? )%f%F{93}%n%f@%F{35}%m%f%# ' + fi + RPROMPT='%F{green}%$((COLUMNS/4))<...<%~%<<%f' + + mkcd () { + mkdir "$1" && cd "$1" + } + ''; + }; +} diff --git a/shared/home-manager/zsh/editing.nix b/shared/home-manager/zsh/editing.nix new file mode 100644 index 0000000..241ef3c --- /dev/null +++ b/shared/home-manager/zsh/editing.nix @@ -0,0 +1,43 @@ +# This module contains all ZSH configuration related to the editing experience (e.g. setting VI mode). +{ + pkgs, + lib, + ... +}: let + inherit (lib.strings) concatStringsSep; + inherit (lib.attrsets) catAttrs; + + plugins = [ + { + name = "zsh-vi-mode-cursor"; + src = pkgs.fetchFromGitHub { + owner = "Buckmeister"; + repo = "zsh-vi-mode-cursor"; + rev = "fa7cc0973ee71636e906e25e782d0aea19545d60"; + hash = "sha256-j73M4bvAoHWt5Wwg47hM0p5Or74x/3btTOPnI22SqG8="; + }; + } + ]; +in { + programs.zsh = { + # VIM! VIM! VIM! + defaultKeymap = "viins"; + + plugins = map (p: removeAttrs p ["config"]) plugins; + + initExtra = '' + # Set up external editing by pressing '!' in normal mode. + autoload -z edit-command-line + zle -N edit-command-line + bindkey -M vicmd '!' edit-command-line + + # Plugins config. + ${concatStringsSep "\n" (catAttrs "config" plugins)} + ''; + }; + + programs.fzf = { + enable = true; + enableZshIntegration = true; + }; +} diff --git a/shared/home-manager/zsh/plugins.nix b/shared/home-manager/zsh/plugins.nix new file mode 100644 index 0000000..6dbc151 --- /dev/null +++ b/shared/home-manager/zsh/plugins.nix @@ -0,0 +1,47 @@ +# This module manages behavioral plugins – plugins that alter how ZSH acts (e.g. autovenv, direnv). +{ + pkgs, + lib, + config, + ... +}: let + inherit (lib.strings) concatStringsSep; + inherit (lib.attrsets) catAttrs; + + plugins = [ + { + name = "autovenv"; + src = pkgs.fetchFromGitHub { + owner = "linnnus"; + repo = "autovenv"; + rev = "d9f0cd7"; + hash = "sha256-GfJIybMYxE97xLSkrOSGsn+AREmnCyqe9n2aZwjw4w4="; + }; + } + { + name = "zsh-vi-mode-cursor"; + src = pkgs.fetchFromGitHub { + owner = "Buckmeister"; + repo = "zsh-vi-mode-cursor"; + rev = "fa7cc0973ee71636e906e25e782d0aea19545d60"; + hash = "sha256-j73M4bvAoHWt5Wwg47hM0p5Or74x/3btTOPnI22SqG8="; + }; + } + { + name = "zsh-nix-shell"; + file = "nix-shell.plugin.zsh"; + src = pkgs.fetchFromGitHub { + owner = "chisui"; + repo = "zsh-nix-shell"; + rev = "v0.7.0"; + sha256 = "149zh2rm59blr2q458a5irkfh82y3dwdich60s9670kl3cl5h2m1"; + }; + } + ]; +in { + programs.zsh = { + plugins = map (p: removeAttrs p ["config"]) plugins; + + initExtra = concatStringsSep "\n" (catAttrs "config" plugins); + }; +} diff --git a/shared/nixos/cloudflare-proxy/default.nix b/shared/nixos/cloudflare-proxy/default.nix new file mode 100644 index 0000000..45ccaa6 --- /dev/null +++ b/shared/nixos/cloudflare-proxy/default.nix @@ -0,0 +1,88 @@ +# This module adds some extra configuration useful when running behid a Cloudflare Proxy. +# Mainly, it blocks all incomming conncections on relevant ports that aren't +# coming from an official CloudFlare domain. +{ + config, + lib, + pkgs, + metadata, + ... +}: let + # TODO: What happens when these get out of date??? Huh??? You little pissbaby + fileToList = x: lib.strings.splitString "\n" (builtins.readFile x); + cfipv4 = fileToList (pkgs.fetchurl { + url = "https://www.cloudflare.com/ips-v4"; + hash = "sha256-8Cxtg7wBqwroV3Fg4DbXAMdFU1m84FTfiE5dfZ5Onns="; + }); + cfipv6 = fileToList (pkgs.fetchurl { + url = "https://www.cloudflare.com/ips-v6"; + hash = "sha256-np054+g7rQDE3sr9U8Y/piAp89ldto3pN9K+KCNMoKk="; + }); + + IPv4Whitelist = [metadata.hosts.muhammed.ipAddress]; + IPv6Whitelist = []; +in { + config = { + # Teach NGINX how to extract the proxied IP from proxied requests. + # + # See: https://nixos.wiki/wiki/Nginx#Using_realIP_when_behind_CloudFlare_or_other_CDN + services.nginx.commonHttpConfig = let + realIpsFromList = lib.strings.concatMapStringsSep "\n" (x: "set_real_ip_from ${x};"); + in '' + ${realIpsFromList cfipv4} + ${realIpsFromList cfipv6} + real_ip_header CF-Connecting-IP; + ''; + + # Block non-Cloudflare IP addresses. + networking.firewall = let + chain = "cloudflare-whitelist"; + in { + extraCommands = let + allow-interface = lib.strings.concatMapStringsSep "\n" (i: ''ip46tables --append ${chain} --in-interface ${i} --jump RETURN''); + allow-ip = cmd: lib.strings.concatMapStringsSep "\n" (r: ''${cmd} --append ${chain} --source ${r} --jump RETURN''); + in '' + # Flush the old firewall rules. This behavior mirrors the default firewall service. + # See: https://github.com/NixOS/nixpkgs/blob/ac911bf685eecc17c2df5b21bdf32678b9f88c92/nixos/modules/services/networking/firewall-iptables.nix#L59-L66 + ip46tables --delete INPUT --protocol tcp --destination-port 80 --syn --jump ${chain} 2>/dev/null || true + ip46tables --delete INPUT --protocol tcp --destination-port 443 --syn --jump ${chain} 2>/dev/null || true + ip46tables --flush ${chain} || true + ip46tables --delete-chain ${chain} || true + + # Create a chain that only allows whitelisted IPs through. + ip46tables --new-chain ${chain} + + # Allow trusted interfaces through. + ${allow-interface config.networking.firewall.trustedInterfaces} + + # Allow local whitelisted IPs through + ${allow-ip "iptables" IPv4Whitelist} + ${allow-ip "ip6tables" IPv6Whitelist} + + # Allow Cloudflare's IP ranges through. + ${allow-ip "iptables" cfipv4} + ${allow-ip "ip6tables" cfipv6} + + # Everything else is dropped. + # + # TODO: I would like to use `nixos-fw-log-refuse` here, but I keep + # running into weird issues when reloading the firewall. + # Something about the table not being deleted properly. + ip46tables --append ${chain} --jump DROP + + # Inject our chain as the first check in INPUT (before nixos-fw). + # We want to capture any new incomming TCP connections. + ip46tables --insert INPUT 1 --protocol tcp --destination-port 80 --syn --jump ${chain} + ip46tables --insert INPUT 1 --protocol tcp --destination-port 443 --syn --jump ${chain} + ''; + extraStopCommands = '' + # Clean up added rulesets (${chain}). This mirrors the behavior of the + # default firewall at the time of writing. + # + # See: https://github.com/NixOS/nixpkgs/blob/ac911bf685eecc17c2df5b21bdf32678b9f88c92/nixos/modules/services/networking/firewall-iptables.nix#L218-L219 + ip46tables --delete INPUT --protocol tcp --destination-port 80 --syn --jump ${chain} 2>/dev/null || true + ip46tables --delete INPUT --protocol tcp --destination-port 443 --syn --jump ${chain} 2>/dev/null || true + ''; + }; + }; +} diff --git a/shared/nixos/common-hm-settings/default.nix b/shared/nixos/common-hm-settings/default.nix new file mode 100644 index 0000000..9262f51 --- /dev/null +++ b/shared/nixos/common-hm-settings/default.nix @@ -0,0 +1,27 @@ +# This module sets common settings related to home-manager (HM). All hosts that +# I directly interact with should include this module. +# +# NOTE: Even though this lives under `shared/nixos` the configuration in here +# should also be compatible with nix-darwin!! +{ + flakeInputs, + flakeOutputs, + metadata, + ... +}: { + # FIXME: Ideally this module would import flakeInputs.home-manager but that causes an infinite recursion for some reason. + + # Use the flake input pkgs so Home Manager configuration can share overlays + # etc. with the rest of the configuration. + home-manager.useGlobalPkgs = true; + + # Pass special arguments from flake.nix further down the chain. I really hate + # this split module system. + home-manager.extraSpecialArgs = {inherit flakeInputs flakeOutputs metadata;}; + + # All interactive systems (i.e. the ones that would use HM) have a 'linus' user. + home-manager.users.linus = { + imports = builtins.attrValues flakeOutputs.homeModules; + xdg.enable = true; + }; +} diff --git a/shared/nixos/common-nix-settings/default.nix b/shared/nixos/common-nix-settings/default.nix new file mode 100644 index 0000000..b5c0a02 --- /dev/null +++ b/shared/nixos/common-nix-settings/default.nix @@ -0,0 +1,60 @@ +# This module sets common settings related to Nix such as enabling flakes and +# using overlays everywhere.. +# +# NOTE: Even though this lives under `shared/nixos` the configuration in here +# should also be compatible with nix-darwin!! +{ + pkgs, + lib, + config, + flakeInputs, + flakeOutputs, + ... +}: +lib.mkMerge [ + { + # Enable de facto stable features. + nix.settings.experimental-features = ["nix-command" "flakes"]; + + nixpkgs.overlays = [ + # Use local overlays. + flakeOutputs.overlays.additions + flakeOutputs.overlays.modifications + + # Add unstable nixpkgs. + (final: prev: {unstable = flakeInputs.nixpkgs-unstable.legacyPackages.${pkgs.system};}) + ]; + + # I'm not *that* vegan. + nixpkgs.config.allowUnfree = true; + + # This will add each flake input as a registry + # To make nix3 commands consistent with your flake + nix.registry = lib.mapAttrs (_: value: {flake = value;}) flakeInputs; + + nix.nixPath = + [ + # Use overlays from this repo for building system configuration as well as system-wide. + # See: https://nixos.wiki/wiki/Overlays#Using_nixpkgs.overlays_from_configuration.nix_as_.3Cnixpkgs-overlays.3E_in_your_NIX_PATH + "nixpkgs-overlays=${flakeInputs.self}/overlays/compat.nix" + + # This will additionally add out inputs to the system's legacy channels + # Making legacy nix commands consistent as well, awesome! + ] + ++ lib.mapAttrsToList (key: value: "${key}=${value.to.path}") config.nix.registry; + + # Add shell-utilities which are only relevant if Nix is enabled. + environment.systemPackages = with pkgs; [ + # For running programs easily. + nix-index # Also includes nix-locate + flakeInputs.comma.packages.${system}.default + + # For editing secrets. + flakeInputs.agenix.packages.${system}.default + ]; + } + (lib.mkIf pkgs.stdenv.isLinux { + # There is not nix-darwin equivalent to this NixOS option. + nix.enable = true; + }) +] diff --git a/shared/nixos/common-shell-settings/default.nix b/shared/nixos/common-shell-settings/default.nix new file mode 100644 index 0000000..ec71861 --- /dev/null +++ b/shared/nixos/common-shell-settings/default.nix @@ -0,0 +1,44 @@ +# This module sets options to ensure a consistent Baseline Shell Experince™ +# across the entire fleet. This includes e.g. common utilities and aliases. +# +# NOTE: Even though this lives under `shared/nixos` the configuration in here +# should also be compatible with nix-darwin!! +{pkgs, ...}: { + # Set ZSH as the shell. + # https://nixos.wiki/wiki/Command_Shell#Changing_default_shelltrue + programs.zsh.enable = true; + environment.shells = [pkgs.zsh]; + + # Very basic system administration tools. + environment.systemPackages = with pkgs; [ + curl + jq + moreutils + neovim + tree + ]; + + # Aliases that are burned into my muscle memory. + environment.shellAliases = { + "mv" = "mv -i"; + "rm" = "rm -i"; + "cp" = "cp -i"; + "ls" = "ls -F -G -A --color=auto"; + "grep" = "grep --color=auto"; + "file" = "file --no-dereference"; + "tree" = "tree --dirsfirst --gitignore"; + + # See: https://github.com/NixOS/nix/issues/5858 + "nix" = "nix --print-build-logs"; + + ".." = "cd ../"; + "..." = "cd ../../"; + "...." = "cd ../../../"; + "....." = "cd ../../../../"; + "......" = "cd ../../../../../"; + "......." = "cd ../../../../../../"; + "........" = "cd ../../../../../../../"; + "........." = "cd ../../../../../../../../"; + ".........." = "cd ../../../../../../../../../"; + }; +} |