├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── check-flake.yml │ ├── github-pages.yml │ └── update-flake.yml ├── .gitignore ├── README.md ├── conf.d └── recyclarr.yml ├── docs ├── .envrc ├── .gitignore ├── book.toml ├── default.nix ├── flake.lock ├── flake.nix └── src │ ├── README.md │ ├── SUMMARY.md │ ├── articles │ ├── declarative-desktop-env-config-with-nix.md │ ├── github-neovim-1password-cli.md │ └── linux-isnt-for-nerds-anymore.md │ ├── avatar.png │ ├── custom.css │ ├── my-dotfiles.md │ ├── special-characters.md │ └── ublock-filters.md ├── flake.lock ├── flake.nix ├── home-manager ├── components │ ├── _1password-shell.nix │ ├── discord.png │ ├── espanso.nix │ ├── fish.nix │ ├── fzf.nix │ ├── git.nix │ ├── gnome │ │ ├── dconf.nix │ │ └── default.nix │ ├── jujutsu.nix │ ├── nvim.nix │ ├── recyclarr.nix │ ├── ssh.nix │ ├── starship.nix │ ├── terminal.nix │ ├── tokyonight_palette.nix │ ├── vencord.nix │ └── zellij.nix ├── home.nix ├── server.nix └── shared.nix ├── hosts ├── darwin │ ├── default.nix │ └── settings.nix ├── laptop │ ├── default.nix │ └── hardware-configuration.nix ├── pc │ ├── default.nix │ └── hardware-configuration.nix └── server │ ├── adguard.nix │ ├── cleanuperr.nix │ ├── containers.nix │ ├── default.nix │ ├── docmost.nix │ ├── duckdns.nix │ ├── hardware-configuration.nix │ ├── homeassistant.nix │ ├── ip.nix │ ├── media.nix │ ├── nas.nix │ ├── nixosModules │ └── nginx.nix │ ├── observability.nix │ ├── paperless.nix │ ├── torrent_client.nix │ ├── vikunja.nix │ └── wireguard.nix ├── nixos ├── _1password.nix ├── allowed-unfree.nix ├── common.nix ├── desktop_environment.nix ├── nix-conf.nix ├── sshd.nix └── theme.nix ├── nvim ├── .luacheckrc ├── .luarc.json ├── ftplugin │ ├── markdown.lua │ └── nix.lua ├── init.lua ├── lua │ └── my │ │ ├── cmd-palette.lua │ │ ├── configure │ │ ├── autopairs.lua │ │ ├── colorizer.lua │ │ ├── colorscheme.lua │ │ ├── comments.lua │ │ ├── completion.lua │ │ ├── flash.lua │ │ ├── git.lua │ │ ├── grug_far.lua │ │ ├── heirline │ │ │ ├── conditions.lua │ │ │ ├── init.lua │ │ │ ├── separators.lua │ │ │ ├── shared.lua │ │ │ ├── statusline.lua │ │ │ └── winbar.lua │ │ ├── init.lua │ │ ├── language-support.lua │ │ ├── markdown.lua │ │ ├── mini_bracketed.lua │ │ ├── mini_files.lua │ │ ├── mini_move.lua │ │ ├── neotest.lua │ │ ├── noice.lua │ │ ├── op.lua │ │ ├── rustaceanvim.lua │ │ ├── smart-splits.lua │ │ ├── snacks_picker.lua │ │ ├── sql.lua │ │ ├── treesitter.lua │ │ └── which_key.lua │ │ ├── lsp │ │ ├── filetypes.lua │ │ ├── go.lua │ │ ├── lua.lua │ │ ├── nix.lua │ │ ├── snippets.lua │ │ └── typescript.lua │ │ ├── plugins.lua │ │ ├── settings.lua │ │ └── utils │ │ ├── clipboard.lua │ │ ├── editor.lua │ │ ├── lsp.lua │ │ └── path.lua └── stylua.toml ├── packages ├── default.nix └── vim-zellij-navigator.nix ├── secrets.nix ├── secrets ├── cleanuperr_env.age ├── cloudflare_certbot_token.age ├── docmost_env.age ├── duckdns_token.age ├── gatus_discord_webhook_env.age ├── homarr_env.age ├── mullvad_wireguard.age ├── paperless_admin_pw.age └── wireguard_server.age └── ublock-mdbook ├── .envrc ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── default.nix ├── flake.lock ├── flake.nix └── src ├── main.rs └── ublock-filters.yml /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: mrjones2014 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | - package-ecosystem: github-actions 5 | directory: "/" 6 | schedule: 7 | interval: weekly 8 | time: '00:00' 9 | timezone: UTC 10 | open-pull-requests-limit: 10 11 | commit-message: 12 | prefix: "chore" 13 | include: "scope" 14 | -------------------------------------------------------------------------------- /.github/workflows/check-flake.yml: -------------------------------------------------------------------------------- 1 | name: Check Nix flake 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | workflow_dispatch: # allow manual trigger 8 | jobs: 9 | check-flake: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: cachix/install-nix-action@v31 14 | - uses: cachix/cachix-action@v16 15 | with: 16 | name: mrjones2014-dotfiles 17 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 18 | - name: Run nix flake check 19 | run: nix flake check 20 | -------------------------------------------------------------------------------- /.github/workflows/github-pages.yml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | on: 3 | push: 4 | branches: 5 | - master 6 | workflow_dispatch: # allow manual trigger 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: cachix/install-nix-action@v31 13 | - name: Build mdbook site 14 | run: nix build ./docs/ 15 | - name: Deploy 16 | uses: peaceiris/actions-gh-pages@v4 17 | with: 18 | github_token: ${{ secrets.GITHUB_TOKEN }} 19 | publish_branch: gh-pages 20 | publish_dir: ./result/ 21 | -------------------------------------------------------------------------------- /.github/workflows/update-flake.yml: -------------------------------------------------------------------------------- 1 | name: Update flake dependencies 2 | 3 | on: 4 | schedule: 5 | - cron: '0 16 * * 5' 6 | workflow_dispatch: # for allowing manual triggers of the workflow 7 | 8 | jobs: 9 | update-dependencies: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: cachix/install-nix-action@v31 14 | - uses: cachix/cachix-action@v16 15 | with: 16 | name: mrjones2014-dotfiles 17 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 18 | - name: Add nix-community cache 19 | run: cachix use nix-community 20 | - name: update flake.lock 21 | run: nix flake update 22 | - name: Create Pull Request 23 | uses: peter-evans/create-pull-request@v7 24 | id: create-pr 25 | with: 26 | commit-message: "flake: update dependencies" 27 | title: "[automation] update flake dependencies" 28 | branch: "automation/update-flake-dependencies" 29 | token: ${{ secrets.UPDATE_FLAKE_TOKEN }} 30 | - name: Enable PR auto-merge on CI success 31 | run: GH_TOKEN="${{ secrets.UPDATE_FLAKE_TOKEN }}" gh pr merge --squash --delete-branch --auto ${{ steps.create-pr.outputs.pull-request-number }} 32 | - name: Ping me in a comment on the PR 33 | run: | 34 | PR_NUMBER=${{ steps.create-pr.outputs.pull-request-number }} 35 | COMMENT="Ping @mrjones2014" 36 | GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} 37 | COMMENT_URL="https://api.github.com/repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" 38 | curl -s -H "Authorization: token ${GITHUB_TOKEN}" -X POST $COMMENT_URL -d "{\"body\":\"$COMMENT\"}" 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .netrwhist 3 | result 4 | # I know I'm supposed to commit this file but I don't really care about it, I've never used it 5 | nvim/lazy-lock.json 6 | book/ 7 | # mdbook theme, only used at build time in CI 8 | docs/theme/ 9 | ublock-mdbook/.direnv 10 | .direnv 11 | 12 | # recyclarr tmp file created by `op inject` 13 | recyclarr-tmp.yml 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dotfiles 2 | 3 | These are my NixOS and macOS dotfiles, packaged as a Nix flake, using [`home-manager`](https://github.com/nix-community/home-manager). 4 | 5 | For info on how to set up my Nix flake, see the [setup instructions](https://mjones.network/my-dotfiles.html). 6 | 7 | ## Caveats 8 | 9 | I try my best to make things work for both macOS and Linux (NixOS), please let me know if something does not work. 10 | -------------------------------------------------------------------------------- /docs/.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | .direnv 3 | -------------------------------------------------------------------------------- /docs/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Mat Jones"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "mjones.network" 7 | 8 | [output.html] 9 | default-theme = "Ayu" 10 | preferred-dark-theme = "Ayu" 11 | additional-css = ["./src/custom.css"] 12 | cname = "mjones.network" 13 | site-url = "https://mjones.network" 14 | 15 | [output.html.print] 16 | enable = false 17 | 18 | [preprocessor.ublock-mdbook] 19 | command = "ublock-mdbook" 20 | -------------------------------------------------------------------------------- /docs/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ublock-mdbook, ... }: 2 | let 3 | highlightJsNix = pkgs.fetchFromGitHub { 4 | owner = "mrjones2014"; 5 | repo = "highlight-js-nix"; 6 | rev = "67aebab"; 7 | hash = "sha256-EmeiP7TAdb7n6KOHJ8qhGzSL0TGkoVuQIkijnrAj5RQ="; 8 | }; 9 | in 10 | pkgs.stdenv.mkDerivation { 11 | pname = "mdbook-docs-site"; 12 | version = "0.1.0"; 13 | src = pkgs.lib.cleanSource ./.; 14 | buildInputs = [ 15 | pkgs.mdbook 16 | ublock-mdbook 17 | ]; 18 | buildPhase = '' 19 | mkdir $out 20 | mdbook build -d $out 21 | rm $out/highlight.js && cp ${highlightJsNix}/highlight.js $out/highlight.js 22 | ublock-mdbook gen-filter-list $out/ublock-filters.txt 23 | ''; 24 | } 25 | -------------------------------------------------------------------------------- /docs/flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1710146030, 9 | "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1714906307, 24 | "narHash": "sha256-UlRZtrCnhPFSJlDQE7M0eyhgvuuHBTe1eJ9N9AQlJQ0=", 25 | "owner": "nixos", 26 | "repo": "nixpkgs", 27 | "rev": "25865a40d14b3f9cf19f19b924e2ab4069b09588", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "nixos", 32 | "ref": "nixos-unstable", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "root": { 38 | "inputs": { 39 | "flake-utils": "flake-utils", 40 | "nixpkgs": "nixpkgs" 41 | } 42 | }, 43 | "systems": { 44 | "locked": { 45 | "lastModified": 1681028828, 46 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 47 | "owner": "nix-systems", 48 | "repo": "default", 49 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "nix-systems", 54 | "repo": "default", 55 | "type": "github" 56 | } 57 | } 58 | }, 59 | "root": "root", 60 | "version": 7 61 | } 62 | -------------------------------------------------------------------------------- /docs/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 4 | flake-utils.url = "github:numtide/flake-utils"; 5 | }; 6 | 7 | outputs = 8 | { nixpkgs, flake-utils, ... }: 9 | flake-utils.lib.eachSystem [ "x86_64-linux" "aarch64-darwin" ] ( 10 | system: 11 | let 12 | pkgs = nixpkgs.legacyPackages.${system}; 13 | ublock-mdbook = import ../ublock-mdbook { inherit pkgs; }; 14 | in 15 | { 16 | packages.default = pkgs.callPackage ./. { 17 | inherit pkgs; 18 | inherit system; 19 | inherit ublock-mdbook; 20 | }; 21 | devShells.default = pkgs.mkShell { 22 | buildInputs = [ 23 | pkgs.mdbook 24 | ublock-mdbook 25 | ]; 26 | }; 27 | } 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /docs/src/README.md: -------------------------------------------------------------------------------- 1 | {{#title mjones.network}} 2 | 3 |
4 | 5 | 6 | 7 | # Hi there, I'm Mat 👋 8 | 9 |
10 |
11 | 12 | I’m currently working as a developer at 1Password. I'm a software engineer with a passion for developer experience, user privacy and sovereignty, open source, Rust, and NixOS. 13 | 14 | My favorite programming languages currently are Rust, Nix, and Lua (for Neovim plugins). I like making Neovim plugins, command line tools, and other developer tools. 15 | 16 | I manage my [dotfiles](https://github.com/mrjones2014/dotfiles) as a Nix flake, and maintain [`legendary.nvim`](https://github.com/mrjones2014/legendary.nvim) and 17 | [`smart-splits.nvim`](https://github.com/mrjones2014/smart-splits.nvim) (Neovim plugins), as well as some other side projects. 18 | 19 | - [Email](mailto:mat@mjones.network) 20 | - [GitHub](https://github.com/mrjones2014) 21 | - [Sponsor](https://github.com/sponsors/mrjones2014) 22 | -------------------------------------------------------------------------------- /docs/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | [README.md](./README.md) 4 | [My Dotfiles](./my-dotfiles.md) 5 | [Useful uBlock Origin Filters](./ublock-filters.md) 6 | 7 | # Articles 8 | 9 | - [Bringing my GitHub workflow into Neovim using 1Password CLI](./articles/github-neovim-1password-cli.md) 10 | - [Declarative Desktop Environment Configuration on NixOS](./articles/declarative-desktop-env-config-with-nix.md) 11 | - [It's 2024 and Linux Isn't Just for Nerds Anymore](./articles/linux-isnt-for-nerds-anymore.md) 12 | -------------------------------------------------------------------------------- /docs/src/articles/declarative-desktop-env-config-with-nix.md: -------------------------------------------------------------------------------- 1 | # Declarative Configuration for Desktop Environments on NixOS 2 | 3 | As soon as I tried out Nix, I wanted to use Nix for _everything._ With Nix, all your configuration is _reproducible_ (as long as you 4 | stay in pure mode, that is), which is amazing if you need to share configurations across devices. 5 | 6 | For the sake of this article, I'll assume you have a working knowledge of Nix and NixOS. 7 | 8 | ## Getting Started 9 | 10 | Building on top of Nix, [`home-manager`](https://github.com/nix-community/home-manager) is great for managing non-global packages 11 | and dotfiles. It also has a module called `dconf` that allows you to declaratively define [`gsettings`](https://wiki.gnome.org/HowDoI/GSettings), a settings system for GNOME and related desktop environments. 12 | 13 | Using the `dconf` module, you can configure everything from your system dark theme, desktop background image, and global keyboard 14 | shortcuts declaratively using Nix. 15 | 16 | First you need to enable `dconf`; in your `/etc/nixos/configuration.nix` or `flake.nix` add: 17 | 18 | ```nix 19 | { pkgs, ... }: { programs.dconf.enable = true; } 20 | ``` 21 | 22 | Then, in your `home-manager` configuration, you can start configuring your desktop environment using `dconf`. 23 | 24 | ```nix 25 | { pkgs, ... }: { 26 | dconf.settings = { 27 | # place your dconf settings here 28 | "some/settings/namespace" = { some-setting = "some-value"; }; 29 | }; 30 | } 31 | ``` 32 | 33 | ## Finding the Correct Settings Namespaces and Keys 34 | 35 | Now that you've enabled `dconf` in your NixOS configuration, we can use `dconf` from the command line to figure out what the 36 | namespaces and keys are for the settings you want to set declaratively in Nix. To do this, open a new terminal window and run 37 | `dconf watch /`. This will start a process which will output any settings value that changes. 38 | 39 | Now, change your desired settings from the settings GUI, and observe the output from `dconf watch /`. For example, 40 | changing your wallpaper image from the settings GUI should output something like: 41 | 42 | ``` 43 | /org/gnome/desktop/background/picture-uri 44 | 'file:///home/mat/.local/share/backgrounds/2023-06-15-14-59-58-wallpaper.jpg' 45 | ``` 46 | 47 | ## Declarative Settings 48 | 49 | With this information, you can set your desktop wallpaper declaratively within your `home-manager` configuration. You can use 50 | `pkgs.fetchurl` to fetch the desired image file and set it as your desktop wallpaper: 51 | 52 | ```nix 53 | { pkgs, ... }: 54 | let 55 | wallpaperImg = pkgs.fetchurl { 56 | url = "https://url/to/your/image"; 57 | # replace this with the SHA256 hash of the image file 58 | hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; 59 | }; 60 | in { 61 | dconf.settings = { 62 | "org/gnome/desktop/background" = { 63 | picture-uri = "file://${wallpaperImg}"; 64 | }; 65 | }; 66 | } 67 | ``` 68 | 69 | With this added, applying your `home-manager` configuration will change your desktop wallpaper! 70 | You can use this method to find just about all the settings you'll need, although some of them might 71 | require some fiddling with. 72 | 73 | For example, creating custom keyboard shortcuts is slightly more complex. First, you have to specify 74 | each custom shortcut group that should exist, then you can specify the shortcuts themselves. For example: 75 | 76 | ```nix 77 | { pkgs, ... }: { 78 | dconf.settings = { 79 | "org/gnome/settings-daemon/plugins/media-keys" = { 80 | # specify that custom shortcut "custom0" should exist and be included 81 | custom-keybindings = [ 82 | "/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom0/" 83 | ]; 84 | terminal = [ "" ]; 85 | }; 86 | # now you can specify the "custom0" shortcut itself 87 | "org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/custom0" = 88 | { 89 | binding = "Print"; # printscrn key 90 | command = "flameshot gui"; 91 | name = "flameshot"; 92 | }; 93 | }; 94 | } 95 | ``` 96 | 97 | Feel free to take a look at all the current settings I use at time of writing [in my dotfiles repo](https://github.com/mrjones2014/dotfiles/tree/e3d1dc94b8cbfd108fa22e9bf58e77dca48bc70c/home-manager/modules/gnome). 98 | -------------------------------------------------------------------------------- /docs/src/articles/linux-isnt-for-nerds-anymore.md: -------------------------------------------------------------------------------- 1 | # Opinion: It's 2024 and Linux Isn't Just for Nerds Anymore 2 | 3 | Linux was once, and to some extent, still is, considered an operating system meant only for tech-savvy individuals and computer enthusiasts. 4 | But, the truth is, Linux is now more user-friendly and easy to use than ever, and there are plenty of reasons you might want to replace Windows 5 | with an alternative operating system. 6 | 7 | ## The Rise of User-Friendly Linux Distributions 8 | 9 | An incredible amount of effort has gone towards making Linux easy to use for everyone, even beginners who aren't comfortable 10 | using the terminal or command line interfaces. Fedora, Mint, Manjaro, and Pop!\_OS (my personal go-to recommendation) are just a few 11 | of many examples of such distributions. Pop!\_OS comes with the Pop! Shop, a graphical software "store" (nearly all of the software there 12 | is free) from which you can install much of the software that basic users might need, without ever touching the command line. 13 | 14 | It's 2024 and today it's completely reasonable to use Linux without ever touching the terminal or any command line interface. 15 | 16 | ## Gaming on Linux: A Massive Leap Forward 17 | 18 | In just the past few years, Valve's development of [Proton](https://github.com/ValveSoftware/Proton) has made incredible progress 19 | for making gaming on Linux incredibly seamless. Simply [enable Steam Play](https://steamcommunity.com/games/221410/announcements/detail/1696055855739350561) 20 | in the Steam client for Linux, and tens or hundreds of thousands of titles will "just work" on Linux. 21 | 22 | Anecdotally, I've been gaming on Linux exclusively, both on desktop and [Steam Deck](https://store.steampowered.com/steamdeck) (the world's greatest 23 | portable gaming device) for several years now, and I can probably count on one hand the number of games I wanted to play that I couldn't get working 24 | on Linux. 25 | 26 | ## A Friendlier Alternative to Microsoft's Windows 27 | 28 | It seems like almost a weekly occurance now that I see some tech news article about a new place that Microsoft has managed to 29 | shove ads into the Windows interface; the [start menu](https://www.theverge.com/2024/4/24/24138949/microsoft-windows-11-start-menu-ads-recommendations-setting-disable), 30 | [system notifications](https://web.archive.org/web/20231118121625/https://old.reddit.com/r/assholedesign/comments/16opnfo/windows_11_gives_you_ads_as_notifications_in_the/), 31 | even _[full screen popup ads on your desktop!](https://www.extremetech.com/computing/microsoft-displaying-full-screen-windows-11-ads-in-windows-10)_ 32 | 33 | With this knowledge, it will probably come as no surprise that Windows is spying on you in order to serve you these targeted ads; by default, 34 | Windows reports any time you launch an app to Microsoft via a "feature" called "Windows Activity History". In fact, there is so much of this 35 | type of spyware in Windows (what Microsoft calls "telemetry") that privacy tools like [NextDNS](https://nextdns.io/), a privacy and security 36 | enhancing DNS server, offer native Windows tracker blocking functionality. 37 | 38 | ![NextDNS Windows Tracker Blocking](https://github.com/mrjones2014/dotfiles/assets/8648891/d224e6d8-d429-4454-a5d4-c6e88da42997) 39 | 40 | The Free Software Foundation even has [a page dedicated to all the pieces of malware Microsoft has introduced as "features" to Windows over the years](https://www.gnu.org/proprietary/malware-microsoft.html). 41 | 42 | > "If you do want to clean your computer of malware, the first software to delete is Windows." - _The Free Software Foundation_ 43 | 44 | Most Linux distrubutions, by contrast, give the user full freedom and control over their system; it doesn't abuse the user, doesn't spam them 45 | with ads on the desktop of their own computer, and doesn't share your app activity with Microsoft. It's important to note, however, 46 | that not all Linux distributions are created equal. 47 | 48 | For example, I highly recommend _against_ using Ubuntu, as the developer, Canonical, 49 | has lost user trust by behaving in a similar manner to Microsoft; the most infamous incident is a "feature" called 50 | [Amazon Lens](https://www.omgubuntu.co.uk/2012/11/how-to-install-a-dedicated-amazon-shopping-lens-in-ubuntu) that, by default, 51 | sends what you type into the system launcher (basically like Linux's version of the start menu) to Amazon. While I used to recommend 52 | Ubuntu as the best beginner-friendly Linux distribution, over the years I've changed my recommendation to [Pop!\_OS](https://pop.system76.com/). 53 | 54 | --- 55 | 56 | It's clear that as we enter 2024 and beyond, Linux isn't just a niche operating system for nerds anymore. 57 | Today, there are plenty of beginner-friendly, very easy to use graphical Linux distributions to choose from 58 | to take back your freedom and privacy from Microsoft. You don't have to tolerate their abuse anymore; using Linux 59 | is easy, even for beginners. 60 | 61 | It's time to end your abusive relationship with Microsoft. 62 | -------------------------------------------------------------------------------- /docs/src/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjones2014/dotfiles/412867a9f6b0e7e40ad5ea5f8d22c3e2102de6b8/docs/src/avatar.png -------------------------------------------------------------------------------- /docs/src/custom.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --content-max-width: 900px; 3 | } 4 | -------------------------------------------------------------------------------- /docs/src/my-dotfiles.md: -------------------------------------------------------------------------------- 1 | # Installing on a New System 2 | 3 | ```bash 4 | mkdir -p "$HOME/git" 5 | cd "$HOME/git" 6 | git clone git@github.com:mrjones2014/dotfiles.git 7 | ``` 8 | 9 | ## NixOS 10 | 11 | If you're installing on a server, see the [NixOS manual](https://nixos.org/manual/nixos/stable/#ch-installation) for headless NixOS installation instructions first. 12 | Once you've got your disks partitioned and NixOS installed, come back here and continue. For desktop computers, you can just use the graphical GNOME NixOS installer, 13 | since we'll be installing GNOME anyway. 14 | 15 | Simply run `NIX_CONFIG="experimental-features = nix-command flakes" sudo nixos-rebuild switch ~/git/dotfiles/.#pc` (replacing `.#pc` with your desired flake output target). 16 | After the first time, you can just run `sudo nixos-rebuild switch --flake $HOME/git/dotfiles/.#pc` (or the `nix-apply` shell alias). 17 | 18 | ## macOS 19 | 20 | On macOS, for the first install, you'll need to run `nix-darwin` via `nix run`: 21 | 22 | ```bash 23 | nix run nix-darwin/master#darwin-rebuild -- switch --extra-experimental-features "nix-command flakes" --flake $HOME/git/dotfiles 24 | ``` 25 | 26 | After that, you can just run `darwin-rebuild switch --flake $HOME/git/dotfiles` (or the `nix-apply` shell alias). 27 | 28 | ## Managing Dotfiles 29 | 30 | Dotfiles are managed via [Nix](https://nixos.org/), using `flake.nix` and [`home-manager`](https://github.com/nix-community/home-manager). 31 | On NixOS, the `home-manager` configuration is managed as a NixOS module, so simply rebuilding your NixOS config also applies the new 32 | `home-manager` config. On macOS, the same behavior is achieved via [nix-darwin](https://github.com/nix-darwin/nix-darwin). 33 | -------------------------------------------------------------------------------- /docs/src/special-characters.md: -------------------------------------------------------------------------------- 1 | # Special Unicode Characters 2 | 3 | I can never find these characters to copy/paste so here they are. 4 | 5 | - chevron right:  6 | - chevron left:  7 | - block: █ 8 | - terminal icon:  9 | - circumscribed arrow:  10 | - vim icon:  11 | - git branch icon:  12 | - lock icon:  13 | - border characters: "─", "│", "─", "│", "╭", "╮", "╯", "╰" 14 | 15 | See also: 16 | 17 | - [ShapeCatcher](https://shapecatcher.com) 18 | - [Image to ASCII](https://505e06b2.github.io/Image-to-Braille/) 19 | -------------------------------------------------------------------------------- /docs/src/ublock-filters.md: -------------------------------------------------------------------------------- 1 | # Useful uBlock Origin Filters 2 | 3 | Have some useful filters you'd like to contribute? Please [add them here](https://github.com/mrjones2014/dotfiles/edit/master/ublock-mdbook/src/ublock-filters.yml)! 4 | 5 |
6 | Expand to copy all filters 7 | 8 | {{#ublockfilters-all}} 9 | 10 |
11 | 12 | Or add the full list as a filter list in uBlock Origin: [ublock-filters.txt](https://mjones.network/ublock-filters.txt) 13 | 14 | {{#ublockfilters-toc}} 15 | 16 | {{#ublockfilters}} 17 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "My dotfiles managed with nix as a flake"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 6 | tokyonight.url = "github:mrjones2014/tokyonight.nix"; 7 | zjstatus.url = "github:dj95/zjstatus"; 8 | nix-darwin = { 9 | url = "github:nix-darwin/nix-darwin/master"; 10 | inputs.nixpkgs.follows = "nixpkgs"; 11 | }; 12 | home-manager = { 13 | url = "github:nix-community/home-manager"; 14 | inputs.nixpkgs.follows = "nixpkgs"; 15 | }; 16 | _1password-shell-plugins = { 17 | url = "github:1Password/shell-plugins"; 18 | inputs.nixpkgs.follows = "nixpkgs"; 19 | }; 20 | agenix = { 21 | url = "github:ryantm/agenix"; 22 | inputs.nixpkgs.follows = "nixpkgs"; 23 | }; 24 | zen-browser = { 25 | url = "github:0xc000022070/zen-browser-flake"; 26 | inputs.nixpkgs.follows = "nixpkgs"; 27 | }; 28 | }; 29 | 30 | outputs = 31 | inputs@{ 32 | nixpkgs, 33 | nix-darwin, 34 | home-manager, 35 | agenix, 36 | ... 37 | }: 38 | { 39 | nixosConfigurations = { 40 | server = nixpkgs.lib.nixosSystem { 41 | specialArgs = { 42 | inherit inputs; 43 | isServer = true; 44 | isLinux = true; 45 | isThinkpad = false; 46 | isDarwin = false; 47 | }; 48 | system = "x86_64-linux"; 49 | modules = [ 50 | home-manager.nixosModules.home-manager 51 | agenix.nixosModules.default 52 | { 53 | environment.systemPackages = [ agenix.packages.x86_64-linux.default ]; 54 | } 55 | ./nixos/common.nix 56 | ./hosts/server 57 | { 58 | home-manager = { 59 | backupFileExtension = "backup"; 60 | useUserPackages = true; 61 | users.mat = import ./home-manager/server.nix; 62 | extraSpecialArgs = { 63 | inherit inputs; 64 | isServer = true; 65 | isLinux = true; 66 | isThinkpad = false; 67 | isDarwin = false; 68 | }; 69 | }; 70 | } 71 | ]; 72 | }; 73 | pc = nixpkgs.lib.nixosSystem { 74 | specialArgs = { 75 | inherit inputs; 76 | isServer = false; 77 | isDarwin = false; 78 | isLinux = true; 79 | isThinkpad = false; 80 | }; 81 | system = "x86_64-linux"; 82 | modules = [ 83 | ./nixos/common.nix 84 | ./hosts/pc 85 | home-manager.nixosModules.home-manager 86 | { 87 | home-manager = { 88 | backupFileExtension = "backup"; 89 | useUserPackages = true; 90 | users.mat = import ./home-manager/home.nix; 91 | extraSpecialArgs = { 92 | inherit inputs; 93 | isServer = false; 94 | isDarwin = false; 95 | isLinux = true; 96 | isThinkpad = false; 97 | }; 98 | }; 99 | } 100 | ]; 101 | }; 102 | laptop = nixpkgs.lib.nixosSystem { 103 | specialArgs = { 104 | inherit inputs; 105 | isServer = false; 106 | isDarwin = false; 107 | isLinux = true; 108 | isThinkpad = true; 109 | }; 110 | system = "x86_64-linux"; 111 | modules = [ 112 | ./nixos/common.nix 113 | ./hosts/laptop 114 | home-manager.nixosModules.home-manager 115 | { 116 | home-manager = { 117 | backupFileExtension = "backup"; 118 | useUserPackages = true; 119 | users.mat = import ./home-manager/home.nix; 120 | extraSpecialArgs = { 121 | inherit inputs; 122 | isServer = false; 123 | isDarwin = false; 124 | isLinux = true; 125 | isThinkpad = true; 126 | }; 127 | }; 128 | } 129 | ]; 130 | }; 131 | }; 132 | darwinConfigurations."Mats-MacBook-Pro" = 133 | let 134 | specialArgs = { 135 | inherit inputs; 136 | isServer = false; 137 | isDarwin = true; 138 | isLinux = false; 139 | isThinkpad = false; 140 | }; 141 | in 142 | nix-darwin.lib.darwinSystem { 143 | inherit specialArgs; 144 | pkgs = nixpkgs.legacyPackages."aarch64-darwin"; 145 | modules = [ 146 | ./hosts/darwin 147 | home-manager.darwinModules.default 148 | { 149 | home-manager = { 150 | users.mat = import ./home-manager/home.nix; 151 | extraSpecialArgs = specialArgs; 152 | }; 153 | } 154 | ]; 155 | }; 156 | }; 157 | } 158 | -------------------------------------------------------------------------------- /home-manager/components/_1password-shell.nix: -------------------------------------------------------------------------------- 1 | { inputs, pkgs, ... }: 2 | let 3 | op_sudo_password_script = pkgs.writeScript "opsudo.bash" '' 4 | #!${pkgs.bash}/bin/bash 5 | # TODO figure out a way to do this without silently depending on `op` being on $PATH 6 | # using `$\{pkgs._1password}/bin/op` results in unable to connect to desktop app 7 | PASSWORD="$(op read "op://Private/System Password/password")" 8 | if [[ -z "$PASSWORD" ]]; then 9 | echo "Failed to get password from 1Password." 10 | read -s -p "Password: " PASSWORD 11 | fi 12 | 13 | echo $PASSWORD 14 | ''; 15 | in 16 | { 17 | home.packages = with pkgs; [ _1password-cli ]; 18 | imports = [ inputs._1password-shell-plugins.hmModules.default ]; 19 | programs = { 20 | fish = { 21 | interactiveShellInit = '' 22 | export SUDO_ASKPASS="${op_sudo_password_script}" 23 | alias sudo="sudo -A" 24 | ''; 25 | }; 26 | _1password-shell-plugins = { 27 | enable = true; 28 | plugins = with pkgs; [ 29 | gh 30 | glab 31 | ]; 32 | }; 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /home-manager/components/discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjones2014/dotfiles/412867a9f6b0e7e40ad5ea5f8d22c3e2102de6b8/home-manager/components/discord.png -------------------------------------------------------------------------------- /home-manager/components/espanso.nix: -------------------------------------------------------------------------------- 1 | { isServer, ... }: 2 | { 3 | services.espanso = { 4 | enable = !isServer; 5 | configs.default = { 6 | search_shortcut = "OFF"; 7 | show_notifications = false; 8 | }; 9 | matches = { 10 | base = { 11 | matches = [ 12 | { 13 | trigger = ":shrug"; 14 | replace = "¯\\_(ツ)_/¯"; 15 | } 16 | { 17 | trigger = ":tflip"; 18 | replace = "(╯°□°)╯︵ ┻━┻"; 19 | } 20 | 21 | { 22 | trigger = ":fingerguns"; 23 | replace = "(☞ ͡° ͜ʖ ͡°)☞"; 24 | } 25 | ]; 26 | }; 27 | }; 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /home-manager/components/fzf.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | lib, 4 | ... 5 | }: 6 | let 7 | key-bindings = [ 8 | # you can use the command `fish_key_reader` to get the key codes to use 9 | { 10 | lhs = "ctrl-e"; 11 | rhs = "fzf-vim-widget"; 12 | } 13 | { 14 | lhs = "ctrl-g"; 15 | rhs = "fzf-project-widget"; 16 | } 17 | { 18 | lhs = "ctrl-f"; 19 | rhs = "fzf-file-widget-wrapped"; 20 | } 21 | { 22 | lhs = "ctrl-r"; 23 | rhs = "fzf-history-widget-wrapped"; 24 | } 25 | { 26 | lhs = "ctrl-p"; 27 | rhs = "fzf-jj-bookmarks"; 28 | } 29 | ]; 30 | in 31 | { 32 | programs.fzf = { 33 | enable = true; 34 | defaultCommand = "${pkgs.ripgrep}/bin/rg --files"; 35 | defaultOptions = [ 36 | "--prompt=' '" 37 | "--marker=''" 38 | "--marker=' '" 39 | # this keybind should match the telescope ones in nvim config 40 | ''--bind="ctrl-d:preview-down,ctrl-f:preview-up"'' 41 | ]; 42 | fileWidgetCommand = "${pkgs.ripgrep}/bin/rg --files"; 43 | fileWidgetOptions = [ 44 | # Preview files with bat 45 | "--preview '${pkgs.bat}/bin/bat --color=always {}'" 46 | "--layout default" 47 | ]; 48 | }; 49 | programs.fish = { 50 | interactiveShellInit = '' 51 | for mode in insert default normal 52 | ${lib.concatMapStrings (keybind: '' 53 | bind -M $mode ${keybind.lhs} ${keybind.rhs} 54 | '') key-bindings} 55 | end 56 | ''; 57 | functions = { 58 | 59 | fzf-history-widget-wrapped = '' 60 | history merge # make FZF search history from all sessions 61 | fzf-history-widget 62 | _prompt_move_to_bottom 63 | ''; 64 | fzf-file-widget-wrapped = '' 65 | fzf-file-widget 66 | _prompt_move_to_bottom 67 | ''; 68 | fzf-project-widget = '' 69 | function _project_jump_get_icon 70 | set -l remote "$(git --work-tree $argv[1] --git-dir $argv[1]/.git ls-remote --get-url 2> /dev/null)" 71 | if string match -r "github.com" "$remote" >/dev/null 72 | set_color --bold normal 73 | echo -n  74 | else if string match -r gitlab "$remote" >/dev/null 75 | set_color --bold FC6D26 76 | echo -n  77 | else 78 | set_color --bold F74E27 79 | echo -n 󰊢 80 | end 81 | end 82 | 83 | function _project_jump_format_project 84 | set -l repo "$HOME/git/$argv[1]" 85 | set -l branch $(git --work-tree $repo --git-dir $repo/.git branch --show-current) 86 | set_color --bold cyan 87 | echo -n "$argv[1]" 88 | echo -n " $(_project_jump_get_icon $repo)" 89 | set_color --bold f74e27 90 | echo " $branch" 91 | end 92 | 93 | function _project_jump_parse_project 94 | # check args 95 | set -f selected $argv[1] 96 | 97 | # if not coming from args 98 | if [ "$selected" = "" ] 99 | # check pipe 100 | read -f selected 101 | end 102 | 103 | # if still empty, return 104 | if [ "$selected" = "" ] 105 | return 106 | end 107 | 108 | set -l dir $(string trim "$(string match -r ".*(?=\s*󰊢||)" "$selected")") 109 | echo "$HOME/git/$dir" 110 | end 111 | 112 | function _project_jump_get_projects 113 | # make sure to use built-in ls, not exa 114 | for dir in $(command ls "$HOME/git") 115 | if test -d "$HOME/git/$dir" 116 | echo "$(_project_jump_format_project $dir)" 117 | end 118 | end 119 | end 120 | 121 | function _project_jump_get_readme 122 | set -l dir $(_project_jump_parse_project "$argv[1]") 123 | if test -f "$dir/README.md" 124 | CLICOLOR_FORCE=1 COLORTERM=truecolor ${pkgs.glow}/bin/glow -p -s dark -w 150 "$dir/README.md" 125 | else 126 | echo 127 | echo $(set_color --bold) "README.md not found" 128 | echo 129 | ${pkgs.lsd}/bin/lsd -F $dir 130 | end 131 | end 132 | 133 | argparse 'format=' -- $argv 134 | if set -ql _flag_format 135 | _project_jump_get_readme $_flag_format 136 | else 137 | set -l selected $(_project_jump_get_projects | fzf --ansi --preview-window 'right,70%' --preview "fzf-project-widget --format {}") 138 | if test -n "$selected" 139 | cd $(_project_jump_parse_project "$selected") 140 | end 141 | commandline -f repaint 142 | end 143 | ''; 144 | fzf-vim-widget = '' 145 | # modified from fzf-file-widget 146 | set -l commandline $(__fzf_parse_commandline) 147 | set -l dir $commandline[1] 148 | set -l fzf_query $commandline[2] 149 | set -l prefix $commandline[3] 150 | 151 | # "-path \$dir'*/\\.*'" matches hidden files/folders inside $dir but not 152 | # $dir itself, even if hidden. 153 | test -n "$FZF_CTRL_T_COMMAND"; or set -l FZF_CTRL_T_COMMAND " 154 | command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \ 155 | -o -type f -print \ 156 | -o -type d -print \ 157 | -o -type l -print 2> /dev/null | sed 's@^\./@@'" 158 | 159 | test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40% 160 | begin 161 | set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" 162 | eval "$FZF_CTRL_T_COMMAND | "(__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r 163 | set result $result $r 164 | end 165 | end 166 | if [ -z "$result" ] 167 | _prompt_move_to_bottom 168 | commandline -f repaint 169 | return 170 | end 171 | set -l filepath_result 172 | for i in $result 173 | set filepath_result "$filepath_result$prefix" 174 | set filepath_result "$filepath_result$(string escape $i)" 175 | set filepath_result "$filepath_result " 176 | end 177 | _prompt_move_to_bottom 178 | commandline -f repaint 179 | $EDITOR $result 180 | ''; 181 | fzf-jj-bookmarks = '' 182 | set -l selected_bookmark (jj bookmark list | fzf --height 40%) 183 | if test -n "$selected_bookmark" 184 | # parse the bookmark name out of the full bookmark info line 185 | set -l bookmark_name (string split ":" "$selected_bookmark" | head -n 1 | string trim) 186 | commandline -i " $bookmark_name " 187 | end 188 | commandline -f repaint 189 | ''; 190 | }; 191 | }; 192 | } 193 | -------------------------------------------------------------------------------- /home-manager/components/git.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | isDarwin, 4 | isLinux, 5 | isServer, 6 | ... 7 | }: 8 | let 9 | git_checkout_fzf_script = pkgs.writeScript "git-ch.bash" '' 10 | #!${pkgs.bash}/bin/bash 11 | if test "$#" -ne 0; then 12 | if [[ "$*" = "master" ]] || [[ "$*" = "main" ]]; then 13 | git checkout "$(git branch --format '%(refname:short)' --sort=-committerdate --list master main | head -n1)" 14 | else 15 | git checkout "$@" 16 | fi 17 | else 18 | git branch -a --format="%(refname:short)" | sed 's|origin/||g' | grep -v "HEAD" | grep -v "origin" | sort | uniq | ${pkgs.fzf}/bin/fzf | xargs git checkout 19 | fi 20 | ''; 21 | in 22 | { 23 | programs.git = { 24 | enable = true; 25 | package = pkgs.git.override { 26 | guiSupport = false; # gui? never heard of her. 27 | }; 28 | ignores = [ 29 | "Session.vim" 30 | ".DS_Store" 31 | ".direnv/" 32 | ]; 33 | aliases = { 34 | s = "status"; 35 | newbranch = "checkout -b"; 36 | commit-amend = "commit --amend --no-edit"; 37 | prune-branches = ''!git branch --merged | grep -v \"master\" | grep -v \"main\" | grep -v \"$(git branch --show-current)\" | grep -v "[*]" >/tmp/merged-branches && vim /tmp/merged-branches && xargs git branch -d l" ]; 58 | # left 1/3rd of screen 59 | resize2 = "11x1 1:1 4:1"; 60 | preset-resize-2 = [ "h" ]; 61 | # center 2/3rds of screen 62 | resize3 = "5x1 2:1 4:1"; 63 | preset-resize-3 = [ "k" ]; 64 | }; 65 | "org/gnome/shell/extensions/dash-to-dock" = { 66 | dash-max-icon-size = 48; 67 | intellihide-mode = "ALL_WINDOWS"; 68 | disable-overview-on-startup = true; 69 | show-show-apps-button = false; 70 | running-indicator-style = "DOTS"; 71 | apply-custom-theme = false; 72 | }; 73 | "org/gnome/shell/extensions/trayIconsReloaded".icons-limit = 10; 74 | "org/gnome/shell/extensions/quick-settings-tweaks" = { 75 | output-show-selected = true; 76 | input-show-selected = true; 77 | input-always-show = true; 78 | volume-mixer-enabled = true; 79 | }; 80 | "org/gnome/desktop/background" = { 81 | picture-uri = "file://${wallpaperImg}"; 82 | picture-uri-dark = "file://${wallpaperImg}"; 83 | }; 84 | "org/gnome/desktop/datetime".automatic-timezone = true; 85 | "org/gnome/desktop/wm/keybindings" = { 86 | move-to-monitor-left = [ "h" ]; 87 | move-to-monitor-right = [ "l" ]; 88 | move-to-monitor-up = [ "k" ]; 89 | move-to-monitor-down = [ "j" ]; 90 | }; 91 | "org/gnome/desktop/peripherals/touchpad".send-events = "enabled"; 92 | "org/gnome/shell/extensions/search-light" = { 93 | shortcut-search = [ "space" ]; 94 | }; 95 | "org/gnome/mutter/keybindings" = { 96 | switch-monitor = [ ]; # disable stupid ass default +p defautl shortcut 97 | }; 98 | }; 99 | } 100 | -------------------------------------------------------------------------------- /home-manager/components/gnome/default.nix: -------------------------------------------------------------------------------- 1 | { isLinux, lib, ... }: 2 | { } 3 | // lib.attrsets.optionalAttrs isLinux { 4 | imports = [ ./dconf.nix ]; 5 | xdg.configFile = { 6 | # workaround for https://github.com/nix-community/home-manager/issues/3447 7 | # autostart 1Password in the background so I can use the SSH agent without manually opening the app first 8 | "autostart/1password.desktop".text = '' 9 | [Desktop Entry] 10 | Name=1Password 11 | Exec=1password --silent 12 | Terminal=false 13 | Type=Application 14 | Icon=1password 15 | StartupWMClass=1Password 16 | Comment=Password manager and secure wallet 17 | MimeType=x-scheme-handler/onepassword; 18 | Categories=Office; 19 | ''; 20 | # autostart Signal 21 | "autostart/signal-desktop.desktop".text = '' 22 | [Desktop Entry] 23 | Name=Signal 24 | Exec=signal-desktop --no-sandbox --start-in-tray %U 25 | Terminal=false 26 | Type=Application 27 | Icon=signal-desktop 28 | StartupWMClass=Signal 29 | Comment=Private messaging from your desktop 30 | MimeType=x-scheme-handler/sgnl;x-scheme-handler/signalcaptcha; 31 | Categories=Network;InstantMessaging;Chat; 32 | ''; 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /home-manager/components/jujutsu.nix: -------------------------------------------------------------------------------- 1 | { config, lib, ... }: 2 | let 3 | palette = import ./tokyonight_palette.nix { inherit lib; }; 4 | in 5 | { 6 | programs.jujutsu = { 7 | enable = true; 8 | settings = { 9 | git = { 10 | private-commits = lib.mkDefault "description(glob:'wip:*') | description(glob:'private:*')"; 11 | push-bookmark-prefix = lib.mkDefault "mrj/"; 12 | }; 13 | revset-aliases = lib.mkDefault { 14 | # The `trunk().. &` bit is an optimization to scan for non-`mine()` commits 15 | # only among commits that are not in `trunk()` 16 | # This prevents me from mutating any commit that isn't authored by me 17 | "immutable_heads()" = "builtin_immutable_heads() | (trunk().. & ~mine())"; 18 | }; 19 | colors = { 20 | # lighten these up a bit for statusline integration 21 | "change_id rest" = palette.dark3; 22 | "commit_id rest" = palette.dark3; 23 | }; 24 | aliases = { 25 | # https://shaddy.dev/notes/jj-tug/ 26 | tug = [ 27 | "bookmark" 28 | "move" 29 | "--from" 30 | "heads(::@- & bookmarks())" 31 | "--to" 32 | "@-" 33 | ]; 34 | }; 35 | user = { 36 | name = config.programs.git.userName; 37 | email = config.programs.git.userEmail; 38 | }; 39 | signing = { 40 | inherit (config.programs.git.signing) key; 41 | behavior = "force"; 42 | backend = "ssh"; 43 | backends.ssh.program = config.programs.git.extraConfig.gpg.ssh.program; 44 | }; 45 | }; 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /home-manager/components/nvim.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | pkgs, 4 | isLinux, 5 | ... 6 | }: 7 | { 8 | home.sessionVariables.MANPAGER = "nvim -c 'Man!' -o -"; 9 | xdg.configFile = { 10 | ripgrep_ignore.text = '' 11 | .git/ 12 | yarn.lock 13 | package-lock.json 14 | packer_compiled.lua 15 | .DS_Store 16 | .netrwhist 17 | dist/ 18 | node_modules/ 19 | **/node_modules/ 20 | wget-log 21 | wget-log.* 22 | /vendor 23 | ''; 24 | nvim = { 25 | source = config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/git/dotfiles/nvim"; 26 | recursive = true; 27 | }; 28 | }; 29 | 30 | programs.neovim = { 31 | enable = true; 32 | viAlias = true; 33 | vimAlias = true; 34 | vimdiffAlias = true; 35 | withNodeJs = false; 36 | withRuby = false; 37 | withPython3 = false; 38 | defaultEditor = true; 39 | coc.enable = false; 40 | 41 | extraWrapperArgs = 42 | [ 43 | "--set" 44 | "NVIM_RUST_ANALYZER" 45 | "${pkgs.rust-analyzer}/bin/rust-analyzer" 46 | "--set" 47 | "LIBSQLITE" 48 | ] 49 | ++ ( 50 | if isLinux then 51 | [ "${pkgs.sqlite.out}/lib/libsqlite3.so" ] 52 | else 53 | [ "${pkgs.sqlite.out}/lib/libsqlite3.dylib" ] 54 | ); 55 | 56 | extraLuaPackages = ps: [ ps.jsregexp ]; 57 | extraPackages = with pkgs; [ 58 | # for compiling Treesitter parsers 59 | gcc 60 | 61 | # debuggers 62 | lldb # comes with lldb-vscode 63 | 64 | # formatters and linters 65 | nixfmt-rfc-style 66 | rustfmt 67 | shfmt 68 | stylua 69 | statix 70 | luajitPackages.luacheck 71 | prettierd 72 | 73 | # LSP servers 74 | nixd 75 | nil 76 | rust-analyzer 77 | cargo # sometimes required for rust-analyzer to work 78 | taplo 79 | gopls 80 | lua 81 | shellcheck 82 | marksman 83 | sumneko-lua-language-server 84 | nodePackages_latest.typescript-language-server 85 | yaml-language-server 86 | bash-language-server 87 | 88 | # this includes css-lsp, html-lsp, json-lsp, eslint-lsp 89 | nodePackages_latest.vscode-langservers-extracted 90 | 91 | # other utils and plugin dependencies 92 | gnumake 93 | src-cli 94 | ripgrep 95 | fd 96 | sqlite 97 | lemmy-help 98 | fzf 99 | cargo 100 | cargo-nextest 101 | clippy 102 | glow 103 | mariadb 104 | imagemagick 105 | ]; 106 | }; 107 | } 108 | -------------------------------------------------------------------------------- /home-manager/components/recyclarr.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | { 3 | home.packages = [ 4 | pkgs.recyclarr 5 | (pkgs.writeScriptBin "recyclarr-sync" '' 6 | op inject -i ~/git/dotfiles/conf.d/recyclarr.yml -o ~/git/dotfiles/recyclarr-tmp.yml \ 7 | && recyclarr sync --config ~/git/dotfiles/recyclarr-tmp.yml 8 | rm -f ~/git/dotfiles/recyclarr-tmp.yml 9 | '') 10 | ]; 11 | } 12 | -------------------------------------------------------------------------------- /home-manager/components/ssh.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | isLinux, 4 | isServer, 5 | lib, 6 | ... 7 | }: 8 | let 9 | sshAuthSock = "${config.home.homeDirectory}/${ 10 | if isLinux then ".1password" else "Library/Group Containers/2BUA8C4S2C.com.1password/t" 11 | }/agent.sock"; 12 | in 13 | { 14 | home.sessionVariables = { } // lib.optionalAttrs (!isServer) { SSH_AUTH_SOCK = sshAuthSock; }; 15 | programs.ssh = { 16 | enable = true; 17 | forwardAgent = true; 18 | matchBlocks = 19 | { } 20 | // lib.optionalAttrs (!isServer) { 21 | # allow SSH_AUTH_SOCK to be forwarded on server from SSH client 22 | "*".extraOptions.IdentityAgent = ''"${sshAuthSock}"''; 23 | }; 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /home-manager/components/starship.nix: -------------------------------------------------------------------------------- 1 | { lib, isServer, ... }: 2 | let 3 | palette = import ./tokyonight_palette.nix { inherit lib; }; 4 | 5 | git_bg = palette.fg_gutter; 6 | git_bg_ansi = palette.hexToAnsiRgb git_bg; 7 | git_fg = palette.fg; 8 | dir_bg = palette.dark5; 9 | server_bg = palette.teal; 10 | server_sep = color: if isServer then "[](fg:${server_bg} bg:${color})" else ""; 11 | in 12 | with palette; 13 | { 14 | programs.starship = { 15 | enable = true; 16 | enableTransience = true; 17 | settings = { 18 | command_timeout = 200; 19 | format = lib.concatStrings [ 20 | ( 21 | if isServer then 22 | "[ ](bg:${server_bg})[](bg:${server_bg} fg:${bg_dark})[ ](bg:${server_bg})" 23 | else 24 | "" 25 | ) 26 | "$character" 27 | "$directory" 28 | "\${env_var.IN_NIX_SHELL}" 29 | "\${custom.dir_sep_no_git}" 30 | "\${custom.git_server_icon}" 31 | "$git_branch" 32 | "\${custom.jj_log}" 33 | "$git_status" 34 | "$line_break" 35 | "$shlvl" 36 | "[❯](bg:${bg_dark} fg:${green}) " 37 | ]; 38 | right_format = "$cmd_duration"; 39 | shlvl = { 40 | disabled = false; 41 | symbol = "❯"; 42 | format = "[$symbol]($style)"; 43 | repeat = true; 44 | repeat_offset = 1; 45 | threshold = 0; 46 | }; 47 | character = { 48 | format = "$symbol"; 49 | success_symbol = "${server_sep green}[  ](bold bg:${green} fg:${bg_dark})[](fg:${green} bg:${dir_bg})"; 50 | error_symbol = "${server_sep red}[  ](bold bg:${red} fg:${bg_dark})[](fg:${red} bg:${dir_bg})"; 51 | vicmd_symbol = "${server_sep blue}[  ](bold bg:${blue} fg:${bg_dark})[](fg:${blue} bg:${dir_bg})"; 52 | vimcmd_replace_one_symbol = "${server_sep purple}[  ](bold bg:${purple} fg:${bg_dark})[](fg:${purple} bg:${dir_bg})"; 53 | vimcmd_replace_symbol = "${server_sep purple}[  ](bold bg:${purple} fg:${bg_dark})[](fg:${purple} bg:${dir_bg})"; 54 | vimcmd_visual_symbol = "${server_sep yellow}[  ](bold bg:${yellow} fg:${bg_dark})[](fg:${yellow} bg:${dir_bg})"; 55 | }; 56 | cmd_duration.format = "[ $duration](bold ${dark3})"; 57 | directory.format = "[ ](bg:${dir_bg})[$path](bold bg:${dir_bg} fg:${green})"; 58 | env_var.IN_NIX_SHELL.format = "[ ](bg:${dir_bg})[](bg:${dir_bg} fg:${blue5})[ ](bg:${dir_bg})"; 59 | git_status.format = "[$all_status$ahead_behind](bg:${git_bg} fg:${yellow})[](fg:${git_bg} bg:${bg_dark})"; 60 | git_branch = { 61 | format = "([ ](bg:${git_bg})[$branch](bg:${git_bg} fg:${git_fg})[ ](bg:${git_bg}))"; 62 | # detatched HEAD mode means probably we're using jj 63 | ignore_branches = [ "HEAD" ]; 64 | }; 65 | custom = { 66 | jj_log = { 67 | description = "Show info from jj about current change set"; 68 | when = true; 69 | require_repo = true; 70 | # NB: --ignore-working-copy, do not snapshot the repo when generating status for prompt, its too slow and not necessary; 71 | # all my git operations/changes will be from manually run jj commands which will snapshot the repo at that time 72 | # The `sed` part is to modify the ANSI color codes so that the background color matches 73 | # I'll need to update this 74 | command = ''jj log --ignore-working-copy -r @- -n 1 --no-graph --no-pager --color always -T "separate(' ', format_short_change_id(self.change_id()), self.bookmarks())" | sed "s/\x1b\[\([0-9;]*\)m/\x1b[\1;48;2;${git_bg_ansi}m/g"''; 75 | # Render ANSI colors directly from the `jj log` output 76 | unsafe_no_escape = true; 77 | format = "([ ](bg:${git_bg})[$output](bg:${git_bg})[ ](bg:${git_bg}))"; 78 | shell = [ 79 | "bash" 80 | "--noprofile" 81 | "--norc" 82 | ]; 83 | }; 84 | git_server_icon = { 85 | description = "Show a GitLab or GitHub icon depending on current git remote"; 86 | when = "git rev-parse --is-inside-work-tree 2> /dev/null"; 87 | command = ''GIT_REMOTE=$(git ls-remote --get-url 2> /dev/null); if [[ "$GIT_REMOTE" =~ "github" ]]; then printf ""; elif [[ "$GIT_REMOTE" =~ "gitlab" ]]; then echo "󰮠"; else echo "󰊢"; fi''; 88 | shell = [ 89 | "bash" 90 | "--noprofile" 91 | "--norc" 92 | ]; 93 | format = "([](fg:${dir_bg} bg:${git_bg})[ ](bg:${git_bg})[$output](bg:${git_bg} fg:${git_fg})[ ](bg:${git_bg}))"; 94 | }; 95 | dir_sep_no_git = { 96 | description = "Show rounded separator when not in a git repo"; 97 | format = "[](fg:${dir_bg} bg:${bg_dark})"; 98 | when = "! git rev-parse --is-inside-work-tree 2> /dev/null"; 99 | shell = [ 100 | "bash" 101 | "--noprofile" 102 | "--norc" 103 | ]; 104 | }; 105 | }; 106 | }; 107 | }; 108 | } 109 | -------------------------------------------------------------------------------- /home-manager/components/terminal.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | config, 4 | isThinkpad, 5 | isDarwin, 6 | ... 7 | }: 8 | { 9 | imports = [ ./zellij.nix ]; 10 | home.packages = [ pkgs.victor-mono ]; 11 | programs.ghostty = { 12 | enable = true; 13 | # TODO remove eventually; Ghostty nixpkgs is broken 14 | # on macOS but I still want to generate the config with nix. 15 | # I'll install manually on macOS and use Nix to generate the config. 16 | # For now, shim the package with pkgs.emptyDirectory to trick nix 17 | # into still generating the config. 18 | package = if isDarwin then null else pkgs.ghostty; 19 | installBatSyntax = false; 20 | clearDefaultKeybinds = true; 21 | settings = { 22 | title = "Ghostty"; 23 | "font-family" = "Victor Mono Semibold"; 24 | "font-family-italic" = "Victor Mono Medium Oblique"; 25 | "font-family-bold-italic" = "Victor Mono Bold Oblique"; 26 | "font-feature" = "ss02,ss06"; 27 | "font-size" = "16"; 28 | "cursor-style" = "block"; 29 | "cursor-style-blink" = false; 30 | "macos-option-as-alt" = true; 31 | "shell-integration-features" = "no-cursor"; 32 | "mouse-hide-while-typing" = true; 33 | "link-url" = true; 34 | # NB: workaround for zellij not having the right PATH on macOS 35 | "command" = 36 | ''env EDITOR="nvim" PATH="$PATH:${config.home.homeDirectory}/.nix-profile/bin" ${pkgs.zellij}/bin/zellij''; 37 | "maximize" = isThinkpad; 38 | "keybind" = [ 39 | "super+v=paste_from_clipboard" 40 | "super+c=copy_to_clipboard" 41 | "super+q=quit" 42 | ]; 43 | }; 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /home-manager/components/tokyonight_palette.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | rec { 3 | bg_dark = "#1a1b26"; 4 | bg = "#24283b"; 5 | bg_highlight = "#292e42"; 6 | terminal_black = "#414868"; 7 | fg = "#c0caf5"; 8 | fg_dark = "#a9b1d6"; 9 | fg_gutter = "#3b4261"; 10 | dark3 = "#545c7e"; 11 | comment = "#565f89"; 12 | dark5 = "#737aa2"; 13 | blue0 = "#3d59a1"; 14 | blue = "#7aa2f7"; 15 | cyan = "#7dcfff"; 16 | blue1 = "#2ac3de"; 17 | blue2 = "#0db9d7"; 18 | blue5 = "#89ddff"; 19 | blue6 = "#b4f9f8"; 20 | blue7 = "#394b70"; 21 | magenta = "#bb9af7"; 22 | magenta2 = "#ff007c"; 23 | purple = "#9d7cd8"; 24 | orange = "#ff9e64"; 25 | yellow = "#e0af68"; 26 | green = "#9ece6a"; 27 | green1 = "#73daca"; 28 | green2 = "#41a6b5"; 29 | teal = "#1abc9c"; 30 | red = "#f7768e"; 31 | red1 = "#db4b4b"; 32 | 33 | # Convert hex digit to decimal 34 | hexDigitToInt = 35 | c: 36 | if c == "0" then 37 | 0 38 | else if c == "1" then 39 | 1 40 | else if c == "2" then 41 | 2 42 | else if c == "3" then 43 | 3 44 | else if c == "4" then 45 | 4 46 | else if c == "5" then 47 | 5 48 | else if c == "6" then 49 | 6 50 | else if c == "7" then 51 | 7 52 | else if c == "8" then 53 | 8 54 | else if c == "9" then 55 | 9 56 | else if c == "a" || c == "A" then 57 | 10 58 | else if c == "b" || c == "B" then 59 | 11 60 | else if c == "c" || c == "C" then 61 | 12 62 | else if c == "d" || c == "D" then 63 | 13 64 | else if c == "e" || c == "E" then 65 | 14 66 | else if c == "f" || c == "F" then 67 | 15 68 | else 69 | throw "Invalid hex digit: ${c}"; 70 | 71 | # Convert 2-digit hex string to decimal 72 | hexToInt = 73 | hex: 74 | let 75 | chars = lib.stringToCharacters (lib.toLower hex); 76 | high = hexDigitToInt (builtins.elemAt chars 0); 77 | low = hexDigitToInt (builtins.elemAt chars 1); 78 | in 79 | high * 16 + low; 80 | 81 | # Function to convert hex color to RGB ANSI background escape sequence 82 | hexToAnsiRgb = 83 | hexColor: 84 | let 85 | # Remove the # prefix and extract RGB components 86 | cleanHex = builtins.substring 1 6 hexColor; 87 | r = hexToInt (builtins.substring 0 2 cleanHex); 88 | g = hexToInt (builtins.substring 2 2 cleanHex); 89 | b = hexToInt (builtins.substring 4 2 cleanHex); 90 | in 91 | "${toString r};${toString g};${toString b}"; 92 | } 93 | -------------------------------------------------------------------------------- /home-manager/components/vencord.nix: -------------------------------------------------------------------------------- 1 | { 2 | isLinux, 3 | pkgs, 4 | lib, 5 | ... 6 | }: 7 | { 8 | xdg.dataFile."icons/discord.png".source = ./discord.png; 9 | home.packages = lib.lists.optionals isLinux [ 10 | # vesktop discord client, I don't like 11 | # vesktop's icon, so override it 12 | (pkgs.vesktop.overrideAttrs (oldAttrs: { 13 | desktopItems = [ 14 | (pkgs.makeDesktopItem { 15 | name = "vesktop"; 16 | desktopName = "Discord"; 17 | exec = "vesktop %U"; 18 | icon = "discord"; 19 | startupWMClass = "Vesktop"; 20 | genericName = "Internet Messenger"; 21 | keywords = [ 22 | "discord" 23 | "vencord" 24 | "electron" 25 | "chat" 26 | ]; 27 | categories = [ 28 | "Network" 29 | "InstantMessaging" 30 | "Chat" 31 | ]; 32 | }) 33 | ]; 34 | })) 35 | ]; 36 | } 37 | -------------------------------------------------------------------------------- /home-manager/home.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs, 3 | config, 4 | pkgs, 5 | lib, 6 | isDarwin, 7 | isLinux, 8 | isThinkpad, 9 | ... 10 | }: 11 | { 12 | nixpkgs.overlays = [ 13 | ( 14 | final: prev: 15 | (import ../packages { 16 | inherit inputs; 17 | inherit pkgs; 18 | inherit (prev) system; 19 | }) 20 | ) 21 | ]; 22 | home = { 23 | username = "mat"; 24 | homeDirectory = if isLinux then "/home/mat" else "/Users/mat"; 25 | 26 | # This value determines the Home Manager release that your configuration is 27 | # compatible with. This helps avoid breakage when a new Home Manager release 28 | # introduces backwards incompatible changes. 29 | # 30 | # You should not change this value, even if you update Home Manager. If you do 31 | # want to update the value, then make sure to first check the Home Manager 32 | # release notes. 33 | stateVersion = "22.11"; 34 | packages = 35 | with pkgs; 36 | [ 37 | spotify 38 | gnumake 39 | ] 40 | ++ lib.lists.optionals isDarwin [ 41 | # put macOS specific packages here 42 | darwin-rebuild 43 | bash # macOS ships with a very old version of bash for whatever reason 44 | ] 45 | ++ lib.lists.optionals isLinux [ 46 | # put Linux specific packages here 47 | signal-desktop 48 | vlc 49 | parsec-bin 50 | ungoogled-chromium 51 | ] 52 | ++ lib.lists.optionals isThinkpad [ ] 53 | ++ lib.lists.optionals (isLinux && (!isThinkpad)) [ 54 | # desktop only packages 55 | obs-studio 56 | r2modman 57 | qbittorrent 58 | ]; 59 | file."${config.home.homeDirectory}/.xprofile".text = '' 60 | export XDG_DATA_DIRS="$XDG_DATA_DIRS:/home/mat/.nix-profile/share" 61 | ''; 62 | }; 63 | xdg.enable = true; 64 | 65 | imports = [ 66 | ./shared.nix 67 | ./components/terminal.nix 68 | ./components/_1password-shell.nix 69 | ./components/espanso.nix 70 | ./components/gnome 71 | ./components/recyclarr.nix 72 | ./components/jujutsu.nix 73 | ./components/vencord.nix 74 | ../nixos/allowed-unfree.nix 75 | inputs.zen-browser.homeModules.default 76 | ]; 77 | 78 | programs = { 79 | zen-browser.enable = !isDarwin; 80 | nix-index.enable = true; 81 | # Let Home Manager install and manage itself. 82 | home-manager.enable = true; 83 | # Direnv integration for flakes 84 | direnv.enable = true; 85 | direnv.nix-direnv.enable = true; 86 | }; 87 | } 88 | -------------------------------------------------------------------------------- /home-manager/server.nix: -------------------------------------------------------------------------------- 1 | { inputs, pkgs, ... }: 2 | { 3 | nixpkgs.overlays = [ 4 | ( 5 | final: prev: 6 | (import ../packages { 7 | inherit inputs; 8 | inherit pkgs; 9 | inherit (prev) system; 10 | }) 11 | ) 12 | ]; 13 | home = { 14 | username = "mat"; 15 | homeDirectory = "/home/mat"; 16 | sessionVariables = { 17 | TERM = "xterm-256color"; 18 | COLORTERM = "truecolor"; 19 | }; 20 | packages = [ pkgs.wireguard-tools ]; 21 | # This value determines the Home Manager release that your configuration is 22 | # compatible with. This helps avoid breakage when a new Home Manager release 23 | # introduces backwards incompatible changes. 24 | # 25 | # You should not change this value, even if you update Home Manager. If you do 26 | # want to update the value, then make sure to first check the Home Manager 27 | # release notes. 28 | stateVersion = "22.11"; 29 | }; 30 | xdg.enable = true; 31 | imports = [ 32 | ./shared.nix 33 | ./components/zellij.nix 34 | ]; 35 | } 36 | -------------------------------------------------------------------------------- /home-manager/shared.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | { 3 | home.packages = [ pkgs.nix-search-cli ]; 4 | imports = [ 5 | ../nixos/nix-conf.nix 6 | ../nixos/theme.nix 7 | ./components/fish.nix 8 | ./components/nvim.nix 9 | ./components/ssh.nix 10 | ./components/starship.nix 11 | ./components/git.nix 12 | ./components/fzf.nix 13 | ]; 14 | } 15 | -------------------------------------------------------------------------------- /hosts/darwin/default.nix: -------------------------------------------------------------------------------- 1 | { config, pkgs, ... }: 2 | { 3 | imports = [ 4 | ../../nixos/nix-conf.nix 5 | ./settings.nix 6 | ]; 7 | nixpkgs.hostPlatform = "aarch64-darwin"; 8 | programs.fish.enable = true; 9 | users.users.mat = { 10 | name = "mat"; 11 | home = "/Users/mat"; 12 | shell = pkgs.fish; 13 | }; 14 | environment.variables.HOMEBREW_NO_ANALYTICS = "1"; 15 | environment.systemPath = [ "/opt/homebrew/bin" ]; 16 | homebrew = { 17 | enable = true; 18 | taps = [ "kiraum/tap" ]; 19 | brews = [ "kiraum/tap/cody" ]; 20 | }; 21 | system.primaryUser = config.users.users.mat.name; 22 | system.stateVersion = 6; 23 | } 24 | -------------------------------------------------------------------------------- /hosts/darwin/settings.nix: -------------------------------------------------------------------------------- 1 | { config, ... }: 2 | { 3 | system.defaults = { 4 | screencapture = { 5 | # store screenshots in ~/Downloads by default 6 | location = "${config.users.users.mat.home}/Downloads"; 7 | # only copy screenshots to clipboard by default 8 | target = "clipboard"; 9 | }; 10 | NSGlobalDomain = { 11 | # expanded save panel by default 12 | NSNavPanelExpandedStateForSaveMode = true; 13 | NSNavPanelExpandedStateForSaveMode2 = true; 14 | # do not save to icloud by default 15 | NSDocumentSaveNewDocumentsToCloud = false; 16 | # Display ASCII control characters using caret notation in standard text views 17 | # Try e.g. `cd /tmp; unidecode "\x{0000}" > cc.txt; open -e cc.txt` 18 | NSTextShowsControlCharacters = true; 19 | # Disable "natural" scrolling (it should be called unnatural scrolling) 20 | "com.apple.swipescrolldirection" = false; 21 | # Trackpad: enable tap to click 22 | "com.apple.mouse.tapBehavior" = 1; 23 | # Enable full keyboard access for all controls 24 | # (e.g. enable Tab in modal dialogs) 25 | AppleKeyboardUIMode = 3; 26 | }; 27 | finder = { 28 | AppleShowAllFiles = true; 29 | AppleShowAllExtensions = true; 30 | ShowPathbar = true; 31 | }; 32 | dock = { 33 | show-process-indicators = false; 34 | show-recents = false; 35 | # Don't show dashboard as a space 36 | dashboard-in-overlay = true; 37 | # Don't rearrange spaces based on most recently used 38 | mru-spaces = false; 39 | autohide = true; 40 | # Disable all hot corners 41 | wvous-tl-corner = 1; 42 | wvous-tr-corner = 1; 43 | wvous-bl-corner = 1; 44 | wvous-br-corner = 1; 45 | }; 46 | ActivityMonitor = { 47 | OpenMainWindow = true; 48 | # show CPU usage graph on icon 49 | IconType = 5; 50 | # Show all processes 51 | ShowCategory = 100; 52 | }; 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /hosts/laptop/default.nix: -------------------------------------------------------------------------------- 1 | { ... }: 2 | { 3 | networking = { 4 | hostName = "nixos-laptop"; 5 | networkmanager.enable = true; 6 | firewall = { 7 | # if packets are still dropped, they will show up in dmesg 8 | logReversePathDrops = true; 9 | # wireguard trips rpfilter up 10 | extraCommands = '' 11 | ip46tables -t mangle -I nixos-fw-rpfilter -p udp -m udp --sport 9999 -j RETURN 12 | ip46tables -t mangle -I nixos-fw-rpfilter -p udp -m udp --dport 9999 -j RETURN 13 | ''; 14 | extraStopCommands = '' 15 | ip46tables -t mangle -D nixos-fw-rpfilter -p udp -m udp --sport 9999 -j RETURN || true 16 | ip46tables -t mangle -D nixos-fw-rpfilter -p udp -m udp --dport 9999 -j RETURN || true 17 | ''; 18 | }; 19 | }; 20 | 21 | imports = [ 22 | ../../nixos/desktop_environment.nix 23 | ../../nixos/_1password.nix 24 | ../../nixos/allowed-unfree.nix 25 | ./hardware-configuration.nix 26 | ]; 27 | 28 | programs = { 29 | fish.enable = true; 30 | 31 | neovim = { 32 | enable = true; 33 | defaultEditor = true; 34 | }; 35 | }; 36 | 37 | environment.variables = { 38 | SUDO_EDITOR = "nvim"; 39 | EDITOR = "nvim"; 40 | }; 41 | 42 | services = { 43 | xserver.enable = true; 44 | mullvad-vpn.enable = true; 45 | flatpak.enable = true; 46 | }; 47 | 48 | boot = { 49 | loader = { 50 | systemd-boot.enable = true; 51 | efi.canTouchEfiVariables = true; 52 | }; 53 | }; 54 | 55 | # This option defines the first version of NixOS you have installed on this particular machine, 56 | # and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions. 57 | # 58 | # Most users should NEVER change this value after the initial install, for any reason, 59 | # even if you've upgraded your system to a new NixOS release. 60 | # 61 | # This value does NOT affect the Nixpkgs version your packages and OS are pulled from, 62 | # so changing it will NOT upgrade your system. 63 | # 64 | # This value being lower than the current NixOS release does NOT mean your system is 65 | # out of date, out of support, or vulnerable. 66 | # 67 | # Do NOT change this value unless you have manually inspected all the changes it would make to your configuration, 68 | # and migrated your data accordingly. 69 | # 70 | # For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion . 71 | system.stateVersion = "25.05"; 72 | } 73 | -------------------------------------------------------------------------------- /hosts/laptop/hardware-configuration.nix: -------------------------------------------------------------------------------- 1 | # Do not modify this file! It was generated by ‘nixos-generate-config’ 2 | # and may be overwritten by future invocations. Please make changes 3 | # to /etc/nixos/configuration.nix instead. 4 | { 5 | config, 6 | lib, 7 | pkgs, 8 | modulesPath, 9 | ... 10 | }: 11 | 12 | { 13 | imports = [ (modulesPath + "/installer/scan/not-detected.nix") ]; 14 | 15 | boot.initrd.availableKernelModules = [ 16 | "xhci_pci" 17 | "nvme" 18 | "usb_storage" 19 | "sd_mod" 20 | "rtsx_pci_sdmmc" 21 | ]; 22 | boot.initrd.kernelModules = [ ]; 23 | boot.kernelModules = [ ]; 24 | boot.extraModulePackages = [ ]; 25 | 26 | fileSystems."/" = { 27 | device = "/dev/disk/by-uuid/71f0a300-d3af-4d9c-bcae-4da7b2cb7cdd"; 28 | fsType = "ext4"; 29 | }; 30 | 31 | boot.initrd.luks.devices."luks-478962df-c7d5-4e0c-9f30-f5435e27612a".device = 32 | "/dev/disk/by-uuid/478962df-c7d5-4e0c-9f30-f5435e27612a"; 33 | 34 | fileSystems."/boot" = { 35 | device = "/dev/disk/by-uuid/EFA5-597E"; 36 | fsType = "vfat"; 37 | options = [ 38 | "fmask=0077" 39 | "dmask=0077" 40 | ]; 41 | }; 42 | 43 | swapDevices = [ ]; 44 | 45 | # Enables DHCP on each ethernet and wireless interface. In case of scripted networking 46 | # (the default) this is the recommended approach. When using systemd-networkd it's 47 | # still possible to use this option, but it's recommended to use it in conjunction 48 | # with explicit per-interface declarations with `networking.interfaces..useDHCP`. 49 | networking.useDHCP = lib.mkDefault true; 50 | # networking.interfaces.enp0s31f6.useDHCP = lib.mkDefault true; 51 | # networking.interfaces.wlp4s0.useDHCP = lib.mkDefault true; 52 | # networking.interfaces.wwp0s20f0u6i12.useDHCP = lib.mkDefault true; 53 | 54 | nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; 55 | hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; 56 | 57 | # enable VAAPI 58 | nixpkgs.config.packageOverrides = pkgs: { 59 | vaapiIntel = pkgs.vaapiIntel.override { enableHybridCodec = true; }; 60 | }; 61 | hardware.graphics = { 62 | enable = true; 63 | extraPackages = with pkgs; [ 64 | intel-media-driver 65 | intel-vaapi-driver # previously vaapiIntel 66 | vaapiVdpau 67 | intel-compute-runtime # OpenCL filter support (hardware tonemapping and subtitle burn-in) 68 | vpl-gpu-rt # QSV on 11th gen or newer 69 | ]; 70 | }; 71 | } 72 | -------------------------------------------------------------------------------- /hosts/pc/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | { 3 | networking.hostName = "nixos-pc"; 4 | imports = [ 5 | ../../nixos/desktop_environment.nix 6 | ../../nixos/_1password.nix 7 | ../../nixos/allowed-unfree.nix 8 | ../../nixos/sshd.nix 9 | ./hardware-configuration.nix 10 | ]; 11 | boot.loader.efi.efiSysMountPoint = "/boot"; 12 | powerManagement.cpuFreqGovernor = "performance"; 13 | hardware = { 14 | graphics = { 15 | enable = true; 16 | enable32Bit = true; 17 | }; 18 | 19 | amdgpu.amdvlk = { 20 | enable = true; 21 | support32Bit.enable = true; 22 | }; 23 | 24 | # setup udev rules for ZSA keyboard firmware flashing 25 | keyboard.zsa.enable = true; 26 | 27 | # logitech mouse support 28 | logitech.wireless = { 29 | enable = true; 30 | # installs solaar for configuring mouse buttons 31 | enableGraphical = true; 32 | }; 33 | 34 | }; 35 | 36 | programs = { 37 | fish.enable = true; 38 | coolercontrol.enable = true; 39 | gamemode.enable = true; 40 | gamescope.enable = true; 41 | steam = { 42 | enable = true; 43 | remotePlay.openFirewall = true; 44 | dedicatedServer.openFirewall = true; 45 | protontricks.enable = true; 46 | gamescopeSession.enable = true; 47 | }; 48 | 49 | neovim = { 50 | enable = true; 51 | defaultEditor = true; 52 | }; 53 | }; 54 | 55 | environment.variables = { 56 | SUDO_EDITOR = "nvim"; 57 | EDITOR = "nvim"; 58 | }; 59 | 60 | environment.systemPackages = with pkgs; [ 61 | steam-run 62 | winetricks 63 | steamtinkerlaunch 64 | parsec-bin 65 | mullvad-vpn 66 | prismlauncher 67 | wally-cli 68 | protonup-qt 69 | dolphin-emu 70 | # rpcs3 # broken right now 71 | ]; 72 | 73 | services = { 74 | xserver.enable = true; 75 | mullvad-vpn.enable = true; 76 | flatpak.enable = true; 77 | }; 78 | 79 | # for dolphin: https://nixos.wiki/wiki/Dolphin_Emulator 80 | # boot.extraModulePackages = [ config.boot.kernelPackages.gcadapter-oc-kmod ]; 81 | 82 | # to autoload at boot: 83 | boot.kernelModules = [ "gcadapter_oc" ]; 84 | # services.udev.packages = [ pkgs.dolphinEmu ]; 85 | 86 | # This value determines the NixOS release from which the default 87 | # settings for stateful data, like file locations and database versions 88 | # on your system were taken. It‘s perfectly fine and recommended to leave 89 | # this value at the release version of the first install of this system. 90 | # Before changing this value read the documentation for this option 91 | # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). 92 | system.stateVersion = "22.11"; # Did you read the comment? 93 | } 94 | -------------------------------------------------------------------------------- /hosts/pc/hardware-configuration.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | modulesPath, 4 | ... 5 | }: 6 | 7 | { 8 | imports = [ (modulesPath + "/installer/scan/not-detected.nix") ]; 9 | boot = { 10 | initrd.availableKernelModules = [ 11 | "xhci_pci" 12 | "ahci" 13 | "nvme" 14 | "usbhid" 15 | ]; 16 | initrd.kernelModules = [ ]; 17 | kernelModules = [ 18 | "kvm-amd" 19 | "coretemp" 20 | ]; 21 | extraModulePackages = [ ]; 22 | }; 23 | fileSystems = { 24 | "/" = { 25 | device = "/dev/disk/by-uuid/d6f1cc32-5216-43eb-a22c-339f1e0ebabf"; 26 | fsType = "ext4"; 27 | }; 28 | 29 | "/boot" = { 30 | device = "/dev/disk/by-uuid/264C-1D31"; 31 | fsType = "vfat"; 32 | options = [ 33 | "fmask=0077" 34 | "dmask=0077" 35 | ]; 36 | }; 37 | 38 | "/mnt/storage" = { 39 | device = "/dev/disk/by-uuid/aad56a7c-b586-4e16-b91f-58fbd796f400"; 40 | fsType = "ext4"; 41 | }; 42 | }; 43 | 44 | swapDevices = [ ]; 45 | networking = { 46 | # Enables DHCP on each ethernet and wireless interface. In case of scripted networking 47 | # (the default) this is the recommended approach. When using systemd-networkd it's 48 | # still possible to use this option, but it's recommended to use it in conjunction 49 | # with explicit per-interface declarations with `networking.interfaces..useDHCP`. 50 | useDHCP = lib.mkDefault true; 51 | networkmanager = { 52 | enable = true; 53 | wifi.powersave = false; 54 | }; 55 | }; 56 | # networking.interfaces.eno2.useDHCP = lib.mkDefault true; 57 | # networking.interfaces.wlo1.useDHCP = lib.mkDefault true; 58 | 59 | nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; 60 | powerManagement.cpuFreqGovernor = "performance"; 61 | powerManagement.powertop.enable = true; 62 | 63 | hardware = { 64 | cpu.amd.updateMicrocode = true; 65 | enableRedistributableFirmware = true; 66 | enableAllHardware = true; 67 | }; 68 | } 69 | -------------------------------------------------------------------------------- /hosts/server/adguard.nix: -------------------------------------------------------------------------------- 1 | let 2 | filterLists = [ 3 | # The Big List of Hacked Malware Web Sites 4 | "https://adguardteam.github.io/HostlistsRegistry/assets/filter_9.txt" 5 | # malicious url blocklist 6 | "https://adguardteam.github.io/HostlistsRegistry/assets/filter_11.txt" 7 | # https://oisd.nl, "passes the girlfriend test" 8 | "https://big.oisd.nl" 9 | # Native trackers (Windows, Apple, etc.) 10 | "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/pro.plus.txt" 11 | ]; 12 | webuiPort = 9003; 13 | in 14 | { 15 | networking.firewall = { 16 | allowedTCPPorts = [ 53 ]; 17 | allowedUDPPorts = [ 53 ]; 18 | }; 19 | services.nginx.subdomains.adguard.port = webuiPort; 20 | services.adguardhome = { 21 | enable = true; 22 | port = webuiPort; 23 | settings = { 24 | users = [ 25 | { 26 | name = "mat"; 27 | # generated with `nix-shell -p apacheHttpd` followed by `htpasswd -B -C 10 -n -b mat [password here]` 28 | # NB: Remember to put a space before the command so it doesn't go into shell history! 29 | password = "$2y$10$BKjlLZTCAgsfEO1L/TJFG.BiirZaHCE8NximCOdD7U5gCq9cz1x1C"; 30 | } 31 | ]; 32 | dns = { 33 | upstream_dns = [ 34 | "https://dns.quad9.net/dns-query" 35 | "https://base.dns.mullvad.net/dns-query" 36 | ]; 37 | anonymize_client_ip = false; 38 | }; 39 | tls = { 40 | enabled = true; 41 | server_name = "adguard.mjones.network"; 42 | force_https = true; 43 | # since its behind a reverse proxy, nginx takes care of encryption 44 | allow_unencrypted_doh = true; 45 | }; 46 | filtering = { 47 | protection_enabled = true; 48 | filtering_enabled = true; 49 | rewrites = [ 50 | { 51 | domain = "*.mjones.network"; 52 | answer = import ./ip.nix; 53 | } 54 | ]; 55 | }; 56 | filters = map (url: { 57 | inherit url; 58 | enabled = true; 59 | }) filterLists; 60 | user_rules = [ 61 | "||comparative-mollusk-y0a4rcrnmuyekxc7u0ajsvh7.herokudns.com^" 62 | "||telemetry.affine.run^" 63 | ]; 64 | }; 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /hosts/server/cleanuperr.nix: -------------------------------------------------------------------------------- 1 | { config, ... }: 2 | let 3 | envFile = config.age.secrets.cleanuperr_env.path; 4 | in 5 | { 6 | age.secrets.cleanuperr_env.file = ../../secrets/cleanuperr_env.age; 7 | virtualisation.oci-containers.containers.cleanuperr = { 8 | autoStart = true; 9 | image = "ghcr.io/flmorg/cleanuperr:latest"; 10 | environmentFiles = [ envFile ]; 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /hosts/server/containers.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | let 3 | update-containers = pkgs.writeShellScriptBin "update-containers" '' 4 | SUDO="" 5 | if [[ $(id -u) -ne 0 ]]; then 6 | SUDO="sudo" 7 | fi 8 | 9 | images=$($SUDO ${pkgs.podman}/bin/podman ps -a --format="{{.Image}}" | sort -u) 10 | 11 | for image in $images 12 | do 13 | $SUDO ${pkgs.podman}/bin/podman pull $image 14 | done 15 | 16 | # restart all running containers 17 | $SUDO ${pkgs.podman} container restart --running 18 | ''; 19 | in 20 | { 21 | virtualisation.oci-containers.backend = "podman"; 22 | virtualisation.podman.defaultNetwork.settings.dns_enabled = true; 23 | environment.systemPackages = [ update-containers ]; 24 | # update oci-containers every Monday 25 | systemd = { 26 | timers.updatecontainers = { 27 | timerConfig = { 28 | Unit = "updatecontainers.service"; 29 | OnCalendar = "Mon 02:00"; 30 | }; 31 | wantedBy = [ "timers.target" ]; 32 | }; 33 | services = { 34 | updatecontainers = { 35 | serviceConfig = { 36 | Type = "oneshot"; 37 | ExecStart = "update-containers"; 38 | }; 39 | }; 40 | }; 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /hosts/server/default.nix: -------------------------------------------------------------------------------- 1 | { ... }: 2 | { 3 | # This option defines the first version of NixOS you have installed on this particular machine, 4 | # and is used to maintain compatibility with application data (e.g. databases) created on older NixOS versions. 5 | # 6 | # Most users should NEVER change this value after the initial install, for any reason, 7 | # even if you've upgraded your system to a new NixOS release. 8 | # 9 | # This value does NOT affect the Nixpkgs version your packages and OS are pulled from, 10 | # so changing it will NOT upgrade your system. 11 | # 12 | # This value being lower than the current NixOS release does NOT mean your system is 13 | # out of date, out of support, or vulnerable. 14 | # 15 | # Do NOT change this value unless you have manually inspected all the changes it would make to your configuration, 16 | # and migrated your data accordingly. 17 | # 18 | # For more information, see `man configuration.nix` or https://nixos.org/manual/nixos/stable/options#opt-system.stateVersion . 19 | system.stateVersion = "23.11"; 20 | 21 | imports = [ 22 | ./hardware-configuration.nix 23 | ./nixosModules/nginx.nix 24 | ./media.nix 25 | ./nas.nix 26 | ./containers.nix 27 | ./wireguard.nix 28 | ./observability.nix 29 | ./vikunja.nix 30 | ./docmost.nix 31 | ./duckdns.nix 32 | ./paperless.nix 33 | ./adguard.nix 34 | ./homeassistant.nix 35 | ../../nixos/sshd.nix 36 | ]; 37 | 38 | powerManagement.cpuFreqGovernor = "performance"; 39 | boot = { 40 | # less aggressive swap usage 41 | kernel.sysctl."vm.swappiness" = 25; 42 | loader = { 43 | systemd-boot.enable = true; 44 | efi.canTouchEfiVariables = true; 45 | efi.efiSysMountPoint = "/boot"; 46 | }; 47 | }; 48 | 49 | programs.neovim = { 50 | enable = true; 51 | defaultEditor = true; 52 | }; 53 | 54 | # enable vaapi on OS-level 55 | nixpkgs.config.packageOverrides = pkgs: { 56 | vaapiIntel = pkgs.vaapiIntel.override { enableHybridCodec = true; }; 57 | }; 58 | programs = { 59 | fish.enable = true; 60 | dconf.enable = true; # TODO this shouldn't be needed but home-manager complains without it 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /hosts/server/docmost.nix: -------------------------------------------------------------------------------- 1 | { 2 | config, 3 | pkgs, 4 | ... 5 | }: 6 | let 7 | port = 3000; 8 | storage_location = "/var/lib/docmost/storage"; 9 | db_data_location = "/var/lib/docmost/db-data"; 10 | redis_data_location = "/var/lib/docmost/redis-data"; 11 | podman_network = "docmost"; 12 | podman_dns_port = 8053; 13 | in 14 | { 15 | systemd = { 16 | # Create required directories 17 | tmpfiles.rules = [ 18 | "d ${storage_location} 0777 root podman - -" 19 | "d ${db_data_location} 0777 root podman - -" 20 | "d ${redis_data_location} 0777 root podman - -" 21 | ]; 22 | 23 | # Create network for containers 24 | services.docmost-podman-network-create = { 25 | serviceConfig.Type = "oneshot"; 26 | wantedBy = [ 27 | "podman-docmost-app.service" 28 | "podman-docmost-db.service" 29 | "podman-docmost-redis.service" 30 | ]; 31 | script = '' 32 | ${pkgs.podman}/bin/podman network inspect ${podman_network} > /dev/null 2>&1 || ${pkgs.podman}/bin/podman network create ${podman_network} 33 | ''; 34 | }; 35 | }; 36 | 37 | age.secrets.docmost_env.file = ../../secrets/docmost_env.age; 38 | services.nginx.subdomains.docs.port = port; 39 | virtualisation.containers.containersConf.settings.network.dns_bind_port = podman_dns_port; 40 | virtualisation.oci-containers.containers = { 41 | docmost-db = { 42 | autoStart = true; 43 | image = "postgres:16-alpine"; 44 | volumes = [ "${db_data_location}:/var/lib/postgresql/data" ]; 45 | networks = [ podman_network ]; 46 | environmentFiles = [ config.age.secrets.docmost_env.path ]; 47 | environment = { 48 | POSTGRES_DB = "docmost"; 49 | POSTGRES_USER = "docmost"; 50 | }; 51 | extraOptions = [ 52 | "--health-cmd=pg_isready -U docmost -d docmost" 53 | "--health-interval=10s" 54 | "--health-timeout=5s" 55 | "--health-retries=5" 56 | ]; 57 | }; 58 | 59 | docmost-redis = { 60 | autoStart = true; 61 | image = "redis:7.2-alpine"; 62 | volumes = [ "${redis_data_location}:/data" ]; 63 | networks = [ podman_network ]; 64 | extraOptions = [ 65 | "--health-cmd=redis-cli --raw incr ping" 66 | "--health-interval=10s" 67 | "--health-timeout=5s" 68 | "--health-retries=5" 69 | ]; 70 | }; 71 | 72 | docmost-app = { 73 | autoStart = true; 74 | image = "docmost/docmost:latest"; 75 | volumes = [ "${storage_location}:/app/data/storage" ]; 76 | ports = [ "${toString port}:${toString port}" ]; 77 | networks = [ podman_network ]; 78 | environmentFiles = [ config.age.secrets.docmost_env.path ]; 79 | environment = { 80 | APP_URL = "http://localhost:${toString port}"; 81 | REDIS_URL = "redis://docmost-redis:6379"; 82 | DISABLE_TELEMETRY = "true"; 83 | }; 84 | dependsOn = [ 85 | "docmost-db" 86 | "docmost-redis" 87 | ]; 88 | }; 89 | }; 90 | } 91 | -------------------------------------------------------------------------------- /hosts/server/duckdns.nix: -------------------------------------------------------------------------------- 1 | { config, ... }: 2 | { 3 | age.secrets.duckdns_token.file = ../../secrets/duckdns_token.age; 4 | services.duckdns = { 5 | enable = true; 6 | tokenFile = config.age.secrets.duckdns_token.path; 7 | domains = [ "mjonesnetwork" ]; 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /hosts/server/hardware-configuration.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | config, 4 | lib, 5 | modulesPath, 6 | ... 7 | }: 8 | 9 | { 10 | imports = [ (modulesPath + "/installer/scan/not-detected.nix") ]; 11 | boot = { 12 | initrd = { 13 | luks.devices = { 14 | "cryptroot" = { 15 | device = "/dev/disk/by-partlabel/root"; # Use partition label, not filesystem label 16 | preLVM = true; 17 | }; 18 | 19 | "cryptswap" = { 20 | device = "/dev/disk/by-partlabel/swap"; # Partition label for swap 21 | preLVM = true; 22 | }; 23 | }; 24 | 25 | availableKernelModules = [ 26 | "xhci_pci" 27 | "ahci" 28 | "usbhid" 29 | "usb_storage" 30 | "sd_mod" 31 | "sr_mod" 32 | ]; 33 | kernelModules = [ ]; 34 | }; 35 | kernelModules = [ "kvm-intel" ]; 36 | extraModulePackages = [ ]; 37 | }; 38 | fileSystems = { 39 | "/" = { 40 | device = "/dev/disk/by-label/nixos"; 41 | fsType = "ext4"; 42 | }; 43 | "/boot" = { 44 | device = "/dev/disk/by-label/boot"; 45 | fsType = "vfat"; 46 | options = [ 47 | "fmask=0077" 48 | "dmask=0077" 49 | "uid=0" 50 | "gid=0" 51 | ]; 52 | }; 53 | "/mnt/fileshare" = { 54 | device = "/dev/disk/by-label/fileshare"; 55 | fsType = "ext4"; 56 | }; 57 | "/export/fileshare" = { 58 | device = "/mnt/fileshare"; 59 | options = [ "bind" ]; 60 | }; 61 | "/mnt/jellyfin" = { 62 | device = "/dev/disk/by-label/media"; 63 | fsType = "ext4"; 64 | }; 65 | }; 66 | 67 | swapDevices = [ { device = "/dev/disk/by-label/swap"; } ]; 68 | networking = { 69 | # Enables DHCP on each ethernet and wireless interface. In case of scripted networking 70 | # (the default) this is the recommended approach. When using systemd-networkd it's 71 | # still possible to use this option, but it's recommended to use it in conjunction 72 | # with explicit per-interface declarations with `networking.interfaces..useDHCP`. 73 | useDHCP = lib.mkDefault true; 74 | hostName = "homelab"; 75 | defaultGateway = "192.168.1.1"; 76 | nameservers = [ 77 | # TODO temporary. Works around a stupid ass issue with AmpliFi router. 78 | # Remove when I get my new UniFi router replacement. 79 | "127.0.0.1" 80 | config.networking.defaultGateway.address 81 | ]; 82 | # static IP on ethernet interface 83 | interfaces.enp0s31f6.ipv4.addresses = [ 84 | { 85 | address = import ./ip.nix; 86 | prefixLength = 24; 87 | } 88 | ]; 89 | }; 90 | 91 | nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; 92 | hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; 93 | 94 | # enable hardware transcoding stuff for jellyfin 95 | nixpkgs.config.packageOverrides = pkgs: { 96 | vaapiIntel = pkgs.vaapiIntel.override { enableHybridCodec = true; }; 97 | }; 98 | hardware.graphics = { 99 | enable = true; 100 | extraPackages = with pkgs; [ 101 | intel-media-driver 102 | intel-vaapi-driver # previously vaapiIntel 103 | vaapiVdpau 104 | intel-compute-runtime # OpenCL filter support (hardware tonemapping and subtitle burn-in) 105 | vpl-gpu-rt # QSV on 11th gen or newer 106 | ]; 107 | }; 108 | } 109 | -------------------------------------------------------------------------------- /hosts/server/homeassistant.nix: -------------------------------------------------------------------------------- 1 | { config, ... }: 2 | { 3 | services = { 4 | nginx.subdomains.home.port = config.services.home-assistant.config.http.server_port; 5 | home-assistant = { 6 | enable = true; 7 | extraPackages = 8 | ps: with ps; [ 9 | psycopg2 10 | pyatv 11 | universal-silabs-flasher 12 | ]; 13 | extraComponents = [ 14 | "default_config" 15 | "met" 16 | "esphome" 17 | "ring" 18 | "homekit_controller" 19 | "apple_tv" 20 | "brother" 21 | "adguard" 22 | "sonos" 23 | "nanoleaf" 24 | "api" 25 | ]; 26 | config = { 27 | default_config = { }; 28 | recorder.db_url = "postgresql://@/hass"; 29 | homeassistant = { 30 | unit_system = "us_customary"; 31 | time_zone = "America/New_York"; 32 | }; 33 | http = { 34 | trusted_proxies = [ "127.0.0.1" ]; 35 | use_x_forwarded_for = true; 36 | }; 37 | }; 38 | }; 39 | postgresql = { 40 | enable = true; 41 | ensureDatabases = [ "hass" ]; 42 | ensureUsers = [ 43 | { 44 | name = "hass"; 45 | ensureDBOwnership = true; 46 | } 47 | ]; 48 | }; 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /hosts/server/ip.nix: -------------------------------------------------------------------------------- 1 | "192.168.1.6" 2 | -------------------------------------------------------------------------------- /hosts/server/media.nix: -------------------------------------------------------------------------------- 1 | { config, ... }: 2 | let 3 | huntarr_port = 9705; 4 | huntarr_data = "/var/lib/huntarr"; 5 | in 6 | { 7 | imports = [ 8 | ./torrent_client.nix 9 | ./cleanuperr.nix 10 | ]; 11 | services.nginx.subdomains = { 12 | # jellyfin doesn't let you configure port via Nix, so just use the default value here 13 | # see: https://jellyfin.org/docs/general/networking/index.html 14 | jellyfin.port = 8096; 15 | jellyseerr.port = config.services.jellyseerr.port; 16 | prowlarr = { 17 | inherit (config.services.prowlarr.settings.server) port; 18 | useLongerTimeout = true; 19 | }; 20 | sonarr = { 21 | inherit (config.services.sonarr.settings.server) port; 22 | useLongerTimeout = true; 23 | }; 24 | radarr = { 25 | inherit (config.services.radarr.settings.server) port; 26 | useLongerTimeout = true; 27 | }; 28 | bazarr = { 29 | port = config.services.bazarr.listenPort; 30 | useLongerTimeout = true; 31 | }; 32 | huntarr = { 33 | port = huntarr_port; 34 | useLongerTimeout = true; 35 | }; 36 | }; 37 | services = { 38 | jellyfin.enable = true; 39 | jellyseerr.enable = true; 40 | prowlarr.enable = true; 41 | sonarr.enable = true; 42 | radarr.enable = true; 43 | bazarr.enable = true; 44 | }; 45 | # TODO remove this when this is resolved https://github.com/NixOS/nixpkgs/issues/360592 46 | nixpkgs.config.permittedInsecurePackages = [ 47 | "aspnetcore-runtime-6.0.36" 48 | "aspnetcore-runtime-wrapped-6.0.36" 49 | "dotnet-sdk-6.0.428" 50 | "dotnet-sdk-wrapped-6.0.428" 51 | ]; 52 | 53 | systemd.tmpfiles.rules = [ 54 | "d ${huntarr_data} 0777 root podman -" 55 | ]; 56 | virtualisation.oci-containers.containers.huntarr = { 57 | image = "huntarr/huntarr:latest"; 58 | autoStart = true; 59 | ports = [ "${toString huntarr_port}:${toString huntarr_port}" ]; 60 | volumes = [ "${huntarr_data}:/config" ]; 61 | environment.TZ = "America/New_York"; 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /hosts/server/nas.nix: -------------------------------------------------------------------------------- 1 | { 2 | # these are NOT exposed to the internet 3 | services = { 4 | # samba share, allow guest users full access 5 | # it's only reachable via LAN anyway 6 | samba = { 7 | enable = true; 8 | openFirewall = true; 9 | settings = { 10 | global = { 11 | "guest account" = "nobody"; 12 | "map to guest" = "Bad User"; 13 | "load printers" = "no"; 14 | "printcap name" = "/dev/null"; 15 | }; 16 | }; 17 | settings = { 18 | fileshare = { 19 | path = "/export/fileshare"; 20 | browseable = "yes"; 21 | writable = "yes"; 22 | public = "yes"; 23 | "read only" = "no"; 24 | "force user" = "nobody"; 25 | "force group" = "users"; 26 | "force directory mode" = "2770"; 27 | }; 28 | }; 29 | }; 30 | samba-wsdd = { 31 | enable = true; 32 | openFirewall = true; 33 | }; 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /hosts/server/nixosModules/nginx.nix: -------------------------------------------------------------------------------- 1 | { config, lib, ... }: 2 | let 3 | # Define the subdomain configuration options 4 | subdomainType = lib.types.submodule { 5 | options = { 6 | port = lib.mkOption { 7 | type = lib.types.port; 8 | description = "Port to proxy to."; 9 | }; 10 | default = lib.mkOption { 11 | type = lib.types.bool; 12 | default = false; 13 | description = "Whether this is the default subdomain."; 14 | }; 15 | useLongerTimeout = lib.mkOption { 16 | type = lib.types.bool; 17 | default = false; 18 | description = "Whether to use longer proxy timeout settings for this subdomain."; 19 | }; 20 | }; 21 | }; 22 | 23 | cfg = config.services.nginx.subdomains; 24 | 25 | # Get all subdomain names where default = true 26 | defaultSubdomains = lib.filterAttrs (_: v: v.default) cfg; 27 | in 28 | { 29 | options.services.nginx.subdomains = lib.mkOption { 30 | type = lib.types.attrsOf subdomainType; 31 | description = '' 32 | Proxy the given subdomain to the specified port. 33 | Configure with an attribute set containing port and optional settings. 34 | ''; 35 | example = { 36 | "api" = { 37 | port = 8080; 38 | useLongerTimeout = true; 39 | }; 40 | "myapp" = { 41 | port = 9090; 42 | default = true; 43 | }; 44 | }; 45 | }; 46 | 47 | config = lib.mkIf (cfg != { }) { 48 | assertions = [ 49 | { 50 | assertion = (lib.length (lib.attrNames defaultSubdomains)) <= 1; 51 | message = "Only one subdomain may have default = true."; 52 | } 53 | ]; 54 | 55 | networking.firewall = { 56 | allowedTCPPorts = [ 57 | 80 58 | 443 59 | ]; 60 | allowedUDPPorts = [ 61 | 80 62 | 443 63 | ]; 64 | }; 65 | age.secrets.cloudflare_certbot_token.file = ../../../secrets/cloudflare_certbot_token.age; 66 | services.nginx = { 67 | enable = true; 68 | recommendedProxySettings = true; 69 | recommendedTlsSettings = true; 70 | proxyTimeout = "180s"; 71 | 72 | virtualHosts = lib.foldl' ( 73 | acc: subdomain: 74 | acc 75 | // { 76 | "${subdomain}.mjones.network" = { 77 | inherit (cfg.${subdomain}) default; 78 | forceSSL = true; 79 | useACMEHost = "mjones.network"; 80 | locations."/" = { 81 | proxyPass = "http://127.0.0.1:${toString cfg.${subdomain}.port}"; 82 | proxyWebsockets = true; 83 | extraConfig = lib.optionalString cfg.${subdomain}.useLongerTimeout '' 84 | proxy_read_timeout 120s; 85 | proxy_connect_timeout 60s; 86 | proxy_send_timeout 60s; 87 | ''; 88 | }; 89 | }; 90 | } 91 | ) { } (lib.attrNames cfg); 92 | }; 93 | security.acme = { 94 | acceptTerms = true; 95 | certs."mjones.network" = { 96 | inherit (config.services.nginx) group; 97 | email = "certs@mjones.network"; 98 | domain = "*.mjones.network"; 99 | dnsProvider = "cloudflare"; 100 | dnsResolver = "1.1.1.1:53"; 101 | environmentFile = config.age.secrets.cloudflare_certbot_token.path; 102 | }; 103 | }; 104 | }; 105 | } 106 | -------------------------------------------------------------------------------- /hosts/server/observability.nix: -------------------------------------------------------------------------------- 1 | { config, lib, ... }: 2 | let 3 | homarrStateDir = "/var/lib/homarr"; 4 | homarrPort = 9090; 5 | dashdotPort = 9091; 6 | gatusPort = 3001; 7 | 8 | # dynamically configure gatus status pages 9 | toTitleCase = 10 | str: 11 | let 12 | firstChar = builtins.substring 0 1 str; 13 | restChars = builtins.substring 1 (builtins.stringLength str) str; 14 | in 15 | lib.strings.toUpper firstChar + restChars; 16 | applyOverrides = 17 | overrides: subdomain: 18 | if lib.hasAttr subdomain overrides then overrides.${subdomain} else toTitleCase subdomain; 19 | nginxSubdomains = config.services.nginx.subdomains; 20 | subdomainOverrides = { 21 | # By default it will just capitalize the first letter 22 | # of the subdomain. Customize subdomain -> Title mapping here. 23 | qbittorrent = "qBitTorrent"; 24 | home = "Home Assistant"; 25 | }; 26 | 27 | # Generate the Gatus endpoints configuration 28 | gatusEndpoints = lib.attrsets.mapAttrsToList ( 29 | name: _: 30 | let 31 | title = applyOverrides subdomainOverrides name; 32 | in 33 | { 34 | name = title; 35 | url = "https://${name}.mjones.network"; 36 | interval = "2m"; 37 | conditions = [ 38 | "[STATUS] == 200" 39 | "[RESPONSE_TIME] < 1500" 40 | ]; 41 | alerts = [ 42 | { 43 | enabled = true; 44 | type = "discord"; 45 | failure-threshold = 4; 46 | success-threshold = 2; 47 | send-on-resolved = true; 48 | description = title; 49 | } 50 | ]; 51 | } 52 | ) nginxSubdomains; 53 | in 54 | { 55 | services = { 56 | nginx.subdomains = { 57 | uptime.port = gatusPort; 58 | dashdot.port = dashdotPort; 59 | glances.port = config.services.glances.port; 60 | homarr = { 61 | port = homarrPort; 62 | default = true; 63 | }; 64 | }; 65 | 66 | gatus = { 67 | enable = true; 68 | environmentFile = config.age.secrets.gatus_discord_webhook_env.path; 69 | settings = { 70 | web.port = gatusPort; 71 | endpoints = gatusEndpoints; 72 | alerting.discord.webhook-url = "\${DISCORD_WEBHOOK_URL}"; 73 | }; 74 | }; 75 | 76 | glances.enable = true; 77 | }; 78 | 79 | age.secrets.gatus_discord_webhook_env.file = ../../secrets/gatus_discord_webhook_env.age; 80 | 81 | systemd.tmpfiles.rules = [ "d ${homarrStateDir} 0750 root root -" ]; 82 | age.secrets.homarr_env.file = ../../secrets/homarr_env.age; 83 | virtualisation.oci-containers.containers.homarr = { 84 | autoStart = true; 85 | image = "ghcr.io/homarr-labs/homarr:latest"; 86 | ports = [ "${builtins.toString homarrPort}:7575" ]; 87 | volumes = [ "${homarrStateDir}:/appdata" ]; 88 | environment.DEFAULT_COLOR_SCHEME = "dark"; 89 | environmentFiles = [ config.age.secrets.homarr_env.path ]; 90 | }; 91 | 92 | virtualisation.oci-containers.containers.dashdot = { 93 | image = "mauricenino/dashdot"; 94 | ports = [ "${builtins.toString dashdotPort}:3001" ]; 95 | volumes = [ "/:/mnt/host:ro" ]; 96 | extraOptions = [ "--privileged=true" ]; 97 | environment = { 98 | DASHDOT_PAGE_TITLE = "Dashboard"; 99 | DASHDOT_USE_IMPERIAL = "true"; 100 | DASHDOT_ALWAYS_SHOW_PERCENTAGES = "true"; 101 | DASHDOT_OVERRIDE_OS = "NixOS"; 102 | DASHDOT_OVERRIDE_ARCH = "x86"; 103 | DASHDOT_CUSTOM_HOST = config.networking.hostName; 104 | DASHDOT_SHOW_HOST = "true"; 105 | }; 106 | }; 107 | } 108 | -------------------------------------------------------------------------------- /hosts/server/paperless.nix: -------------------------------------------------------------------------------- 1 | { config, ... }: 2 | { 3 | services.nginx.subdomains.paperless.port = config.services.paperless.port; 4 | age.secrets.paperless_admin_pw.file = ../../secrets/paperless_admin_pw.age; 5 | services.paperless = { 6 | enable = true; 7 | passwordFile = config.age.secrets.paperless_admin_pw.path; 8 | database.createLocally = true; 9 | settings.PAPERLESS_URL = "https://paperless.mjones.network"; 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /hosts/server/torrent_client.nix: -------------------------------------------------------------------------------- 1 | { config, pkgs, ... }: 2 | let 3 | configDir = "/var/lib/qbittorrentvpn"; 4 | wireguardConfigPath = config.age.secrets.mullvad_wireguard.path; 5 | qbittorrent_port = 8080; 6 | vuetorrent_port = 8081; 7 | tcpPorts = [ 8 | qbittorrent_port 9 | 8118 10 | 9118 11 | 58946 12 | ]; 13 | podman_network = "qbittorrent"; 14 | in 15 | { 16 | age.secrets.mullvad_wireguard.file = ../../secrets/mullvad_wireguard.age; 17 | systemd.tmpfiles.rules = [ 18 | "d ${configDir} 055 qbittorrentvpn qbittorrentvpn - -" 19 | "d ${configDir}/wireguard 055 qbittorrentvpn qbittorrentvpn - -" 20 | ]; 21 | system.activationScripts.copyWireguardConfigIntoContainer.text = '' 22 | mkdir -p ${configDir}/wireguard && cp ${wireguardConfigPath} ${configDir}/wireguard/mullvad_wireguard.conf 23 | ''; 24 | systemd.services.qbittorrent-podman-network-create = { 25 | serviceConfig.Type = "oneshot"; 26 | wantedBy = [ 27 | "podman-qbittorrentvpn.service" 28 | "podman-vuetorrent.service" 29 | ]; 30 | script = '' 31 | ${pkgs.podman}/bin/podman network inspect ${podman_network} > /dev/null 2>&1 || ${pkgs.podman}/bin/podman network create ${podman_network} 32 | ''; 33 | }; 34 | services.nginx.subdomains.qbittorrent.port = qbittorrent_port; 35 | virtualisation.oci-containers.containers.qbittorrentvpn = { 36 | autoStart = true; 37 | image = "ghcr.io/binhex/arch-qbittorrentvpn"; 38 | networks = [ podman_network ]; 39 | extraOptions = [ 40 | "--sysctl=net.ipv4.conf.all.src_valid_mark=1" 41 | "--privileged=true" 42 | ]; 43 | ports = builtins.map (port: "${builtins.toString port}:${builtins.toString port}") tcpPorts; 44 | volumes = [ 45 | "/mnt/jellyfin:/data" 46 | "${configDir}:/config" 47 | "/etc/localtime:/etc/localtime:ro" 48 | ]; 49 | environment = { 50 | VPN_ENABLED = "yes"; 51 | VPN_PROV = "custom"; 52 | VPN_CLIENT = "wireguard"; 53 | USERSPACE_WIREGUARD = "no"; 54 | STRICT_PORT_FORWARD = "yes"; 55 | ENABLE_PRIVOXY = "yes"; 56 | LAN_NETWORK = "192.168.1.0/24"; 57 | NAME_SERVERS = "1.1.1.1,1.0.0.1"; 58 | ENABLE_STARTUP_SCRIPTS = "yes"; 59 | ENABLE_SOCKS = "yes"; 60 | VPN_INPUT_PORTS = ""; 61 | VPN_OUTPUT_PORTS = ""; 62 | DEBUG = "false"; 63 | UMASK = "000"; 64 | PUID = "0"; 65 | PGID = "0"; 66 | }; 67 | }; 68 | # prettier web UI 69 | services.nginx.subdomains.vuetorrent.port = vuetorrent_port; 70 | virtualisation.oci-containers.containers.vuetorrent = { 71 | autoStart = true; 72 | image = "ghcr.io/vuetorrent/vuetorrent-backend:latest"; 73 | networks = [ podman_network ]; 74 | ports = [ "${toString vuetorrent_port}:${toString vuetorrent_port}" ]; 75 | environment = { 76 | PORT = toString vuetorrent_port; 77 | QBIT_BASE = "http://qbittorrentvpn:${toString qbittorrent_port}"; 78 | RELEASE_TYPE = "stable"; 79 | # every Sunday 80 | UPDATE_VT_CRON = "0 0 * * 0"; 81 | }; 82 | }; 83 | } 84 | -------------------------------------------------------------------------------- /hosts/server/vikunja.nix: -------------------------------------------------------------------------------- 1 | { config, ... }: 2 | { 3 | services.nginx.subdomains.vikunja.port = config.services.vikunja.port; 4 | services.vikunja = { 5 | enable = true; 6 | frontendScheme = "http"; 7 | frontendHostname = import ./ip.nix; 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /hosts/server/wireguard.nix: -------------------------------------------------------------------------------- 1 | { pkgs, config, ... }: 2 | let 3 | wireguard_port = 9999; 4 | wireguard_interface = "wgvpn"; 5 | external_interface = "enp0s31f6"; 6 | in 7 | { 8 | age.secrets.wireguard_server.file = ../../secrets/wireguard_server.age; 9 | boot.kernel.sysctl."net.ipv4.ip_forward" = 1; 10 | networking = { 11 | # Enable NAT 12 | nat = { 13 | enable = true; 14 | externalInterface = external_interface; 15 | internalInterfaces = [ wireguard_interface ]; 16 | }; 17 | firewall.allowedUDPPorts = [ wireguard_port ]; 18 | wg-quick.interfaces = { 19 | # the network interface name. You can name the interface arbitrarily. 20 | "${wireguard_interface}" = { 21 | # Determines the IP address and subnet of the client's end of the tunnel interface 22 | address = [ "10.0.0.1/24" ]; 23 | # The port that WireGuard listens to - recommended that this be changed from default 24 | listenPort = wireguard_port; 25 | # Path to the server's private key 26 | privateKeyFile = config.age.secrets.wireguard_server.path; 27 | 28 | # This allows the wireguard server to route your traffic to the internet and hence be like a VPN 29 | postUp = '' 30 | ${pkgs.iptables}/bin/iptables -A FORWARD -i ${wireguard_interface} -j ACCEPT 31 | ${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -s 10.0.0.1/24 -o ${external_interface} -j MASQUERADE 32 | # Force all DNS traffic through local AdGuard Home 33 | ${pkgs.iptables}/bin/iptables -t nat -A PREROUTING -i ${wireguard_interface} -p udp --dport 53 -j DNAT --to-destination 10.0.0.1:53 34 | ${pkgs.iptables}/bin/iptables -t nat -A PREROUTING -i ${wireguard_interface} -p tcp --dport 53 -j DNAT --to-destination 10.0.0.1:53 35 | ''; 36 | 37 | # Undo the above 38 | preDown = '' 39 | ${pkgs.iptables}/bin/iptables -D FORWARD -i ${wireguard_interface} -j ACCEPT 40 | ${pkgs.iptables}/bin/iptables -t nat -D POSTROUTING -s 10.0.0.1/24 -o ${external_interface} -j MASQUERADE 41 | 42 | # Remove DNS redirection rules 43 | ${pkgs.iptables}/bin/iptables -t nat -D PREROUTING -i ${wireguard_interface} -p udp --dport 53 -j DNAT --to-destination 10.0.0.1:53 44 | ${pkgs.iptables}/bin/iptables -t nat -D PREROUTING -i ${wireguard_interface} -p tcp --dport 53 -j DNAT --to-destination 10.0.0.1:53 45 | ''; 46 | 47 | peers = [ 48 | { 49 | publicKey = "klCZN17QW/0ZtAlmj24R6ftBRozXUGn3hvWjF9ledC4="; 50 | allowedIPs = [ "10.0.0.2/32" ]; 51 | } 52 | # Robby/Megan 53 | { 54 | publicKey = "0XXP3UgA67bcImCB4UOvyno3fhiBx7v6ufd4y4MH1xE="; 55 | allowedIPs = [ "10.0.0.3/32" ]; 56 | } 57 | # Andrew 58 | { 59 | publicKey = "30hYSNSsFVjTmi4553kdbucg5laEkGvERgHxgqnDGkE="; 60 | allowedIPs = [ "10.0.0.4/32" ]; 61 | } 62 | ]; 63 | }; 64 | }; 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /nixos/_1password.nix: -------------------------------------------------------------------------------- 1 | { 2 | programs._1password.enable = true; 3 | programs._1password-gui = { 4 | enable = true; 5 | polkitPolicyOwners = [ "mat" ]; 6 | }; 7 | environment.etc."1password/custom_allowed_browsers" = { 8 | text = '' 9 | zen 10 | ''; 11 | mode = "0755"; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /nixos/allowed-unfree.nix: -------------------------------------------------------------------------------- 1 | { lib, ... }: 2 | { 3 | nixpkgs.config.allowUnfreePredicate = 4 | pkg: 5 | let 6 | name = lib.getName pkg; 7 | in 8 | builtins.elem name [ 9 | "spotify" 10 | "1password" 11 | "1password-cli" 12 | "steam" 13 | "steam-run" 14 | "steam-original" 15 | "steam-unwrapped" 16 | "parsec-bin" 17 | # This is required for pkgs.nodePackages_latest.vscode-langservers-extracted on NixOS 18 | # however VS Code should NOT be installed on this system! 19 | # Use VS Codium instead: https://github.com/VSCodium/vscodium 20 | "vscode" 21 | ]; 22 | } 23 | -------------------------------------------------------------------------------- /nixos/common.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | { 3 | imports = [ ../nixos/nix-conf.nix ]; 4 | 5 | environment.systemPackages = [ pkgs.mullvad-vpn ]; 6 | 7 | security.rtkit.enable = true; 8 | networking = { 9 | wireguard.enable = true; 10 | firewall = { 11 | enable = true; 12 | allowPing = true; 13 | }; 14 | }; 15 | # enable mDNS for stuff like NAS and SSH 16 | services.avahi = { 17 | enable = true; 18 | nssmdns4 = true; 19 | openFirewall = true; 20 | publish = { 21 | enable = true; 22 | userServices = true; 23 | }; 24 | }; 25 | services = { 26 | mullvad-vpn.enable = true; 27 | # going to use pipewire instead 28 | pulseaudio.enable = false; 29 | 30 | # Configure keymap in X11 31 | xserver = { 32 | xkb = { 33 | layout = "us"; 34 | variant = ""; 35 | }; 36 | }; 37 | 38 | # Enable CUPS to print documents. 39 | printing.enable = true; 40 | pipewire = { 41 | enable = true; 42 | alsa.enable = true; 43 | alsa.support32Bit = true; 44 | pulse.enable = true; 45 | # If you want to use JACK applications, uncomment this 46 | #jack.enable = true; 47 | 48 | # use the example session manager (no others are packaged yet so this is enabled by default, 49 | # no need to redefine it in your config for now) 50 | #media-session.enable = true; 51 | }; 52 | }; 53 | 54 | boot = { 55 | loader = { 56 | # bootloader 57 | systemd-boot.enable = true; 58 | efi.canTouchEfiVariables = true; 59 | }; 60 | }; 61 | 62 | systemd.extraConfig = '' 63 | DefaultTimeoutStopSec=10s 64 | ''; 65 | systemd.user.extraConfig = '' 66 | DefaultTimeoutStopSec=10s 67 | ''; 68 | 69 | security = { 70 | sudo.enable = true; 71 | pam = { 72 | sshAgentAuth.enable = true; 73 | # sshAgentAuth.authorizedKeysFiles = 74 | # lib.mkForce [ "/etc/ssh/authorized_keys.d/%u" ]; 75 | services.sudo.sshAgentAuth = true; 76 | }; 77 | }; 78 | 79 | # Set your time zone. 80 | time.timeZone = "America/New_York"; 81 | 82 | # Select internationalisation properties. 83 | i18n.defaultLocale = "en_US.UTF-8"; 84 | 85 | i18n.extraLocaleSettings = { 86 | LC_ADDRESS = "en_US.UTF-8"; 87 | LC_IDENTIFICATION = "en_US.UTF-8"; 88 | LC_MEASUREMENT = "en_US.UTF-8"; 89 | LC_MONETARY = "en_US.UTF-8"; 90 | LC_NAME = "en_US.UTF-8"; 91 | LC_NUMERIC = "en_US.UTF-8"; 92 | LC_PAPER = "en_US.UTF-8"; 93 | LC_TELEPHONE = "en_US.UTF-8"; 94 | LC_TIME = "en_US.UTF-8"; 95 | }; 96 | 97 | users = { 98 | mutableUsers = false; 99 | users = { 100 | mat = { 101 | shell = pkgs.fish; 102 | isNormalUser = true; 103 | # generated by `mkpasswd` 104 | hashedPassword = "$y$j9T$L.RrmE3CRSB.lQayiw2ZN/$vA4XkSR13yL016t3HaZ11uCN/sCmXqBcuUcSBxMjiPD"; 105 | home = "/home/mat"; 106 | extraGroups = [ 107 | "wheel" 108 | "networkmanager" 109 | "oci" 110 | "podman" 111 | ]; 112 | }; 113 | }; 114 | }; 115 | } 116 | -------------------------------------------------------------------------------- /nixos/desktop_environment.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | { 3 | nixpkgs.overlays = [ 4 | # GNOME 46: triple-buffering-v4-46 5 | # See: 6 | # - https://nixos.wiki/wiki/GNOME#Dynamic_triple_buffering 7 | # - https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1441 8 | (final: prev: { 9 | gnome = prev.gnome.overrideScope ( 10 | gnomeFinal: gnomePrev: { 11 | mutter = gnomePrev.mutter.overrideAttrs (old: { 12 | src = pkgs.fetchFromGitLab { 13 | domain = "gitlab.gnome.org"; 14 | owner = "vanvugt"; 15 | repo = "mutter"; 16 | rev = "triple-buffering-v4-46"; 17 | hash = "sha256-C2VfW3ThPEZ37YkX7ejlyumLnWa9oij333d5c4yfZxc="; 18 | }; 19 | }); 20 | } 21 | ); 22 | }) 23 | ]; 24 | 25 | # Make electron apps detect wayland properly 26 | environment.sessionVariables.NIXOS_OZONE_WL = "1"; 27 | services = { 28 | gnome.gnome-keyring.enable = true; 29 | displayManager.defaultSession = "gnome"; 30 | desktopManager.gnome.enable = true; 31 | displayManager.gdm.enable = true; 32 | }; 33 | environment = { 34 | # don't install GNOME crap like Contacts, Photos, etc. 35 | gnome.excludePackages = with pkgs; [ 36 | gnome-photos 37 | gnome-tour 38 | cheese # webcam tool 39 | epiphany # web browser 40 | geary # email reader 41 | yelp # Help view 42 | seahorse # password/ssh manager, I use 1Password SSH 43 | gnome-calendar 44 | gnome-music 45 | gnome-characters 46 | tali # poker game 47 | iagno # go game 48 | hitori # sudoku game 49 | atomix # puzzle game 50 | gnome-contacts 51 | gnome-initial-setup 52 | gnome-software 53 | ]; 54 | }; 55 | 56 | programs.dconf.enable = true; 57 | } 58 | -------------------------------------------------------------------------------- /nixos/nix-conf.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | lib, 4 | inputs, 5 | isLinux, 6 | ... 7 | }: 8 | { 9 | nix = { 10 | package = lib.mkDefault pkgs.lix; 11 | settings = { 12 | substituters = [ 13 | "https://cache.nixos.org" 14 | "https://nix-community.cachix.org" 15 | ]; 16 | trusted-public-keys = [ 17 | "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" 18 | "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" 19 | ]; 20 | trusted-users = [ 21 | "root" 22 | "mat" 23 | ]; 24 | keep-outputs = true; 25 | keep-derivations = true; 26 | auto-optimise-store = if isLinux then true else false; # https://github.com/NixOS/nix/issues/7273 27 | 28 | experimental-features = "nix-command flakes"; 29 | }; 30 | # enable `nix-shell -p nixpkgs#something` without using channels 31 | # also use the exact version of nixpkgs from the flake the system is built from 32 | # to avoid cache misses 33 | nixPath = lib.mkForce [ "nixpkgs=${inputs.nixpkgs}" ]; 34 | registry.nixpkgs.flake = inputs.nixpkgs; 35 | }; 36 | } 37 | -------------------------------------------------------------------------------- /nixos/sshd.nix: -------------------------------------------------------------------------------- 1 | { 2 | services = { 3 | fail2ban.enable = true; 4 | openssh = { 5 | enable = true; 6 | settings = { 7 | # only allow SSH key auth 8 | PasswordAuthentication = false; 9 | PermitRootLogin = "no"; 10 | AllowUsers = [ "mat" ]; 11 | }; 12 | }; 13 | }; 14 | 15 | users.users.mat.openssh.authorizedKeys.keys = [ 16 | "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDsT6GLG7sY8YKX7JM+jqS3EAti3YMzwHKWViveqkZvu" 17 | ]; 18 | } 19 | -------------------------------------------------------------------------------- /nixos/theme.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs, 3 | pkgs, 4 | lib, 5 | config, 6 | isLinux, 7 | isServer, 8 | ... 9 | }: 10 | { 11 | imports = [ inputs.tokyonight.homeManagerModules.default ]; 12 | home.sessionVariables.COLORSCHEME = "tokyonight"; 13 | # enable globally for all supported programs 14 | tokyonight = { 15 | enable = true; 16 | style = "night"; 17 | }; 18 | programs.btop.settings = { 19 | color_theme = "tokyonight-night"; 20 | theme_background = false; 21 | }; 22 | # handle GTK themeing 23 | gtk = { 24 | enable = isLinux && !isServer; 25 | theme = { 26 | package = pkgs.tokyonight-gtk-theme.override { 27 | # macos style window buttons 28 | tweakVariants = [ "macos" ]; 29 | }; 30 | name = "Tokyonight-Dark"; 31 | }; 32 | }; 33 | 34 | dconf.settings."org/gnome/shell/extensions/user-theme".name = "Tokyonight-Dark"; 35 | 36 | xdg.configFile = 37 | { } 38 | // lib.optionalAttrs isLinux { 39 | "gtk-4.0/assets".source = 40 | "${config.gtk.theme.package}/share/themes/${config.gtk.theme.name}/gtk-4.0/assets"; 41 | "gtk-4.0/gtk.css".source = 42 | "${config.gtk.theme.package}/share/themes/${config.gtk.theme.name}/gtk-4.0/gtk.css"; 43 | "gtk-4.0/gtk-dark.css".source = 44 | "${config.gtk.theme.package}/share/themes/${config.gtk.theme.name}/gtk-4.0/gtk-dark.css"; 45 | }; 46 | 47 | # My Neovim Lua is not generated by Nix 48 | programs.neovim.tokyonight.enable = false; 49 | 50 | } 51 | -------------------------------------------------------------------------------- /nvim/.luacheckrc: -------------------------------------------------------------------------------- 1 | globals = { 2 | 'vim', 3 | 'dbg', 4 | } 5 | -------------------------------------------------------------------------------- /nvim/.luarc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", 3 | "Lua.workspace.checkThirdParty": false 4 | } -------------------------------------------------------------------------------- /nvim/ftplugin/markdown.lua: -------------------------------------------------------------------------------- 1 | vim.api.nvim_set_option_value('wrap', true, { win = 0 }) 2 | vim.api.nvim_set_option_value('linebreak', true, { win = 0 }) 3 | 4 | -- set up autocmd to revert the above options when another filetype is focused 5 | vim.api.nvim_create_autocmd('BufWinEnter', { 6 | callback = function() 7 | if vim.bo.ft == 'markdown' then 8 | vim.api.nvim_set_option_value('wrap', true, { win = 0 }) 9 | vim.api.nvim_set_option_value('linebreak', true, { win = 0 }) 10 | else 11 | vim.api.nvim_set_option_value('wrap', false, { win = 0 }) 12 | vim.api.nvim_set_option_value('linebreak', false, { win = 0 }) 13 | end 14 | end, 15 | }) 16 | 17 | local ok, otter = pcall(require, 'otter') 18 | if ok then 19 | otter.activate() 20 | end 21 | -------------------------------------------------------------------------------- /nvim/ftplugin/nix.lua: -------------------------------------------------------------------------------- 1 | local ok, otter = pcall(require, 'otter') 2 | if ok then 3 | pcall(otter.activate) 4 | end 5 | -------------------------------------------------------------------------------- /nvim/init.lua: -------------------------------------------------------------------------------- 1 | -- use Neovim's experimental Lua module loader that does byte-caching of Lua modules 2 | vim.loader.enable() 3 | ---Debug Lua stuff and print a nice debug message via `vim.inspect`. 4 | ---@param ... any 5 | _G.dbg = function(...) 6 | local info = debug.getinfo(2, 'S') 7 | local source = info.source:sub(2) 8 | source = vim.loop.fs_realpath(source) or source 9 | source = vim.fn.fnamemodify(source, ':~:.') .. ':' .. info.linedefined 10 | local what = { ... } 11 | if vim.islist(what) and vim.tbl_count(what) <= 1 then 12 | what = what[1] 13 | end 14 | local msg = vim.inspect(vim.deepcopy(what)) 15 | vim.notify(msg, vim.log.levels.INFO, { 16 | title = 'Debug: ' .. source, 17 | on_open = function(win) 18 | vim.wo[win].conceallevel = 3 19 | vim.wo[win].concealcursor = '' 20 | vim.wo[win].spell = false 21 | local buf = vim.api.nvim_win_get_buf(win) 22 | vim.treesitter.start(buf, 'lua') 23 | end, 24 | }) 25 | end 26 | 27 | require('my.settings') 28 | require('my.plugins') 29 | 30 | vim.api.nvim_create_user_command('H', function() 31 | vim.cmd.help(vim.fn.expand('')) 32 | end, { desc = 'Help for cursorword' }) 33 | 34 | vim.api.nvim_create_autocmd('UiEnter', { 35 | callback = function() 36 | local bufs = vim.api.nvim_list_bufs() 37 | for _, buf in ipairs(bufs) do 38 | local bufname = vim.api.nvim_buf_get_name(buf) 39 | if #bufs == 1 and vim.fn.isdirectory(bufname) ~= 0 then 40 | -- if opened to a directory, cd to the directory 41 | vim.cmd.cd(bufname) 42 | break 43 | end 44 | 45 | if #bufname ~= 0 then 46 | return 47 | end 48 | end 49 | end, 50 | once = true, 51 | }) 52 | 53 | -- if I'm editing my nvim config, make sure I'm `cd`d into `nvim` 54 | vim.api.nvim_create_autocmd('BufRead', { 55 | once = true, 56 | pattern = '*', 57 | callback = function() 58 | local bufname = vim.api.nvim_buf_get_name(0) 59 | if 60 | string.find(bufname, '/git/dotfiles/nvim') and not vim.endswith(vim.loop.cwd()--[[@as string]], '/nvim') 61 | then 62 | vim.cmd.cd('./nvim') 63 | end 64 | end, 65 | }) 66 | 67 | -- open files to last location 68 | vim.api.nvim_create_autocmd('BufReadPost', { 69 | command = [[if line("'\"") >= 1 && line("'\"") <= line("$") && &ft !~# 'commit' | exe "normal! g`\"" | endif]], 70 | }) 71 | 72 | -- custom URL handling to open GitHub shorthands 73 | local open = vim.ui.open 74 | vim.ui.open = function(uri) ---@diagnostic disable-line: duplicate-set-field 75 | -- GitHub shorthand pattern, e.g. mrjones2014/dotfiles 76 | if not string.match(uri, '[a-z]*://[^ >,;]*') and string.match(uri, '[%w%p\\-]*/[%w%p\\-]*') then 77 | uri = string.format('https://github.com/%s', uri) 78 | elseif 79 | vim.api.nvim_get_option_value('filetype', { buf = 0 }) == 'rust' 80 | and not string.match(uri, '^https?://[%w-_%.%?%.:/%+=&]+') 81 | then 82 | -- if the cursorword is not a URL, see if we can open the docs page with rustaceanvim 83 | vim.cmd.RustLsp('openDocs') 84 | return 85 | end 86 | 87 | open(uri) 88 | end 89 | 90 | -- if in SSH session, copy to local system clipboard using OSC52 91 | -- See: https://github.com/neovim/neovim/discussions/28010#discussioncomment-9529001 92 | if vim.env.SSH_TTY then 93 | vim.api.nvim_create_autocmd('TextYankPost', { 94 | callback = function() 95 | local copy_to_unnamedplus = require('vim.ui.clipboard.osc52').copy('+') 96 | copy_to_unnamedplus(vim.v.event.regcontents) 97 | local copy_to_unnamed = require('vim.ui.clipboard.osc52').copy('*') 98 | copy_to_unnamed(vim.v.event.regcontents) 99 | end, 100 | }) 101 | end 102 | 103 | -- set up UI tweaks on load 104 | require('my.utils.lsp').apply_ui_tweaks() 105 | -------------------------------------------------------------------------------- /nvim/lua/my/cmd-palette.lua: -------------------------------------------------------------------------------- 1 | ---@class PaletteEntry 2 | ---@field text string|fun(buf:number, win:number):string 3 | ---@field on_selected fun(buf:number, win:number) 4 | 5 | ---@type PaletteEntry[] 6 | return { 7 | { 8 | text = ' Copy relative filepath', 9 | on_selected = function(buf) 10 | local filepath = vim.api.nvim_buf_get_name(buf) 11 | if not filepath or #filepath == 0 then 12 | vim.notify('Could not expand filepath') 13 | return 14 | end 15 | 16 | local relpath = vim.fn.simplify(require('my.utils.path').relative(vim.fn.expand('%') --[[@as string]])) 17 | require('my.utils.clipboard').copy(relpath) 18 | vim.notify('Relative filepath copied to clipboard') 19 | end, 20 | }, 21 | { 22 | text = '󰊢 Copy git branch name', 23 | on_selected = function(buf) 24 | local branch = vim.g.gitsigns_head or vim.b[buf].gitsigns_head 25 | if not branch or #branch == 0 then 26 | vim.notify('Could not determine git branch') 27 | return 28 | end 29 | require('my.utils.clipboard').copy(branch) 30 | end, 31 | }, 32 | } 33 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/autopairs.lua: -------------------------------------------------------------------------------- 1 | return { 2 | { 3 | 'windwp/nvim-autopairs', 4 | event = { 'InsertEnter' }, 5 | config = function() 6 | local Rule = require('nvim-autopairs.rule') 7 | local npairs = require('nvim-autopairs') 8 | local cond = require('nvim-autopairs.conds') 9 | npairs.setup({ 10 | disable_filetype = { 'snacks_picker_input' }, 11 | }) 12 | -- <> pair for generics and stuff, 13 | -- complete <> if the preceding text is alphanumeric or :: for Rust 14 | npairs.add_rule(Rule('<', '>', { 15 | -- *exclude* these filetypes so that nvim-ts-autotag works instead 16 | '-html', 17 | '-javascriptreact', 18 | '-typescriptreact', 19 | }):with_pair(cond.before_regex('%a+:?:?$', 3)):with_move(function(opts) 20 | return opts.char == '>' 21 | end)) 22 | end, 23 | }, 24 | { 25 | 'windwp/nvim-ts-autotag', 26 | ft = { 'html', 'javascriptreact', 'typescriptreact' }, 27 | opts = {}, 28 | }, 29 | } 30 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/colorizer.lua: -------------------------------------------------------------------------------- 1 | ---@diagnostic disable-next-line -- optional parameters omitted 2 | local filetypes = vim.list_extend(vim.deepcopy(require('my.lsp.filetypes').filetypes), { 'conf', 'tmux', 'Onedarkpro' }) 3 | 4 | return { 5 | 'NvChad/nvim-colorizer.lua', 6 | ft = filetypes, 7 | cmd = 'ColorizerAttachToBuffer', 8 | opts = { 9 | filetypes = filetypes, 10 | user_default_options = { 11 | names = false, 12 | css = true, 13 | sass = { enable = true, parsers = { 'css' } }, 14 | always_update = true, 15 | }, 16 | }, 17 | } 18 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/colorscheme.lua: -------------------------------------------------------------------------------- 1 | local function get_tokyonight_hl_fg(hl, group, guard) 2 | -- don't allow more than 5 levels of recursion 3 | guard = guard or 0 4 | if guard > 5 then 5 | return nil 6 | end 7 | 8 | if not hl[group] then 9 | return nil 10 | end 11 | 12 | if hl[group].link then 13 | return get_tokyonight_hl_fg(hl, hl[group].link, guard + 1) 14 | end 15 | 16 | return hl[group].fg 17 | end 18 | 19 | local colorscheme = vim.env.COLORSCHEME or 'tokyonight' 20 | 21 | return { 22 | 'folke/tokyonight.nvim', 23 | dependencies = { 24 | { 25 | 'folke/snacks.nvim', 26 | opts = { 27 | indent = { 28 | animate = { enabled = false }, 29 | }, 30 | }, 31 | }, 32 | }, 33 | enabled = colorscheme == 'tokyonight', 34 | lazy = false, 35 | priority = 1000, 36 | opts = { 37 | style = 'night', 38 | dim_inactive = true, 39 | plugins = { auto = true }, 40 | on_highlights = function(hl, c) 41 | hl.WinBar = { bg = c.fg_gutter } 42 | hl.WinBarNC = hl.WinBar 43 | -- navic in winbar 44 | hl.NavicText.bg = c.fg_gutter 45 | hl.NavicSeparator.bg = c.fg_gutter 46 | for key, _ in pairs(hl) do 47 | if vim.startswith(key, 'NavicIcons') then 48 | hl[key] = { bg = hl.WinBar.bg, fg = get_tokyonight_hl_fg(hl, key) } 49 | end 50 | end 51 | -- borderless pickers 52 | local prompt = '#2d3149' 53 | hl.SnacksPickerInput = { 54 | bg = prompt, 55 | fg = c.fg_dark, 56 | } 57 | hl.SnacksPickerInputBorder = { 58 | bg = prompt, 59 | fg = prompt, 60 | } 61 | hl.SnacksPickerPrompt = { 62 | bg = prompt, 63 | } 64 | hl.NormalFloat = { 65 | bg = c.bg, 66 | } 67 | hl.SnacksPickerBoxTitle = { 68 | bg = prompt, 69 | fg = prompt, 70 | } 71 | hl.SnacksPickerBoxBorder = hl.SnacksPickerBoxTitle 72 | hl.SnacksPickerBorder = { 73 | bg = c.bg, 74 | fg = c.bg, 75 | } 76 | hl.SnacksPickerPreviewTitle = hl.SnacksPickerBorder 77 | hl.SnacksPickerResultsTitle = hl.SnacksPickerBorder 78 | hl.SnacksPickerListTitle = hl.SnacksPickerBorder 79 | end, 80 | }, 81 | config = function(_, opts) 82 | require('tokyonight').setup(opts) 83 | vim.cmd.colorscheme('tokyonight-night') 84 | end, 85 | } 86 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/comments.lua: -------------------------------------------------------------------------------- 1 | return { 2 | { 3 | 'folke/todo-comments.nvim', 4 | event = 'BufRead', 5 | opts = { 6 | -- See result with below comments 7 | -- TODO a todo message 8 | -- FIX Fix me 9 | -- BUG this is a bug 10 | -- PERF performance note 11 | -- NOTE just a note 12 | -- HACK this is a hack 13 | -- WARN this is a warning 14 | -- WARNING this is also a warning 15 | -- 16 | -- TODO with a very long 17 | -- multiline comment 18 | -- 19 | -- SAFETY: a safety comment for a Rust `unsafe { }` block 20 | -- 21 | -- NB: nota bene 22 | highlight = { 23 | -- change pattern to not require a colon after the keyword 24 | pattern = [[.*<(KEYWORDS)\s*]], 25 | keyword = 'bg', 26 | comments_only = true, 27 | }, 28 | search = { pattern = [[.*<(KEYWORDS)\s*]] }, 29 | keywords = { 30 | SAFETY = { icon = '󰲉 ', color = 'warning' }, 31 | NB = { icon = '󰴄 ', color = 'info' }, 32 | }, 33 | }, 34 | }, 35 | { 36 | 'numToStr/Comment.nvim', 37 | event = 'BufRead', 38 | dependencies = { 'JoosepAlviste/nvim-ts-context-commentstring' }, 39 | config = function() 40 | -- default mappings: 41 | -- { 42 | -- toggler = { 43 | -- ---Line-comment toggle keymap 44 | -- line = 'gcc', 45 | -- ---Block-comment toggle keymap 46 | -- block = 'gbc', 47 | -- }, 48 | -- ---LHS of operator-pending mappings in NORMAL and VISUAL mode 49 | -- opleader = { 50 | -- ---Line-comment keymap 51 | -- line = 'gc', 52 | -- ---Block-comment keymap 53 | -- block = 'gb', 54 | -- }, 55 | -- ---LHS of extra mappings 56 | -- extra = { 57 | -- ---Add comment on the line above 58 | -- above = 'gcO', 59 | -- ---Add comment on the line below 60 | -- below = 'gco', 61 | -- ---Add comment at the end of line 62 | -- eol = 'gcA', 63 | -- } 64 | -- } 65 | 66 | require('Comment').setup({ ---@diagnostic disable-line: missing-fields 67 | pre_hook = require('ts_context_commentstring.integrations.comment_nvim').create_pre_hook(), 68 | }) 69 | require('Comment.ft').mysql = { '# %s', '/* %s */' } 70 | end, 71 | }, 72 | } 73 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/completion.lua: -------------------------------------------------------------------------------- 1 | return { 2 | 'saghen/blink.cmp', 3 | version = '*', 4 | dependencies = { 5 | { 6 | 'L3MON4D3/LuaSnip', 7 | version = 'v2.*', 8 | keys = { 9 | { 10 | '', 11 | function() 12 | require('luasnip').jump(-1) 13 | end, 14 | mode = { 'i', 's' }, 15 | desc = 'Jump to previous snippet node', 16 | }, 17 | { 18 | '', 19 | function() 20 | local ls = require('luasnip') 21 | if ls.expand_or_jumpable() then 22 | ls.expand_or_jump() 23 | end 24 | end, 25 | mode = { 'i', 's' }, 26 | desc = 'Expand or jump to next snippet node', 27 | }, 28 | { 29 | '', 30 | function() 31 | local ls = require('luasnip') 32 | if ls.choice_active() then 33 | ls.change_choice(-1) 34 | end 35 | end, 36 | mode = { 'i', 's' }, 37 | desc = 'Select previous choice in snippet choice nodes', 38 | }, 39 | { 40 | '', 41 | function() 42 | local ls = require('luasnip') 43 | if ls.choice_active() then 44 | ls.change_choice(1) 45 | end 46 | end, 47 | mode = { 'i', 's' }, 48 | desc = 'Select next choice in snippet choice nodes', 49 | }, 50 | { 51 | '', 52 | function() 53 | require('luasnip').unlink_current() 54 | end, 55 | mode = { 'i', 'n' }, 56 | desc = 'Clear snippet jumps', 57 | }, 58 | }, 59 | }, 60 | { 'folke/lazydev.nvim' }, 61 | }, 62 | opts = { 63 | enabled = function() 64 | return not vim.tbl_contains({ 'minifiles' }, vim.bo.filetype) 65 | end, 66 | snippets = { preset = 'luasnip' }, 67 | signature = { enabled = true }, 68 | keymap = { 69 | preset = 'enter', 70 | [''] = { 'select_next', 'fallback' }, 71 | [''] = { 'select_prev', 'fallback' }, 72 | [''] = {}, 73 | [''] = {}, 74 | }, 75 | completion = { 76 | menu = { 77 | draw = { 78 | columns = { 79 | { 'label', 'label_description', gap = 1 }, 80 | { 'kind_icon', 'kind' }, 81 | }, 82 | }, 83 | }, 84 | documentation = { auto_show = true, auto_show_delay_ms = 0 }, 85 | ghost_text = { enabled = true }, 86 | }, 87 | cmdline = { 88 | keymap = { 89 | [''] = {}, 90 | [''] = { 'accept', 'fallback' }, 91 | }, 92 | completion = { 93 | menu = { auto_show = true }, 94 | }, 95 | }, 96 | sources = { 97 | default = { 'lazydev', 'lsp', 'path', 'snippets', 'buffer' }, 98 | providers = { 99 | lazydev = { 100 | name = 'LazyDev', 101 | module = 'lazydev.integrations.blink', 102 | -- boost lazydev suggestions to top 103 | score_offset = 100, 104 | }, 105 | }, 106 | }, 107 | }, 108 | } 109 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/flash.lua: -------------------------------------------------------------------------------- 1 | return { 2 | 'folke/flash.nvim', 3 | keys = { 4 | { 5 | 's', 6 | function() 7 | require('flash').jump() 8 | end, 9 | mode = { 'n', 'x', 'o' }, 10 | desc = 'Jump forwards', 11 | }, 12 | { 13 | 'S', 14 | function() 15 | require('flash').jump({ search = { forward = false } }) 16 | end, 17 | mode = { 'n', 'x', 'o' }, 18 | desc = 'Jump backwards', 19 | }, 20 | }, 21 | opts = { 22 | jump = { nohlsearch = true }, 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/git.lua: -------------------------------------------------------------------------------- 1 | local clipboard = require('my.utils.clipboard') 2 | return { 3 | { 4 | 'folke/snacks.nvim', 5 | keys = { 6 | { 7 | 'gy', 8 | function() 9 | clipboard.copy(require('snacks.gitbrowse').get_url()) 10 | end, 11 | desc = 'Copy git permalink', 12 | silent = true, 13 | }, 14 | }, 15 | opts = { 16 | gitbrowse = { 17 | what = 'permalink', 18 | url_patterns = { 19 | ['gitlab%.1password%.io'] = { 20 | branch = '/-/tree/{branch}', 21 | file = '/-/blob/{branch}/{file}#L{line_start}-L{line_end}', 22 | permalink = '/-/blob/{commit}/{file}#L{line_start}-L{line_end}', 23 | commit = '/-/commit/{commit}', 24 | }, 25 | }, 26 | }, 27 | }, 28 | }, 29 | { 30 | 'lewis6991/gitsigns.nvim', 31 | lazy = false, 32 | keys = { 33 | { 34 | 'bl', 35 | function() 36 | vim.cmd.Gitsigns('toggle_current_line_blame') 37 | end, 38 | desc = 'Toggle inline git blame', 39 | }, 40 | }, 41 | opts = { 42 | current_line_blame = false, 43 | current_line_blame_opts = { 44 | virt_text = true, 45 | virt_text_pos = 'eol', 46 | delay = 100, 47 | }, 48 | signcolumn = true, 49 | current_line_blame_formatter = ' | , - ', 50 | on_attach = function() 51 | vim.cmd.redrawstatus() 52 | end, 53 | }, 54 | }, 55 | { 56 | 'sindrets/diffview.nvim', 57 | cmd = { 58 | 'DiffviewLog', 59 | 'DiffviewOpen', 60 | 'DiffviewClose', 61 | 'DiffviewRefresh', 62 | 'DiffviewFocusFiles', 63 | 'DiffviewFileHistory', 64 | 'DiffviewToggleFiles', 65 | }, 66 | opts = { 67 | enhanced_diff_hl = true, 68 | view = { 69 | file_panel = { 70 | win_config = { 71 | position = 'right', 72 | }, 73 | }, 74 | }, 75 | }, 76 | }, 77 | { 78 | 'pwntester/octo.nvim', 79 | cmd = 'Octo', 80 | opts = { 81 | gh_env = function() 82 | local github_token = require('op').get_secret('GitHub', 'token') 83 | if not github_token or not vim.startswith(github_token, 'ghp_') then 84 | error('Failed to get GitHub token.') 85 | end 86 | 87 | return { GITHUB_TOKEN = github_token } 88 | end, 89 | }, 90 | }, 91 | } 92 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/grug_far.lua: -------------------------------------------------------------------------------- 1 | return { 2 | 'MagicDuck/grug-far.nvim', 3 | cmd = { 'GrugFar', 'GrugFarWithin' }, 4 | keys = { 5 | { 6 | 's', 7 | function() 8 | require('grug-far').open({}) 9 | end, 10 | mode = 'n', 11 | desc = 'Search and replace', 12 | }, 13 | { 14 | 'f', 15 | function() 16 | require('grug-far').open({ prefills = { paths = vim.fn.expand('%') } }) 17 | end, 18 | desc = 'Search and replace in current file', 19 | }, 20 | { 21 | 's', 22 | function() 23 | require('grug-far').open({ visualSelectionUsage = 'operate-within-range' }) 24 | end, 25 | mode = 'v', 26 | desc = 'Search and replace visual selection', 27 | }, 28 | }, 29 | } 30 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/heirline/conditions.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | function M.should_show_filename(bufname) 4 | local bt = vim.api.nvim_get_option_value('buftype', { buf = 0 }) 5 | local ft = vim.api.nvim_get_option_value('filetype', { buf = 0 }) 6 | return (not bt or bt == '') 7 | and ft ~= 'nofile' 8 | and ft ~= 'Trouble' 9 | and ft ~= 'snacks_picker_input' 10 | and ft ~= 'help' 11 | and bufname 12 | and bufname ~= '' 13 | end 14 | 15 | function M.is_floating_window(win_id) 16 | win_id = win_id or 0 17 | local win_cfg = vim.api.nvim_win_get_config(win_id) 18 | return win_cfg and (win_cfg.relative ~= '' or not win_cfg.relative) 19 | end 20 | 21 | return M 22 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/heirline/init.lua: -------------------------------------------------------------------------------- 1 | return { 2 | { 3 | 'SmiteshP/nvim-navic', 4 | init = function() 5 | require('my.utils.lsp').on_attach(function(client, bufnr) 6 | if 7 | client.server_capabilities.documentSymbolProvider 8 | and client.name ~= 'nil_ls' -- don't attach to both nil_ls and nixd 9 | and not vim.startswith(client.name, 'otter-ls') -- don't attach to otter-ls 10 | then 11 | require('nvim-navic').attach(client, bufnr) 12 | end 13 | end) 14 | end, 15 | config = function() 16 | require('nvim-navic').setup({ 17 | highlight = true, 18 | separator = '  ', 19 | }) 20 | end, 21 | }, 22 | { 23 | 'rebelot/heirline.nvim', 24 | lazy = false, 25 | config = function() 26 | local shared = require('my.configure.heirline.shared') 27 | local sl = require('my.configure.heirline.statusline') 28 | local wb = require('my.configure.heirline.winbar') 29 | 30 | local colors = require('tokyonight.colors').setup() 31 | require('heirline').setup({ 32 | opts = { 33 | colors = { 34 | black = colors.bg_dark, 35 | gray = colors.dark5, 36 | green = colors.green, 37 | blue = colors.blue, 38 | yellow = colors.terminal.yellow_bright, 39 | base = colors.bg, 40 | surface0 = colors.fg_gutter, 41 | surface1 = colors.dark3, 42 | surface2 = colors.blue7, 43 | }, 44 | disable_winbar_cb = function() 45 | local conditions = require('my.configure.heirline.conditions') 46 | return conditions.is_floating_window() or not conditions.should_show_filename(vim.api.nvim_buf_get_name(0)) 47 | end, 48 | }, 49 | statusline = { ---@diagnostic disable-line:missing-fields 50 | sl.Mode, 51 | sl.Branch, 52 | shared.FileIcon('surface0'), 53 | sl.FilePath, 54 | sl.Align, 55 | sl.UnsavedChanges, 56 | sl.Align, 57 | sl.RecordingMacro, 58 | sl.SpellCheckToggle, 59 | sl.LspFormatToggle, 60 | sl.LazyStats, 61 | shared.Diagnostics(false), 62 | }, 63 | winbar = { ---@diagnostic disable-line:missing-fields 64 | shared.FileIcon('base'), 65 | wb.UniqueFilename, 66 | wb.Diagnostics, 67 | shared.Trunc, 68 | wb.Navic, 69 | }, 70 | }) 71 | end, 72 | }, 73 | } 74 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/heirline/separators.lua: -------------------------------------------------------------------------------- 1 | return { 2 | rounded_left = '', 3 | rounded_right = '', 4 | } 5 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/heirline/shared.lua: -------------------------------------------------------------------------------- 1 | local utils = require('heirline.utils') 2 | 3 | local M = {} 4 | 5 | function M.FileIcon(bg_color) 6 | return { 7 | init = function(self) 8 | self.bufname = vim.api.nvim_buf_get_name(0) 9 | local icon, hl = require('nvim-web-devicons').get_icon(self.bufname, vim.fn.fnamemodify(self.bufname, ':e')) 10 | self.icon = icon 11 | self.icon_hl = hl 12 | end, 13 | provider = ' ', 14 | hl = { bg = bg_color }, 15 | { 16 | condition = function(self) 17 | return self.icon ~= nil 18 | and self.icon_hl ~= nil 19 | and require('my.configure.heirline.conditions').should_show_filename(self.bufname) 20 | end, 21 | provider = function(self) 22 | return self.icon 23 | end, 24 | hl = function(self) 25 | return { fg = utils.get_highlight(self.icon_hl).fg, bg = bg_color } 26 | end, 27 | }, 28 | } 29 | end 30 | 31 | local diagnostics_order = { 32 | vim.diagnostic.severity.HINT, 33 | vim.diagnostic.severity.INFO, 34 | vim.diagnostic.severity.WARN, 35 | vim.diagnostic.severity.ERROR, 36 | } 37 | local severity_name = { 38 | [vim.diagnostic.severity.HINT] = 'Hint', 39 | [vim.diagnostic.severity.INFO] = 'Info', 40 | [vim.diagnostic.severity.WARN] = 'Warn', 41 | [vim.diagnostic.severity.ERROR] = 'Error', 42 | } 43 | local diagnostics_base = { 44 | update = { 'DiagnosticChanged', 'BufEnter' }, 45 | init = function(self) 46 | self.counts = vim.diagnostic.count(0) 47 | end, 48 | } 49 | 50 | function M.Diagnostics(is_winbar, bg) 51 | bg = bg or 'surface0' 52 | return utils.insert( 53 | diagnostics_base, 54 | unpack(vim 55 | .iter(diagnostics_order) 56 | :map(function(severity) 57 | local component = { 58 | provider = function(self) 59 | local sign = vim.diagnostic.config().signs.text[severity] 60 | return string.format('%s%s ', sign, self.counts[severity] or 0) 61 | end, 62 | hl = function() 63 | return { fg = utils.get_highlight(string.format('DiagnosticSign%s', severity_name[severity])).fg, bg = bg } 64 | end, 65 | } 66 | if is_winbar then 67 | component.condition = function(self) 68 | return (self.counts[severity] or 0) > 0 69 | end 70 | end 71 | return component 72 | end) 73 | :totable()) 74 | ) 75 | end 76 | 77 | M.Trunc = { 78 | provider = '%<', 79 | } 80 | 81 | return M 82 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/heirline/winbar.lua: -------------------------------------------------------------------------------- 1 | local conditions = require('heirline.conditions') 2 | local sep = require('my.configure.heirline.separators') 3 | 4 | local special_filenames = { 'mod.rs', 'lib.rs' } 5 | 6 | local function get_current_filenames() 7 | local listed_buffers = vim 8 | .iter(vim.api.nvim_list_bufs()) 9 | :filter(function(bufnr) 10 | return vim.bo[bufnr].buflisted and vim.api.nvim_buf_is_loaded(bufnr) 11 | end) 12 | :totable() 13 | 14 | return vim.iter(listed_buffers):map(vim.api.nvim_buf_get_name):totable() 15 | end 16 | 17 | -- Get unique name for the current buffer 18 | local function get_unique_filename(filename) 19 | local filenames = vim 20 | .iter(get_current_filenames()) 21 | :filter(function(filename_other) 22 | return filename_other ~= filename 23 | end) 24 | :map(string.reverse) 25 | :totable() -- Reverse filenames in order to compare their names 26 | filename = string.reverse(filename) 27 | 28 | local index 29 | 30 | -- For every other filename, compare it with the name of the current file char-by-char to 31 | -- find the minimum index `i` where the i-th character is different for the two filenames 32 | -- After doing it for every filename, get the maximum value of `i` 33 | if next(filenames) then 34 | index = math.max(unpack(vim 35 | .iter(filenames) 36 | :map(function(filename_other) 37 | for i = 1, #filename do 38 | -- Compare i-th character of both names until they aren't equal 39 | if filename:sub(i, i) ~= filename_other:sub(i, i) then 40 | return i 41 | end 42 | end 43 | return 1 44 | end) 45 | :totable())) 46 | else 47 | index = 1 48 | end 49 | 50 | -- Iterate backwards (since filename is reversed) until a "/" is found 51 | -- in order to show a valid file path 52 | while index <= #filename do 53 | if filename:sub(index, index) == '/' then 54 | index = index - 1 55 | break 56 | end 57 | 58 | index = index + 1 59 | end 60 | 61 | local result = string.reverse(string.sub(filename, 1, index)) 62 | -- for special filenames like `lib.rs`, `mod.rs`, `index.ts` etc. 63 | -- always show at least one parent 64 | if vim.iter(ipairs(special_filenames)):any(function(_, special) 65 | return special == result 66 | end) then 67 | local parts = vim.split(string.reverse(filename), '/') 68 | -- if parent is just `src` then show another parent 69 | if parts[#parts - 1] == 'src' then 70 | return table.concat({ parts[#parts - 2], parts[#parts - 1], parts[#parts] }, '/') 71 | end 72 | return table.concat({ parts[#parts - 1], parts[#parts] }, '/') 73 | end 74 | return result 75 | end 76 | 77 | local M = {} 78 | 79 | M.UniqueFilename = { 80 | init = function(self) 81 | self.bufname = get_unique_filename(vim.api.nvim_buf_get_name(0)) 82 | end, 83 | { 84 | condition = function(self) 85 | return require('my.configure.heirline.conditions').should_show_filename(self.bufname) 86 | end, 87 | provider = function(self) 88 | return string.format(' %s ', self.bufname) 89 | end, 90 | hl = { bg = 'base' }, 91 | }, 92 | { 93 | -- file save status indicator 94 | condition = function() 95 | return vim.bo.modified == true 96 | end, 97 | provider = ' ', 98 | hl = { bg = 'base' }, 99 | }, 100 | { 101 | provider = sep.rounded_right, 102 | hl = function() 103 | return { bg = conditions.has_diagnostics() and 'surface1' or 'surface2', fg = 'base' } 104 | end, 105 | }, 106 | } 107 | 108 | M.Diagnostics = { 109 | provider = ' ', 110 | hl = { bg = 'surface1' }, 111 | condition = conditions.has_diagnostics, 112 | require('my.configure.heirline.shared').Diagnostics(true, 'surface1'), 113 | { 114 | provider = sep.rounded_right, 115 | hl = { fg = 'surface1', bg = 'surface2' }, 116 | }, 117 | } 118 | 119 | M.Navic = { 120 | condition = function() 121 | return conditions.lsp_attached() and require('nvim-navic').is_available() 122 | end, 123 | provider = function() 124 | return string.format(' %s', require('nvim-navic').get_location()) 125 | end, 126 | hl = { bg = 'surface0' }, 127 | } 128 | 129 | return M 130 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/init.lua: -------------------------------------------------------------------------------- 1 | -- plugins with little or no config can go here 2 | 3 | return { 4 | { 'nvim-lua/plenary.nvim' }, 5 | { 6 | 'DaikyXendo/nvim-material-icon', 7 | main = 'nvim-web-devicons', 8 | opts = {}, 9 | }, 10 | { 'tpope/vim-eunuch', cmd = { 'Delete', 'Move', 'Chmod', 'SudoWrite', 'Rename' } }, 11 | { 'tpope/vim-sleuth', event = 'BufReadPre' }, 12 | { 13 | 'nat-418/boole.nvim', 14 | keys = { '', '' }, 15 | opts = { mappings = { increment = '', decrement = '' } }, 16 | }, 17 | { 18 | 'mrjones2014/iconpicker.nvim', 19 | cmds = { 'Icons' }, 20 | init = function() 21 | vim.api.nvim_create_user_command('Icons', function() 22 | require('iconpicker').pick(function(icon) 23 | if not icon or #icon == 0 then 24 | return 25 | end 26 | 27 | require('my.utils.clipboard').copy(icon) 28 | vim.notify('Copied icon to clipboard.', vim.log.levels.INFO) 29 | end) 30 | end, { desc = 'Pick NerdFont icons and copy to clipboard' }) 31 | end, 32 | }, 33 | { 'mrjones2014/lua-gf.nvim', dev = true, ft = 'lua' }, 34 | { 35 | 'echasnovski/mini.splitjoin', 36 | keys = { 37 | { 38 | 'gS', 39 | function() 40 | require('mini.splitjoin').toggle() 41 | end, 42 | desc = 'Split/join arrays, argument lists, etc. from one vs. multiline and vice versa', 43 | }, 44 | }, 45 | }, 46 | { 'echasnovski/mini.trailspace', event = 'BufRead', opts = {} }, 47 | { 48 | 'max397574/better-escape.nvim', 49 | event = 'InsertEnter', 50 | opts = { 51 | mappings = { 52 | -- do not map jj because I use jujutsu and the command is jj 53 | i = { 54 | j = { k = '', j = false }, 55 | k = { k = '', j = '' }, 56 | }, 57 | c = { 58 | j = { k = '', j = false }, 59 | k = { k = '', j = '' }, 60 | }, 61 | }, 62 | }, 63 | }, 64 | { 65 | 'saecki/crates.nvim', 66 | event = { 'BufRead Cargo.toml' }, 67 | opts = {}, 68 | }, 69 | { 70 | 'folke/lazy.nvim', 71 | lazy = false, 72 | keys = { 73 | { 74 | 'L', 75 | function() 76 | vim.cmd.Lazy() 77 | end, 78 | }, 79 | }, 80 | }, 81 | } 82 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/markdown.lua: -------------------------------------------------------------------------------- 1 | return { 2 | { 3 | 'MeanderingProgrammer/markdown.nvim', 4 | main = 'render-markdown', 5 | dependencies = { 'nvim-treesitter/nvim-treesitter' }, 6 | opts = {}, 7 | ft = 'markdown', 8 | }, 9 | { 10 | 'OXY2DEV/helpview.nvim', 11 | ft = 'help', 12 | dependencies = { 'nvim-treesitter/nvim-treesitter' }, 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/mini_bracketed.lua: -------------------------------------------------------------------------------- 1 | return { 2 | 'echasnovski/mini.bracketed', 3 | keys = { 4 | { '[c', desc = 'Jump to previous comment block' }, 5 | { ']c', desc = 'Jump to next comment block' }, 6 | { '[x', desc = 'Jump to previous conflict marker' }, 7 | { ']x', desc = 'Jump to next conflict marker' }, 8 | { '[d', desc = 'Jump to previous diagnostic' }, 9 | { ']d', desc = 'Jump to next diagnostic' }, 10 | { '[q', desc = 'Jump to previous Quickfix list entry' }, 11 | { ']q', desc = 'Jump to next Quickfix list entry' }, 12 | { '[t', desc = 'Jump to previous Treesitter node' }, 13 | { ']t', desc = 'Jump to next Treesitter node' }, 14 | }, 15 | opts = {}, 16 | } 17 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/mini_files.lua: -------------------------------------------------------------------------------- 1 | return { 2 | 'echasnovski/mini.files', 3 | dependencies = { 4 | { 5 | 'folke/snacks.nvim', 6 | lazy = false, 7 | opts = { rename = { enabled = false } }, 8 | }, 9 | }, 10 | init = function() 11 | vim.api.nvim_create_autocmd('User', { 12 | pattern = 'MiniFilesActionRename', 13 | callback = function(event) 14 | -- LSP rename files when renamed via mini.files 15 | require('snacks.rename').on_rename_file(event.data.from, event.data.to) 16 | end, 17 | }) 18 | vim.api.nvim_create_autocmd('User', { 19 | pattern = 'MiniFilesBufferCreate', 20 | callback = function(args) 21 | local minifiles = require('mini.files') 22 | local buf = args.data.buf_id 23 | 24 | -- close with as well as q 25 | vim.keymap.set('n', '', function() 26 | minifiles.close() 27 | end, { buffer = buf }) 28 | 29 | -- set up ability to confirm changes with :w 30 | vim.api.nvim_set_option_value('buftype', 'acwrite', { buf = buf }) 31 | vim.api.nvim_buf_set_name(buf, string.format('mini.files-%s', vim.loop.hrtime())) 32 | vim.api.nvim_create_autocmd('BufWriteCmd', { 33 | buffer = buf, 34 | callback = function() 35 | minifiles.synchronize() 36 | end, 37 | }) 38 | 39 | -- ctrl+v to open selected buffer in a split 40 | vim.keymap.set('n', '', function() 41 | vim.api.nvim_win_call(minifiles.get_explorer_state().target_window, function() 42 | vim.cmd.vsp() 43 | minifiles.set_target_window(vim.api.nvim_get_current_win()) 44 | end) 45 | minifiles.go_in({ close_on_file = true }) 46 | end, { desc = 'Open file in split window', buffer = buf }) 47 | end, 48 | }) 49 | end, 50 | keys = { 51 | { 52 | '', 53 | function() 54 | local minifiles = require('mini.files') 55 | if vim.bo.ft == 'minifiles' then 56 | minifiles.close() 57 | else 58 | local file = vim.api.nvim_buf_get_name(0) 59 | local file_exists = vim.fn.filereadable(file) ~= 0 60 | minifiles.open(file_exists and file or nil) 61 | minifiles.reveal_cwd() 62 | end 63 | end, 64 | desc = 'Open mini.files', 65 | }, 66 | }, 67 | opts = { 68 | content = { 69 | filter = function(entry) 70 | return entry.name ~= '.DS_Store' and entry.name ~= '.git' and entry.name ~= '.direnv' 71 | end, 72 | sort = function(entries) 73 | -- technically can filter entries here too, and checking gitignore for _every entry individually_ 74 | -- like I would have to in `content.filter` above is too slow. Here we can give it _all_ the entries 75 | -- at once, which is much more performant. 76 | local all_paths = table.concat( 77 | vim 78 | .iter(entries) 79 | :map(function(entry) 80 | return entry.path 81 | end) 82 | :totable(), 83 | '\n' 84 | ) 85 | local output_lines = {} 86 | local job_id = vim.fn.jobstart({ 'git', 'check-ignore', '--stdin' }, { 87 | stdout_buffered = true, 88 | on_stdout = function(_, data) 89 | output_lines = data 90 | end, 91 | }) 92 | 93 | -- command failed to run 94 | if job_id < 1 then 95 | return entries 96 | end 97 | 98 | -- send paths via STDIN 99 | vim.fn.chansend(job_id, all_paths) 100 | vim.fn.chanclose(job_id, 'stdin') 101 | vim.fn.jobwait({ job_id }) 102 | return require('mini.files').default_sort(vim 103 | .iter(entries) 104 | :filter(function(entry) 105 | return not vim.tbl_contains(output_lines, entry.path) 106 | end) 107 | :totable()) 108 | end, 109 | }, 110 | mappings = { 111 | go_in_plus = '', 112 | }, 113 | windows = { 114 | preview = true, 115 | width_preview = 120, 116 | }, 117 | }, 118 | } 119 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/mini_move.lua: -------------------------------------------------------------------------------- 1 | return { 2 | 'echasnovski/mini.move', 3 | keys = { 4 | { '', desc = 'Move text left', mode = { 'n', 'v', 'x' } }, 5 | { '', desc = 'Move text right', mode = { 'n', 'v', 'x' } }, 6 | { '', desc = 'Move text down', mode = { 'n', 'v', 'x' } }, 7 | { '', desc = 'Move text up', mode = { 'n', 'v', 'x' } }, 8 | }, 9 | opts = { 10 | mappings = { 11 | left = '', 12 | right = '', 13 | up = '', 14 | down = '', 15 | line_left = '', 16 | line_right = '', 17 | line_up = '', 18 | line_down = '', 19 | }, 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/neotest.lua: -------------------------------------------------------------------------------- 1 | local cmds = { 2 | { 3 | 'Test', 4 | function() 5 | require('neotest').run.run() 6 | end, 7 | { 8 | desc = 'Run nearest test', 9 | }, 10 | }, 11 | { 12 | 'TestFile', 13 | function() 14 | require('neotest').run.run(vim.fn.expand('%')) 15 | end, 16 | { desc = 'Run tests in current file' }, 17 | }, 18 | { 19 | 'TestStop', 20 | function() 21 | require('neotest').run.stop() 22 | end, 23 | { desc = 'Kill running tests' }, 24 | }, 25 | { 26 | 'TestOpen', 27 | function() 28 | require('neotest').output.open() 29 | end, 30 | { desc = 'Open test output' }, 31 | }, 32 | { 33 | 'TestSummary', 34 | function() 35 | require('neotest').summary.open() 36 | end, 37 | { desc = 'Open test summary' }, 38 | }, 39 | } 40 | 41 | return { 42 | 'nvim-neotest/neotest', 43 | dependencies = { 44 | 'nvim-neotest/nvim-nio', 45 | 'nvim-neotest/neotest-plenary', 46 | 'nvim-neotest/neotest-go', 47 | 'nvim-neotest/neotest-jest', 48 | 'mrcjkb/rustaceanvim', 49 | }, 50 | cmds = vim 51 | .iter(cmds) 52 | :map(function(cmd) 53 | return cmd[1] 54 | end) 55 | :totable(), 56 | init = function() 57 | for _, cmd in ipairs(cmds) do 58 | vim.api.nvim_create_user_command(cmd[1], cmd[2], cmd[3]) 59 | end 60 | end, 61 | config = function() 62 | ---@diagnostic disable: missing-fields 63 | require('neotest').setup({ 64 | discovery = { enabled = false }, 65 | diagnostic = { enabled = true }, 66 | status = { enabled = true }, 67 | icons = { 68 | expanded = '', 69 | child_prefix = '', 70 | child_indent = '', 71 | final_child_prefix = '', 72 | non_collapsible = '', 73 | collapsed = '', 74 | 75 | passed = '', 76 | running = '', 77 | failed = '', 78 | unknown = '', 79 | }, 80 | summary = { 81 | mappings = { 82 | jumpto = '', 83 | expand = { '', '<2-LeftMouse>' }, 84 | }, 85 | }, 86 | adapters = { 87 | require('neotest-plenary'), 88 | require('neotest-go'), 89 | require('rustaceanvim.neotest'), 90 | require('neotest-jest')({ 91 | jestCommand = 'pnpm jest', 92 | env = { CI = true }, 93 | cwd = function(path) 94 | return require('lspconfig.util').root_pattern('package.json', 'jest.config.js')(path) 95 | end, 96 | }), 97 | }, 98 | }) 99 | end, 100 | } 101 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/noice.lua: -------------------------------------------------------------------------------- 1 | return { 2 | 'folke/noice.nvim', 3 | dependencies = { 4 | 'rcarriga/nvim-notify', 5 | 'MunifTanjim/nui.nvim', 6 | }, 7 | event = 'VeryLazy', 8 | opts = { 9 | lsp = { 10 | signature = { enabled = true }, 11 | hover = { enabled = true }, 12 | documentation = { 13 | opts = { 14 | win_options = { 15 | concealcursor = 'n', 16 | conceallevel = 3, 17 | winhighlight = { Normal = 'LspFloat' }, 18 | }, 19 | }, 20 | }, 21 | override = { 22 | ['vim.lsp.util.convert_input_to_markdown_lines'] = true, 23 | ['vim.lsp.util.stylize_markdown'] = true, 24 | -- ['cmp.entry.get_documentation'] = true, 25 | }, 26 | }, 27 | cmdline = { 28 | format = { 29 | cmdline = { icon = ' ' }, 30 | search_down = { icon = '  ' }, 31 | search_up = { icon = '  ' }, 32 | filter = { icon = ' ', lang = 'fish' }, 33 | lua = { icon = ' ' }, 34 | help = { icon = ' 󰋖' }, 35 | }, 36 | opts = { 37 | position = { 38 | row = '98%', 39 | col = 0, 40 | }, 41 | size = { width = '100%' }, 42 | border = { 43 | padding = { 0, 3 }, 44 | }, 45 | win_options = { 46 | winhighlight = { 47 | Normal = 'SnacksPickerInput', 48 | FloatBorder = 'SnacksPickerInputBorder', 49 | }, 50 | }, 51 | }, 52 | }, 53 | history = { 54 | filter = {}, 55 | }, 56 | routes = { 57 | { 58 | filter = { 59 | any = { 60 | { find = 'No active Snippet' }, 61 | { find = 'No signature help available' }, 62 | { find = '^<$' }, 63 | { kind = 'wmsg' }, 64 | }, 65 | }, 66 | opts = { skip = true }, 67 | }, 68 | }, 69 | views = { 70 | mini = { 71 | position = { 72 | row = '98%', 73 | }, 74 | }, 75 | popup = { 76 | win_options = { 77 | winhighlight = { 78 | Normal = 'NormalFloat', 79 | FloatBorder = 'FloatBorder', 80 | }, 81 | }, 82 | }, 83 | }, 84 | }, 85 | } 86 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/op.lua: -------------------------------------------------------------------------------- 1 | return { 2 | 'mrjones2014/op.nvim', 3 | dev = true, 4 | build = 'make install', 5 | cmd = { 6 | 'OpSignin', 7 | 'OpSignout', 8 | 'OpWhoami', 9 | 'OpCreate', 10 | 'OpView', 11 | 'OpEdit', 12 | 'OpOpen', 13 | 'OpInsert', 14 | 'OpNote', 15 | 'OpSidebar', 16 | 'OpAnalyzeBuffer', 17 | }, 18 | opts = { 19 | sidebar = { 20 | side = 'left', 21 | }, 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/rustaceanvim.lua: -------------------------------------------------------------------------------- 1 | return { 2 | 'mrcjkb/rustaceanvim', 3 | ft = 'rust', 4 | version = '^5', 5 | depenencies = { 'folke/neoconf.nvim' }, 6 | keys = { 7 | { 8 | 'rd', 9 | function() 10 | vim.cmd.RustLsp('relatedDiagnostics') 11 | end, 12 | desc = 'Open related diagnostics', 13 | }, 14 | { 15 | 'oc', 16 | function() 17 | vim.cmd.vsp() 18 | vim.cmd.RustLsp('openCargo') 19 | end, 20 | desc = 'Open Cargo.toml in vert split', 21 | }, 22 | }, 23 | init = function() 24 | local neoconf = require('neoconf') 25 | require('my.lsp.snippets').rust() 26 | vim.g.rustaceanvim = { 27 | server = { 28 | cmd = { vim.env.NVIM_RUST_ANALYZER }, 29 | default_settings = { 30 | ['rust-analyzer'] = { 31 | cargo = { allFeatures = true, targetDir = true }, 32 | check = { 33 | allTargets = true, 34 | command = 'clippy', 35 | }, 36 | diagnostics = { 37 | disabled = { 'inactive-code', 'unresolved-proc-macro' }, 38 | }, 39 | procMacro = { enable = true }, 40 | flags = { exit_timeout = 100 }, 41 | files = { 42 | -- Make rustaceanvim effectively work with neoconf for the fields we care about 43 | excludeDirs = vim.tbl_get(neoconf.get('vscode') or {}, 'rust-analyzer', 'files', 'excludeDirs') or { 44 | 'target', 45 | 'node_modules', 46 | '.direnv', 47 | '.git', 48 | }, 49 | }, 50 | }, 51 | }, 52 | }, 53 | } 54 | end, 55 | } 56 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/smart-splits.lua: -------------------------------------------------------------------------------- 1 | local function lazymap(module, fn) 2 | return function() 3 | require(module)[fn]() 4 | end 5 | end 6 | 7 | return { 8 | 'mrjones2014/smart-splits.nvim', 9 | dev = true, 10 | lazy = false, 11 | opts = { ignored_buftypes = { 'nofile' }, cursor_follows_swapped_bufs = true }, 12 | keys = { 13 | { '', lazymap('smart-splits', 'move_cursor_left'), desc = 'Move to left window' }, 14 | { '', lazymap('smart-splits', 'move_cursor_down'), desc = 'Move to downward window' }, 15 | { '', lazymap('smart-splits', 'move_cursor_up'), desc = 'Move to upward window' }, 16 | { '', lazymap('smart-splits', 'move_cursor_right'), desc = 'Move to right window' }, 17 | { '', lazymap('smart-splits', 'resize_left'), desc = 'Resize window left' }, 18 | { '', lazymap('smart-splits', 'resize_down'), desc = 'Resize window down' }, 19 | { '', lazymap('smart-splits', 'resize_up'), desc = 'Resize window up' }, 20 | { '', lazymap('smart-splits', 'resize_right'), desc = 'Resize window right' }, 21 | { 'h', lazymap('smart-splits', 'swap_buf_left'), desc = 'Swap buffer left' }, 22 | { 'j', lazymap('smart-splits', 'swap_buf_up'), desc = 'Swap buffer left' }, 23 | { 'k', lazymap('smart-splits', 'swap_buf_down'), desc = 'Swap buffer left' }, 24 | { 'l', lazymap('smart-splits', 'swap_buf_right'), desc = 'Swap buffer left' }, 25 | }, 26 | } 27 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/snacks_picker.lua: -------------------------------------------------------------------------------- 1 | local path = require('my.utils.path') 2 | 3 | local ripgrep_ignore_file_path = ( 4 | path.join(vim.env.XDG_CONFIG_HOME or path.join(vim.env.HOME, '.config'), 'ripgrep_ignore') 5 | ) 6 | 7 | local function pick(which, opts) 8 | return function() 9 | return require('snacks.picker')[which](opts) 10 | end 11 | end 12 | 13 | return { 14 | 'folke/snacks.nvim', 15 | keys = { 16 | { 'ff', pick('files', { hidden = true }), desc = 'Find files' }, 17 | { 'fh', pick('recent'), desc = 'Find oldfiles' }, 18 | { 'fb', pick('buffers'), desc = 'Find buffers' }, 19 | { 'ft', pick('grep', { hidden = true }), desc = 'Find text' }, 20 | { 'fs', pick('lsp_workspace_symbols'), desc = 'Find LSP symbols in the workspace' }, 21 | { 'fr', pick('resume'), desc = 'Resume last finder' }, 22 | }, 23 | opts = { 24 | styles = { 25 | input = { 26 | relative = 'cursor', 27 | }, 28 | }, 29 | input = { enabled = true }, 30 | image = { enabled = true }, 31 | picker = { 32 | layout = { 33 | preset = 'telescope', 34 | }, 35 | sources = { 36 | files = { args = { '--ignore-file', ripgrep_ignore_file_path } }, 37 | grep = { args = { '--ignore-file', ripgrep_ignore_file_path } }, 38 | recent = { 39 | filter = { 40 | paths = { 41 | [vim.uv.cwd()] = true, 42 | [string.format('%s/.git/COMMIT_EDITMSG', vim.uv.cwd())] = false, 43 | }, 44 | }, 45 | }, 46 | }, 47 | actions = { 48 | trouble_open = { 49 | action = function(picker) 50 | require('trouble.sources.snacks').open(picker, {}) 51 | end, 52 | description = 'smart-open-with-trouble', 53 | }, 54 | }, 55 | win = { 56 | input = { 57 | keys = { 58 | -- fully close picker on `ESC`, I use `jk` to go to normal mode 59 | [''] = { 'close', mode = { 'n', 'i' } }, 60 | -- keep `` to clear prompt 61 | [''] = false, 62 | [''] = { 63 | 'trouble_open', 64 | mode = { 'n', 'i' }, 65 | }, 66 | }, 67 | }, 68 | }, 69 | formatters = { 70 | file = { 71 | filename_first = true, 72 | truncate = 85, 73 | }, 74 | }, 75 | }, 76 | }, 77 | dependencies = { 78 | { 79 | 'folke/trouble.nvim', 80 | cmd = 'Trouble', 81 | keys = { 82 | { 'd', 'Trouble diagnostics toggle filter.buf=0', desc = 'Toggle diagnostics list' }, 83 | { 'q', 'Trouble qflist toggle', desc = 'Toggle quickfix list' }, 84 | { 'l', 'Trouble symbols toggle win.position=right', desc = 'Show LSP symbol tree' }, 85 | }, 86 | opts = { action_keys = { hover = {} } }, 87 | }, 88 | { 89 | 'kevinhwang91/nvim-bqf', 90 | ft = 'qf', -- load on quickfix list opened, 91 | opts = { 92 | func_map = { 93 | pscrolldown = '', 94 | pscrollup = '', 95 | }, 96 | }, 97 | }, 98 | }, 99 | } 100 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/sql.lua: -------------------------------------------------------------------------------- 1 | return { 2 | 'kristijanhusak/vim-dadbod-ui', 3 | dependencies = { 4 | { 'tpope/vim-dadbod', lazy = true }, 5 | { 'kristijanhusak/vim-dadbod-completion', ft = { 'sql', 'mysql', 'plsql' }, lazy = true }, 6 | }, 7 | cmd = { 8 | 'DBUI', 9 | 'DBUIToggle', 10 | 'DBUIAddConnection', 11 | 'DBUIFindBuffer', 12 | }, 13 | init = function() 14 | vim.g.db_ui_use_nerd_fonts = 1 15 | end, 16 | } 17 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/treesitter.lua: -------------------------------------------------------------------------------- 1 | return { 2 | 'nvim-treesitter/nvim-treesitter', 3 | dependencies = { 4 | { 5 | 'calops/hmts.nvim', 6 | version = '*', 7 | ft = 'nix', 8 | }, 9 | { 10 | 'hiphish/rainbow-delimiters.nvim', 11 | config = function() 12 | local rainbow = require('rainbow-delimiters') 13 | vim.g.rainbow_delimiters = { 14 | strategy = { 15 | [''] = rainbow.strategy['global'], 16 | vim = rainbow.strategy['local'], 17 | }, 18 | query = { 19 | [''] = 'rainbow-delimiters', 20 | lua = 'rainbow-blocks', 21 | html = 'rainbow-tags', 22 | }, 23 | highlight = { 24 | 'RainbowDelimiterRed', 25 | 'RainbowDelimiterYellow', 26 | 'RainbowDelimiterBlue', 27 | 'RainbowDelimiterOrange', 28 | 'RainbowDelimiterGreen', 29 | 'RainbowDelimiterViolet', 30 | 'RainbowDelimiterCyan', 31 | }, 32 | } 33 | end, 34 | }, 35 | { 36 | 'JoosepAlviste/nvim-ts-context-commentstring', 37 | init = function() 38 | vim.g.skip_ts_context_commentstring_module = true 39 | end, 40 | opts = {}, 41 | }, 42 | 'andymass/vim-matchup', 43 | { 44 | 'ziontee113/query-secretary', 45 | keys = { 46 | { 47 | 'qs', 48 | function() 49 | require('query-secretary').query_window_initiate() 50 | end, 51 | desc = 'Open Query Secretary', 52 | }, 53 | }, 54 | opts = {}, 55 | }, 56 | }, 57 | event = 'BufRead', 58 | cmd = { 'TSInstall', 'TSUpdate', 'TSUpdateSync' }, 59 | build = function() 60 | if #vim.api.nvim_list_uis() == 0 then 61 | -- update sync if running headless 62 | vim.cmd.TSUpdateSync() 63 | else 64 | -- otherwise update async 65 | vim.cmd.TSUpdate() 66 | end 67 | end, 68 | init = function() 69 | vim.g.matchup_matchparen_offscreen = { 70 | method = 'popup', 71 | } 72 | end, 73 | config = function() 74 | vim.filetype.add({ 75 | extension = { 76 | mdx = 'mdx', 77 | }, 78 | }) 79 | -- highlight mdx with markdown -- it's close enough. We also do JSX injection via ./after/queries/markdown/injections.scm 80 | vim.treesitter.language.register('markdown', 'mdx') 81 | require('nvim-treesitter.configs').setup({ ---@diagnostic disable-line:missing-fields 82 | ensure_installed = require('my.lsp.filetypes').treesitter_parsers, 83 | highlight = { 84 | enable = true, 85 | }, 86 | indent = { 87 | enable = true, 88 | }, 89 | incremental_selection = { 90 | enable = true, 91 | -- not sure why but these don't work correctly 92 | -- if they're defined in keymaps.lua instead of here 93 | keymaps = { 94 | init_selection = '', 95 | node_incremental = '', 96 | scope_incremental = '', 97 | node_decremental = '', 98 | }, 99 | }, 100 | -- treesitter parser for Swift requires treesitter-cli and only really works on mac 101 | additional_vim_regex_highlighting = { 'swift' }, 102 | matchup = { 103 | enable = true, 104 | include_match_words = true, 105 | }, 106 | }) 107 | end, 108 | } 109 | -------------------------------------------------------------------------------- /nvim/lua/my/configure/which_key.lua: -------------------------------------------------------------------------------- 1 | local function toggle_char_at_end_of_line(char) 2 | return function() 3 | local api = vim.api 4 | local line = api.nvim_get_current_line() 5 | local commentstring = vim.api.nvim_get_option_value('commentstring', { buf = 0 }):gsub('%%s', '') 6 | local escaped_commentstring = commentstring:gsub('([%(%)%.%%%+%-%*%?%[%^%$])', '%%%1') 7 | local code, comment = line:match('^(.*)' .. escaped_commentstring .. '(.*)$') 8 | 9 | if code then 10 | code = code:gsub('%s*$', '') -- remove trailing spaces 11 | local last_char = code:sub(-1) 12 | 13 | if last_char == char then 14 | code = code:sub(1, #code - 1) 15 | else 16 | code = code .. char 17 | end 18 | 19 | line = code .. ' ' .. commentstring .. (comment or '') 20 | else 21 | line = line:gsub('%s*$', '') -- remove trailing spaces 22 | local last_char = line:sub(-1) 23 | 24 | if last_char == char then 25 | line = line:sub(1, #line - 1) 26 | else 27 | line = line .. char 28 | end 29 | end 30 | 31 | return api.nvim_set_current_line(line) 32 | end 33 | end 34 | 35 | return { 36 | 'folke/which-key.nvim', 37 | event = 'VeryLazy', 38 | dependencies = { 39 | { 40 | 'folke/snacks.nvim', 41 | lazy = false, 42 | opts = { bufdelete = { enabled = true } }, 43 | keys = { 44 | { 45 | 'W', 46 | function() 47 | require('snacks.bufdelete').delete(0) 48 | end, 49 | desc = 'Close current buffer', 50 | }, 51 | }, 52 | }, 53 | }, 54 | opts = { 55 | preset = 'helix', 56 | win = { col = 0 }, 57 | expand = 2, 58 | triggers = { 59 | { '', mode = 'nixsotc' }, 60 | { 'f', mode = 'n' }, 61 | }, 62 | spec = { 63 | { 64 | '', 65 | function() 66 | local buf = vim.api.nvim_get_current_buf() 67 | local win = vim.api.nvim_get_current_win() 68 | vim.ui.select(require('my.cmd-palette'), { 69 | prompt = 'Command Palette', 70 | format_item = function( 71 | item --[[@as PaletteEntry]] 72 | ) 73 | if type(item.text) == 'function' then 74 | return item.text(buf, win) 75 | end 76 | return item.text 77 | end, 78 | }, function( 79 | choice --[[@as PaletteEntry]] 80 | ) 81 | local callback = vim.tbl_get(choice or {}, 'on_selected') 82 | if type(callback) == 'function' then 83 | callback(buf, win) 84 | end 85 | end) 86 | end, 87 | }, 88 | { '', ':noh', desc = 'Clear highlighting on in normal mode', hidden = true }, 89 | { 90 | 'jk', 91 | function() 92 | require('notify').dismiss({ silent = true, pending = true }) 93 | end, 94 | desc = 'Dismiss notifications', 95 | }, 96 | -- allow moving the cursor through wrapped lines using j and k, 97 | -- note that I have line wrapping turned off but turned on only for Markdown 98 | { 'j', 'v:count || mode(1)[0:1] == "no" ? "j" : "gj"', expr = true, mode = { 'n', 'v' }, hidden = true }, 99 | { 'k', 'v:count || mode(1)[0:1] == "no" ? "k" : "gk"', expr = true, mode = { 'n', 'v' }, hidden = true }, 100 | { '', ':bn', desc = 'Move to next buffer' }, 101 | { '', ':bp', desc = 'Move to previous buffer' }, 102 | { 103 | 's', 104 | require('my.utils.editor').toggle_spellcheck, 105 | desc = 'Toggle spellcheck', 106 | }, 107 | { 108 | '', 109 | toggle_char_at_end_of_line(';'), 110 | mode = { 'n', 'i' }, 111 | desc = 'Toggle semicolon', 112 | }, 113 | { 114 | '', 115 | toggle_char_at_end_of_line(','), 116 | mode = { 'n', 'i' }, 117 | desc = 'Toggle trailing comma', 118 | }, 119 | }, 120 | }, 121 | keys = { 122 | { 123 | '?', 124 | function() 125 | require('which-key').show({ global = false }) 126 | end, 127 | desc = 'Buffer Local Keymaps (which-key)', 128 | }, 129 | }, 130 | } 131 | -------------------------------------------------------------------------------- /nvim/lua/my/lsp/filetypes.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | M.config = { 4 | ['css'] = { 5 | formatter = 'prettierd', 6 | lspconfig = { 'cssls' }, 7 | }, 8 | ['html'] = { 9 | lspconfig = { 'html' }, 10 | }, 11 | ['json'] = { 12 | lspconfig = 'jsonls', 13 | treesitter = { 'json', 'jsonc' }, 14 | }, 15 | ['typescript'] = { 16 | lspconfig = { 'ts_ls', 'eslint' }, 17 | formatter = 'prettierd', 18 | treesitter = { 'typescript', 'javascript', 'tsx' }, 19 | }, 20 | ['lua'] = { 21 | lspconfig = 'lua_ls', 22 | formatter = 'stylua', 23 | linter = 'luacheck', 24 | treesitter = { 'lua', 'luadoc' }, 25 | }, 26 | ['rust'] = { 27 | -- let rustaceanvim set this up for us 28 | lspconfig = false, 29 | -- we just want auto formatting 30 | formatter = 'rustfmt', 31 | }, 32 | ['go'] = { 33 | lspconfig = 'gopls', 34 | formatter = 'gofmt', 35 | }, 36 | ['markdown'] = { 37 | lspconfig = 'marksman', 38 | formatter = { 39 | 'prettierd', 40 | 'injected', -- see :h conform-formatters, formats injected code blocks 41 | }, 42 | treesitter = { 'markdown', 'markdown_inline' }, 43 | }, 44 | ['sh'] = { 45 | treesitter = { 'bash' }, 46 | -- shellcheck and shellfmt are run through the LSP 47 | lspconfig = 'bashls', 48 | }, 49 | ['swift'] = { 50 | lspconfig = 'sourcekit', 51 | formatter = 'swiftfmt', 52 | treesitter = false, -- requires treesitter-cli and only really works on mac 53 | }, 54 | ['nix'] = { 55 | lspconfig = { 'nixd', 'nil_ls' }, 56 | linter = 'statix', 57 | formatter = { 'nixfmt', 'injected' }, 58 | }, 59 | ['toml'] = { 60 | lspconfig = 'taplo', 61 | }, 62 | ['fish'] = { 63 | formatter = 'fish_indent', 64 | linter = 'fish', 65 | }, 66 | ['yaml'] = { 67 | lspconfig = 'yamlls', 68 | treesitter = { 'yaml' }, 69 | }, 70 | } 71 | 72 | M.filetypes = vim.tbl_keys(M.config) 73 | 74 | if vim.fn.filereadable('./tailwind.config.js') ~= 0 then 75 | table.insert(M.config['css'].lspconfig, 'tailwindcss') 76 | table.insert(M.config['typescript'].lspconfig, 'tailwindcss') 77 | table.insert(M.config['html'].lspconfig, 'tailwindcss') 78 | end 79 | 80 | -- these all use the same config 81 | M.config['javascript'] = M.config['typescript'] 82 | M.config['typescriptreact'] = M.config['typescript'] 83 | M.config['javascriptreact'] = M.config['typescript'] 84 | M.config['scss'] = M.config['css'] 85 | 86 | local function cfg_by_ft(cfg) 87 | local result = {} 88 | for ft, ft_cfg in pairs(M.config) do 89 | if ft_cfg[cfg] then 90 | result[ft] = type(ft_cfg[cfg]) == 'table' and ft_cfg[cfg] or { ft_cfg[cfg] } 91 | end 92 | end 93 | return result 94 | end 95 | 96 | M.formatters_by_ft = cfg_by_ft('formatter') 97 | M.linters_by_ft = cfg_by_ft('linter') 98 | 99 | M.treesitter_parsers = (function() 100 | local result = {} 101 | for ft, config in pairs(M.config) do 102 | if config.treesitter ~= false then 103 | local treesitter = config.treesitter or ft 104 | treesitter = type(treesitter) == 'table' and treesitter or { treesitter } 105 | result = vim.list_extend(result, treesitter) 106 | end 107 | end 108 | 109 | -- insert extra parsers here 110 | table.insert(result, 'regex') 111 | table.insert(result, 'just') 112 | table.insert(result, 'kdl') 113 | 114 | return result 115 | end)() 116 | 117 | return M 118 | -------------------------------------------------------------------------------- /nvim/lua/my/lsp/go.lua: -------------------------------------------------------------------------------- 1 | return { 2 | settings = { 3 | gopls = { 4 | hints = { 5 | compositeLiteralFields = true, 6 | compositeLiteralTypes = true, 7 | constantValues = true, 8 | functionTypeParameters = true, 9 | parameterNames = true, 10 | rangeVariableTypes = true, 11 | }, 12 | }, 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /nvim/lua/my/lsp/lua.lua: -------------------------------------------------------------------------------- 1 | local runtime_path = vim.split(package.path, ';', {}) 2 | table.insert(runtime_path, 'lua/?.lua') 3 | table.insert(runtime_path, 'lua/?/init.lua') 4 | 5 | local globals = { 'vim' } 6 | if string.find(assert(vim.loop.cwd()), 'hammerspoon') then 7 | globals = { 'hs' } 8 | end 9 | 10 | return { 11 | root_dir = require('lspconfig.util').root_pattern('.luacheckrc', 'stylua.toml'), 12 | settings = { 13 | Lua = { 14 | completion = { 15 | autoRequire = false, 16 | }, 17 | runtime = { 18 | -- Tell the language server which version of Lua you're using (most likely LuaJIT in the case of Neovim) 19 | version = 'LuaJIT', 20 | -- Setup your lua path 21 | path = runtime_path, 22 | }, 23 | diagnostics = { 24 | globals = globals, 25 | }, 26 | workspace = { 27 | -- Make the server aware of Neovim runtime files 28 | library = vim.api.nvim_get_runtime_file('', true), 29 | -- disable annoying "do you need to configure your workspace as luassert" prompts 30 | checkThirdParty = false, 31 | }, 32 | -- Do not send telemetry data containing a randomized but unique identifier 33 | telemetry = { 34 | enable = false, 35 | }, 36 | }, 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /nvim/lua/my/lsp/nix.lua: -------------------------------------------------------------------------------- 1 | return { 2 | settings = { 3 | nixd = { 4 | options = { 5 | nixos = { 6 | expr = string.format( 7 | '(builtins.getFlake ("git+file://" + toString %s/git/dotfiles/.)).nixosConfigurations.pc.options', 8 | vim.env.HOME 9 | ), 10 | }, 11 | home_manager = { 12 | expr = string.format( 13 | -- line is too long 14 | -- luacheck: ignore 15 | '(builtins.getFlake ("git+file://" + toString %s/git/dotfiles/.)).nixosConfigurations."%s".modules.home-manager', 16 | vim.env.HOME, 17 | vim.uv.os_uname().sysname == 'Darwin' and 'Mats-MacBook-Pro' or 'pc' 18 | ), 19 | }, 20 | nix_darwin = { 21 | expr = string.format( 22 | -- line is too long 23 | -- luacheck: ignore 24 | '(builtins.getFlake ("git+file://" + toString %s/git/dotfiles/.)).nixosConfigurations."Mats-MacBook-Pro".options', 25 | vim.env.HOME 26 | ), 27 | }, 28 | }, 29 | }, 30 | }, 31 | } 32 | -------------------------------------------------------------------------------- /nvim/lua/my/lsp/snippets.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local LUA_MODULE_RETURN_TS_QUERY = [[ 4 | (return_statement 5 | (expression_list 6 | (identifier) @return-value-name)) 7 | ]] 8 | 9 | function M.lua() 10 | local ls = require('luasnip') 11 | local s = ls.s 12 | local i = ls.insert_node 13 | local f = ls.function_node 14 | local fmt = require('luasnip.extras.fmt').fmt 15 | local p = ls.parser.parse_snippet 16 | local c = ls.choice_node 17 | 18 | local function get_returned_mod_name() 19 | local query = vim.treesitter.query.parse('lua', LUA_MODULE_RETURN_TS_QUERY) 20 | local parser = vim.treesitter.get_parser(0, 'lua') 21 | if parser == nil then 22 | error('Install Lua treesitter parser') 23 | end 24 | local tree = parser:parse()[1] 25 | local num_lines = #vim.api.nvim_buf_get_lines(0, 0, -1, false) 26 | for _, node, _ in query:iter_captures(tree:root(), 0, num_lines - 3, num_lines) do 27 | return vim.treesitter.get_node_text(node, 0, {}) 28 | end 29 | end 30 | 31 | local function get_req_module(req_path) 32 | local parts = vim.split(req_path[1][1], '%.', { trimempty = true }) 33 | return parts[#parts] or '' 34 | end 35 | 36 | local function get_req_module_upper(req_path) 37 | local path = get_req_module(req_path) 38 | return path:sub(1, 1):upper() .. path:sub(2) 39 | end 40 | 41 | ls.add_snippets('lua', { 42 | -- type req, type the module path, and the `local {}` will be automatically 43 | -- filled in as the last section of the module path, e.g. `require('my.custom.mod)` => `local mod` 44 | -- the `local` part is also a choice node allowing you to toggle between `local mod` and `local Mod` 45 | s( 46 | 'req', 47 | fmt("local {} = require('{}')", { 48 | c(2, { 49 | f(get_req_module, { 1 }), 50 | f(get_req_module_upper, { 1 }), 51 | }), 52 | i(1), 53 | }) 54 | ), 55 | -- create a new function on the module, dynamically using the `local` that is 56 | -- returned from the overall module, e.g. 57 | -- local MyMod = {} 58 | -- ... 59 | -- return MyMod 60 | -- in this case it will use `function MyMod.fn_name()` 61 | s( 62 | 'mfn', 63 | c(1, { 64 | fmt('function {}.{}({})\n {}\nend', { 65 | f(get_returned_mod_name, {}), 66 | i(1), 67 | i(2), 68 | i(3), 69 | }), 70 | fmt('function {}:{}({})\n {}\nend', { 71 | f(get_returned_mod_name, {}), 72 | i(1), 73 | i(2), 74 | i(3), 75 | }), 76 | }) 77 | ), 78 | -- create a module structure, allowing you to customize the module name 79 | p('mod', 'local $1 = {}\n\n$0\n\nreturn $1'), 80 | -- require without assigning to a local 81 | p('rq', "require('$0')"), 82 | -- inline global function 83 | p('fn', 'function($1)\n $0\nend'), 84 | -- inline local function 85 | p('lfn', 'local function $1($2)\n $0\nend'), 86 | }) 87 | end 88 | 89 | -- since these snippets are shared between 4 filetypes, 90 | -- ensure we're only registering them once 91 | local ts_snippets_added = false 92 | function M.typescript() 93 | if ts_snippets_added then 94 | return 95 | end 96 | 97 | local ls = require('luasnip') 98 | local p = ls.parser.parse_snippet 99 | 100 | local snippets = { 101 | p('fn', 'function $1($2)$3 {\n $0\n}'), 102 | p('afn', 'const $1 = ($2)$3 => {\n $0\n}'), 103 | p('ifn', '($1)$2 => {\n $0\n}'), 104 | } 105 | 106 | ls.add_snippets('typescript', snippets) 107 | ls.add_snippets('typescriptreact', snippets) 108 | ls.add_snippets('javascript', snippets) 109 | ls.add_snippets('javascriptreact', snippets) 110 | 111 | ts_snippets_added = true 112 | end 113 | M.typescriptreact = M.typescript 114 | M.javascript = M.typescript 115 | M.javascriptreact = M.typescript 116 | 117 | function M.rust() 118 | local ls = require('luasnip') 119 | local p = ls.parser.parse_snippet 120 | ls.add_snippets('rust', { 121 | p('deny', '#![deny(clippy::all, clippy::pedantic, rust_2018_idioms, clippy::unwrap_used)]\n\n', {}), 122 | p('fn', 'fn $1($2)$3 {\n $0\n}'), 123 | p('res', 'Result<$1, $2>$0'), 124 | p('opt', 'Option<$1>$0'), 125 | p('const', 'const $1: $2 = $0;'), 126 | p('enum', '#[derive(Debug)]\nenum $1 {\n $0\n}'), 127 | p('derive', '#[derive($0)]'), 128 | p('tst', '#[test]'), 129 | p('impl', 'impl $1 {\n $0\n}'), 130 | p('for', 'for $1 in $2 {\n $0\n}'), 131 | p('ifl', 'if let Some($1) = $1 {\n $0\n}'), 132 | p('lets', 'let Some($1) = $1 else {\n $0\n};'), 133 | p('leto', 'let Ok($1) = $1 else {\n $0\n};'), 134 | }) 135 | end 136 | 137 | function M.nix() 138 | local ls = require('luasnip') 139 | local p = ls.parser.parse_snippet 140 | ls.add_snippets('nix', { 141 | p( 142 | 'flake', 143 | [[{ 144 | description = "$1"; 145 | inputs = { 146 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";$2 147 | }; 148 | outputs = inputs@{ nixpkgs, ... }: { 149 | $0 150 | }; 151 | }]] 152 | ), 153 | }) 154 | end 155 | 156 | return M 157 | -------------------------------------------------------------------------------- /nvim/lua/my/lsp/typescript.lua: -------------------------------------------------------------------------------- 1 | local root_dir = require('lspconfig.util').root_pattern('pnpm-workspace.yaml', 'package.json') 2 | -- prioritize workspace roots 3 | if 4 | vim.fn.filereadable(vim.loop.cwd() .. '/pnpm-workspace.yaml') > 0 5 | or vim.fn.filereadable(vim.loop.cwd() .. '/pnpm-workspace.yml') > 0 6 | then 7 | root_dir = function() 8 | return vim.loop.cwd() 9 | end 10 | end 11 | 12 | return { 13 | root_dir = root_dir, 14 | settings = { 15 | typescript = { 16 | inlayHints = { 17 | includeInlayParameterNameHints = 'all', 18 | includeInlayParameterNameHintsWhenArgumentMatchesName = true, 19 | includeInlayFunctionParameterTypeHints = true, 20 | includeInlayVariableTypeHints = false, 21 | includeInlayPropertyDeclarationTypeHints = true, 22 | includeInlayFunctionLikeReturnTypeHints = true, 23 | includeInlayEnumMemberValueHints = true, 24 | }, 25 | }, 26 | javascript = { 27 | inlayHints = { 28 | includeInlayParameterNameHints = 'all', 29 | includeInlayParameterNameHintsWhenArgumentMatchesName = true, 30 | includeInlayFunctionParameterTypeHints = true, 31 | includeInlayVariableTypeHints = false, 32 | includeInlayPropertyDeclarationTypeHints = true, 33 | includeInlayFunctionLikeReturnTypeHints = true, 34 | includeInlayEnumMemberValueHints = true, 35 | }, 36 | }, 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /nvim/lua/my/plugins.lua: -------------------------------------------------------------------------------- 1 | local lazypath = vim.fn.stdpath('data') .. '/lazy/lazy.nvim' 2 | if not vim.loop.fs_stat(lazypath) then 3 | vim.fn.system({ 4 | 'git', 5 | 'clone', 6 | '--filter=blob:none', 7 | '--single-branch', 8 | 'https://github.com/folke/lazy.nvim.git', 9 | lazypath, 10 | }) 11 | end 12 | vim.opt.runtimepath:prepend(lazypath) 13 | require('lazy').setup('my.configure', { 14 | defaults = { 15 | lazy = true, 16 | }, 17 | dev = { path = '~/git', fallback = true }, 18 | install = { 19 | colorscheme = { vim.env.COLORSCHEME or 'tokyonight', 'habamax' }, 20 | }, 21 | performance = { 22 | rtp = { 23 | disabled_plugins = { 24 | 'netrw', 25 | 'netrwPlugin', 26 | 'netrwSettings', 27 | 'netrwFileHandlers', 28 | 'gzip', 29 | 'zip', 30 | 'zipPlugin', 31 | 'tar', 32 | 'tarPlugin', 33 | 'getscript', 34 | 'getscriptPlugin', 35 | 'vimball', 36 | 'vimballPlugin', 37 | '2html_plugin', 38 | 'logipat', 39 | 'rrhelper', 40 | 'spellfile_plugin', 41 | 'matchit', 42 | }, 43 | }, 44 | }, 45 | }) 46 | -------------------------------------------------------------------------------- /nvim/lua/my/settings.lua: -------------------------------------------------------------------------------- 1 | vim.g.mapleader = ' ' 2 | vim.g.maplocalleader = ';' 3 | vim.opt.compatible = false 4 | vim.opt.cursorline = false 5 | vim.opt.autoread = true 6 | vim.opt.mouse = 'a' 7 | vim.opt.autoindent = true 8 | vim.opt.backspace = 'indent,eol,start' 9 | vim.opt.scrolloff = 4 10 | vim.opt.signcolumn = 'yes:3' 11 | vim.opt.hidden = true 12 | vim.opt.number = true 13 | vim.opt.undodir = string.format('%s/undo', vim.fn.stdpath('data')) 14 | vim.opt.undofile = true 15 | vim.opt.wildmenu = true 16 | vim.opt.wrap = false 17 | vim.opt.autochdir = false 18 | vim.opt.diffopt = 'vertical' 19 | vim.opt.smartcase = true 20 | vim.opt.ignorecase = true 21 | vim.opt.termguicolors = true 22 | vim.opt.syntax = 'on' 23 | vim.opt.modeline = true 24 | vim.opt.updatetime = 100 25 | vim.opt.confirm = true 26 | vim.opt.showtabline = 0 27 | vim.opt.cmdheight = 0 28 | vim.opt.sessionoptions = 'buffers,curdir,folds,winpos,winsize,localoptions' 29 | vim.opt.laststatus = 3 30 | vim.opt.virtualedit = 'onemore' 31 | vim.opt.splitkeep = 'screen' 32 | vim.opt.clipboard = 'unnamedplus' 33 | 34 | -- setting to 0 makes it default to value of tabstop 35 | vim.opt.shiftwidth = 0 36 | vim.opt.tabstop = 2 37 | vim.opt.expandtab = true 38 | vim.opt.splitbelow = true 39 | vim.opt.splitright = true 40 | 41 | vim.opt.shortmess:append('I') 42 | vim.opt.wildignore:append({ '*.DS_Store' }) 43 | -- enable line-wrapping with left and right cursor movement 44 | vim.opt.whichwrap:append({ ['<'] = true, ['>'] = true, ['h'] = true, ['l'] = true, ['['] = true, [']'] = true }) 45 | -- add @, -, and $ as keywords for full SCSS support 46 | vim.opt.iskeyword:append({ '@-@', '-', '$' }) 47 | -- slightly thicker window separators 48 | vim.opt.fillchars = { 49 | horiz = '━', 50 | horizup = '┻', 51 | horizdown = '┳', 52 | vert = '┃', 53 | vertleft = '┫', 54 | vertright = '┣', 55 | verthoriz = '╋', 56 | eob = ' ', 57 | } 58 | 59 | vim.filetype.add({ 60 | pattern = { 61 | ['*.jsonc'] = 'jsonc', 62 | ['tsconfig.json'] = 'jsonc', 63 | ['tsconfig*.json'] = 'jsonc', 64 | }, 65 | }) 66 | 67 | vim.opt.exrc = true 68 | -------------------------------------------------------------------------------- /nvim/lua/my/utils/clipboard.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | -- use OSC52 yank so it works over SSH 4 | local copy_fn = require('vim.ui.clipboard.osc52').copy('+') 5 | 6 | ---Copy string to system clipboard 7 | ---@param str string|string[] 8 | function M.copy(str) 9 | if type(str) == 'string' then 10 | -- the built-in clipboard function 11 | -- takes a table of strings 12 | str = { str } 13 | end 14 | copy_fn(str) 15 | end 16 | 17 | return M 18 | -------------------------------------------------------------------------------- /nvim/lua/my/utils/editor.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | function M.toggle_spellcheck() 4 | if vim.o.spell then 5 | vim.opt.spell = false 6 | else 7 | vim.opt.spelloptions = { 'camel', 'noplainbuffer' } 8 | vim.opt.spell = true 9 | end 10 | end 11 | 12 | function M.spellcheck_enabled() 13 | return vim.o.spell 14 | end 15 | 16 | return M 17 | -------------------------------------------------------------------------------- /nvim/lua/my/utils/lsp.lua: -------------------------------------------------------------------------------- 1 | local Methods = vim.lsp.protocol.Methods 2 | 3 | local M = {} 4 | 5 | local formatting_enabled = true 6 | 7 | ---Set up a callback to run on LSP attach 8 | ---@param callback fun(client:table,bufnr:number) 9 | function M.on_attach(callback) 10 | vim.api.nvim_create_autocmd('LspAttach', { 11 | callback = function(args) 12 | local bufnr = args.buf 13 | local client = vim.lsp.get_client_by_id(args.data.client_id) 14 | if client then 15 | callback(client, bufnr) 16 | end 17 | end, 18 | }) 19 | end 20 | 21 | function M.on_attach_default(client, bufnr) 22 | -- if current nvim version supports inlay hints, enable them 23 | if vim.lsp['inlay_hint'] ~= nil and client.supports_method(Methods.textDocument_inlayHint) then 24 | vim.lsp.inlay_hint.enable(true) 25 | end 26 | 27 | -- Run eslint fixes before writing 28 | if client.name == 'eslint' then 29 | vim.api.nvim_create_autocmd('BufWritePre', { 30 | buffer = bufnr, 31 | command = 'EslintFixAll', 32 | }) 33 | end 34 | 35 | vim.api.nvim_create_autocmd('CursorHold', { 36 | callback = function() 37 | vim.diagnostic.open_float(nil, { focus = false, scope = 'cursor', border = 'none' }) 38 | end, 39 | buffer = bufnr, 40 | }) 41 | end 42 | 43 | function M.apply_ui_tweaks() 44 | -- customize LSP icons 45 | local icons = { 46 | Error = ' ', 47 | Warn = ' ', 48 | Hint = ' ', 49 | Info = ' ', 50 | } 51 | 52 | local icon_map = { 53 | [vim.diagnostic.severity.ERROR] = icons.Error, 54 | [vim.diagnostic.severity.WARN] = icons.Warn, 55 | [vim.diagnostic.severity.INFO] = icons.Info, 56 | [vim.diagnostic.severity.HINT] = icons.Hint, 57 | } 58 | 59 | local function diagnostic_format(diagnostic) 60 | return string.format('%s %s (%s)', icon_map[diagnostic.severity], diagnostic.message, diagnostic.code) 61 | end 62 | 63 | vim.diagnostic.config({ 64 | virtual_text = { 65 | format = diagnostic_format, 66 | prefix = '', 67 | }, 68 | float = { 69 | format = diagnostic_format, 70 | }, 71 | signs = { priority = 100, text = icon_map }, 72 | underline = true, 73 | update_in_insert = false, 74 | severity_sort = true, 75 | }) 76 | 77 | -- enable virtual text diagnostics for Neotest only 78 | vim.diagnostic.config({ virtual_text = true }, vim.api.nvim_create_namespace('neotest')) 79 | end 80 | 81 | function M.toggle_formatting_enabled(enable) 82 | if enable == nil then 83 | enable = not formatting_enabled 84 | end 85 | if enable then 86 | formatting_enabled = true 87 | vim.notify('Enabling LSP formatting...', vim.log.levels.INFO) 88 | else 89 | formatting_enabled = false 90 | vim.notify('Disabling LSP formatting...', vim.log.levels.INFO) 91 | end 92 | end 93 | 94 | function M.is_formatting_enabled() 95 | return formatting_enabled 96 | end 97 | 98 | ---@param buf number|nil defaults to 0 (current buffer) 99 | ---@return string|nil 100 | function M.get_formatter_name(buf) 101 | buf = buf or tonumber(vim.g.actual_curbuf or vim.api.nvim_get_current_buf()) 102 | 103 | -- if it uses conform.nvim, grab the formatter name 104 | local formatter = require('my.lsp.filetypes').formatters_by_ft[vim.bo[buf].ft] 105 | if formatter then 106 | return type(formatter) == 'table' and table.concat(formatter, ', ') or formatter 107 | end 108 | 109 | -- otherwise just return the LSP server name 110 | local clients = vim.lsp.get_clients({ bufnr = buf, method = Methods.textDocument_formatting }) 111 | if #clients > 0 then 112 | return clients[1].name 113 | end 114 | 115 | return nil 116 | end 117 | 118 | return M 119 | -------------------------------------------------------------------------------- /nvim/lua/my/utils/path.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | ---Join two or more paths together 4 | ---@param ... string 5 | ---@return string 6 | function M.join(...) 7 | return vim.fn.simplify(table.concat({ ... }, '/')) 8 | end 9 | 10 | ---Get path relative to current working directory, 11 | ---replacing `$HOME` with `~` if applicable. 12 | ---@param path string 13 | ---@return string 14 | function M.relative(path) 15 | return vim.fn.fnamemodify(path, ':~:.') or path 16 | end 17 | 18 | return M 19 | -------------------------------------------------------------------------------- /nvim/stylua.toml: -------------------------------------------------------------------------------- 1 | indent_type = "Spaces" 2 | indent_width = 2 3 | quote_style = "AutoPreferSingle" 4 | -------------------------------------------------------------------------------- /packages/default.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs, 3 | pkgs, 4 | system, 5 | ... 6 | }: 7 | { 8 | vim-zellij-navigator = pkgs.callPackage ./vim-zellij-navigator.nix { }; 9 | zjstatus = inputs.zjstatus.packages.${system}.default; 10 | darwin-rebuild = inputs.nix-darwin.packages."aarch64-darwin".default; 11 | } 12 | -------------------------------------------------------------------------------- /packages/vim-zellij-navigator.nix: -------------------------------------------------------------------------------- 1 | { 2 | lib, 3 | stdenv, 4 | fetchurl, 5 | }: 6 | 7 | stdenv.mkDerivation rec { 8 | pname = "vim-zellij-navigator"; 9 | version = "0.2.1"; 10 | 11 | src = fetchurl { 12 | url = "https://github.com/hiasr/vim-zellij-navigator/releases/download/${version}/vim-zellij-navigator.wasm"; 13 | sha256 = "sha256-wpIxPkmVpoAgOsdQKYuipSlDAbsD3/n6tTuOEriJHn0="; 14 | }; 15 | 16 | dontUnpack = true; 17 | 18 | installPhase = '' 19 | mkdir -p $out/bin 20 | cp $src $out/bin/vim-zellij-navigator.wasm 21 | chmod +x $out/bin/vim-zellij-navigator.wasm 22 | ''; 23 | 24 | meta = with lib; { 25 | description = "Vim Zellij Navigator WASM plugin"; 26 | homepage = "https://github.com/hiasr/vim-zellij-navigator"; 27 | license = licenses.mit; 28 | platforms = platforms.all; 29 | maintainers = [ ]; 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /secrets.nix: -------------------------------------------------------------------------------- 1 | # This module is NOT imported into the NixOS config, 2 | # it is only used by the agenix CLI to determine which 3 | # keys to use to encrypt secrets. 4 | let 5 | # my public key 6 | users = [ 7 | "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDhIkQh2Viqa519kFJjIPUrz3jrwkSljezVlLU5GP0uh mat@nixos" 8 | ]; 9 | # server host key 10 | systems = [ 11 | "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDXHRx83f5MWdhcEHXduTINyUu6yqd2eOgZHE0XNYFlO root@homelab" 12 | ]; 13 | secrets = [ 14 | "secrets/mullvad_wireguard.age" 15 | "secrets/wireguard_server.age" 16 | "secrets/cleanuperr_env.age" 17 | "secrets/homarr_env.age" 18 | "secrets/cloudflare_certbot_token.age" 19 | "secrets/duckdns_token.age" 20 | "secrets/gatus_discord_webhook_env.age" 21 | "secrets/paperless_admin_pw.age" 22 | "secrets/docmost_env.age" 23 | ]; 24 | in 25 | builtins.listToAttrs ( 26 | map (secret: { 27 | name = secret; 28 | value = { 29 | publicKeys = users ++ systems; 30 | }; 31 | }) secrets 32 | ) 33 | -------------------------------------------------------------------------------- /secrets/cleanuperr_env.age: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjones2014/dotfiles/412867a9f6b0e7e40ad5ea5f8d22c3e2102de6b8/secrets/cleanuperr_env.age -------------------------------------------------------------------------------- /secrets/cloudflare_certbot_token.age: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjones2014/dotfiles/412867a9f6b0e7e40ad5ea5f8d22c3e2102de6b8/secrets/cloudflare_certbot_token.age -------------------------------------------------------------------------------- /secrets/docmost_env.age: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjones2014/dotfiles/412867a9f6b0e7e40ad5ea5f8d22c3e2102de6b8/secrets/docmost_env.age -------------------------------------------------------------------------------- /secrets/duckdns_token.age: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjones2014/dotfiles/412867a9f6b0e7e40ad5ea5f8d22c3e2102de6b8/secrets/duckdns_token.age -------------------------------------------------------------------------------- /secrets/gatus_discord_webhook_env.age: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjones2014/dotfiles/412867a9f6b0e7e40ad5ea5f8d22c3e2102de6b8/secrets/gatus_discord_webhook_env.age -------------------------------------------------------------------------------- /secrets/homarr_env.age: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjones2014/dotfiles/412867a9f6b0e7e40ad5ea5f8d22c3e2102de6b8/secrets/homarr_env.age -------------------------------------------------------------------------------- /secrets/mullvad_wireguard.age: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjones2014/dotfiles/412867a9f6b0e7e40ad5ea5f8d22c3e2102de6b8/secrets/mullvad_wireguard.age -------------------------------------------------------------------------------- /secrets/paperless_admin_pw.age: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjones2014/dotfiles/412867a9f6b0e7e40ad5ea5f8d22c3e2102de6b8/secrets/paperless_admin_pw.age -------------------------------------------------------------------------------- /secrets/wireguard_server.age: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrjones2014/dotfiles/412867a9f6b0e7e40ad5ea5f8d22c3e2102de6b8/secrets/wireguard_server.age -------------------------------------------------------------------------------- /ublock-mdbook/.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /ublock-mdbook/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | 3 | # `nix build` results 4 | result 5 | -------------------------------------------------------------------------------- /ublock-mdbook/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ublock-mdbook" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | mdbook = "0.4.30" 10 | serde = { version = "1", features = ["derive"] } 11 | serde_json = "1" 12 | serde_yaml = "0.9.21" 13 | clap = "4" 14 | regex = "1" 15 | -------------------------------------------------------------------------------- /ublock-mdbook/default.nix: -------------------------------------------------------------------------------- 1 | { pkgs, ... }: 2 | let 3 | manifest = (pkgs.lib.importTOML ./Cargo.toml).package; 4 | in 5 | pkgs.rustPlatform.buildRustPackage { 6 | inherit (manifest) version; 7 | pname = manifest.name; 8 | cargoLock.lockFile = ./Cargo.lock; 9 | src = pkgs.lib.cleanSource ./.; 10 | } 11 | -------------------------------------------------------------------------------- /ublock-mdbook/flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1694529238, 9 | "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1696193975, 24 | "narHash": "sha256-mnQjUcYgp9Guu3RNVAB2Srr1TqKcPpRXmJf4LJk6KRY=", 25 | "owner": "nixos", 26 | "repo": "nixpkgs", 27 | "rev": "fdd898f8f79e8d2f99ed2ab6b3751811ef683242", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "nixos", 32 | "ref": "nixos-unstable", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "root": { 38 | "inputs": { 39 | "flake-utils": "flake-utils", 40 | "nixpkgs": "nixpkgs" 41 | } 42 | }, 43 | "systems": { 44 | "locked": { 45 | "lastModified": 1681028828, 46 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 47 | "owner": "nix-systems", 48 | "repo": "default", 49 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "nix-systems", 54 | "repo": "default", 55 | "type": "github" 56 | } 57 | } 58 | }, 59 | "root": "root", 60 | "version": 7 61 | } 62 | -------------------------------------------------------------------------------- /ublock-mdbook/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "A very basic flake"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | }; 8 | 9 | outputs = 10 | { 11 | self, 12 | nixpkgs, 13 | flake-utils, 14 | ... 15 | }: 16 | flake-utils.lib.eachSystem [ "x86_64-linux" "aarch64-darwin" ] ( 17 | system: 18 | let 19 | pkgs = nixpkgs.legacyPackages.${system}; 20 | in 21 | { 22 | packages.default = pkgs.callPackage ./. { inherit pkgs; }; 23 | devShell = pkgs.mkShell { 24 | CARGO_INSTALL_ROOT = "${toString ./.}/.cargo"; 25 | buildInputs = with pkgs; [ 26 | cargo 27 | rustc 28 | git 29 | clippy 30 | rust-analyzer 31 | libiconv 32 | ]; 33 | }; 34 | } 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /ublock-mdbook/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::{Arg, ArgMatches, Command}; 2 | use mdbook::{ 3 | book::Book, 4 | preprocess::{CmdPreprocessor, Preprocessor, PreprocessorContext}, 5 | BookItem, 6 | }; 7 | use regex::Regex; 8 | use std::io::prelude::*; 9 | use std::{collections::HashMap, error::Error, io, process}; 10 | 11 | fn parse_ublock_filters() -> (String, String, String) { 12 | let filters = 13 | serde_yaml::from_str::>>(include_str!("./ublock-filters.yml")) 14 | .unwrap(); 15 | 16 | let md_linkify_pattern = Regex::new(r"[^A-Za-z]").unwrap(); 17 | 18 | let mut filters_toc = String::from(""); 19 | let mut filters_all = String::from(""); 20 | let mut individual_sections = String::from(""); 21 | for item in filters.iter() { 22 | for (site_name, filters_text) in item.iter() { 23 | filters_toc.push_str( 24 | format!( 25 | "- [{}](#{})\n", 26 | site_name, 27 | md_linkify_pattern 28 | .replace_all(site_name.to_lowercase().as_str(), "-") 29 | .replace("---", "--") 30 | ) 31 | .as_str(), 32 | ); 33 | individual_sections.push_str( 34 | format!( 35 | "\n## {}\n\n```adblock\n{}\n```\n", 36 | site_name, 37 | filters_text.trim() 38 | ) 39 | .as_str(), 40 | ); 41 | filters_all.push_str( 42 | format!( 43 | r#" 44 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 45 | !! {}{}!! 46 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 47 | 48 | {} 49 | 50 | "#, 51 | site_name, 52 | " ".repeat(36 - site_name.len()), 53 | filters_text.trim(), 54 | ) 55 | .as_str(), 56 | ); 57 | } 58 | } 59 | 60 | filters_all = filters_all.trim().to_string(); 61 | 62 | filters_toc.push('\n'); 63 | 64 | (filters_all, filters_toc, individual_sections) 65 | } 66 | 67 | struct UblockTagProcessor; 68 | 69 | impl Preprocessor for UblockTagProcessor { 70 | fn name(&self) -> &str { 71 | "ublockfilters" 72 | } 73 | 74 | fn run(&self, _: &PreprocessorContext, mut book: Book) -> mdbook::errors::Result { 75 | let (filters_all, filters_toc, filters_sections) = parse_ublock_filters(); 76 | book.for_each_mut(|bookitem| { 77 | if let BookItem::Chapter(chapter) = bookitem { 78 | chapter.content = chapter 79 | .content 80 | .replace( 81 | "{{#ublockfilters-all}}", 82 | format!("```adblock\n{}\n```", filters_all.as_str()).as_str(), 83 | ) 84 | .replace("{{#ublockfilters-toc}}", filters_toc.as_str()) 85 | .replace("{{#ublockfilters}}", filters_sections.as_str()); 86 | } 87 | }); 88 | Ok(book) 89 | } 90 | } 91 | 92 | fn handle_processing(pre: &dyn Preprocessor) -> Result<(), Box> { 93 | let (ctx, book) = CmdPreprocessor::parse_input(io::stdin())?; 94 | let processed_book = pre.run(&ctx, book).unwrap(); 95 | serde_json::to_writer(io::stdout(), &processed_book)?; 96 | 97 | Ok(()) 98 | } 99 | 100 | fn is_supported(pre: &dyn Preprocessor, sub_args: &ArgMatches) -> bool { 101 | let renderer = sub_args 102 | .get_one::("renderer") 103 | .expect("Required argument"); 104 | pre.supports_renderer(renderer) 105 | } 106 | 107 | fn make_app() -> Command { 108 | Command::new("ublockfilters-preprocessor") 109 | .about("A mdbook preprocessor which loads my uBlock Origin filters into the page, and generates a filter list which can be subscribed to.") 110 | .subcommand( 111 | Command::new("supports") 112 | .arg(Arg::new("renderer").required(true)) 113 | .about("Check whether a renderer is supported by this preprocessor"), 114 | ).subcommand(Command::new("gen-filter-list").arg( 115 | Arg::new("path").required(true) 116 | ).about("Generate ublock-filters.txt list that can be subscribed to.")) 117 | } 118 | 119 | fn main() -> Result<(), Box> { 120 | let matches = make_app().get_matches(); 121 | 122 | let preprocessor = UblockTagProcessor {}; 123 | 124 | if let Some(sub_args) = matches.subcommand_matches("supports") { 125 | process::exit(if is_supported(&preprocessor, sub_args) { 126 | 0 127 | } else { 128 | 1 129 | }); 130 | } else if let Some(sub_args) = matches.subcommand_matches("gen-filter-list") { 131 | let path = sub_args 132 | .get_one::("path") 133 | .expect("required argument"); 134 | let (filters_all, _, _) = parse_ublock_filters(); 135 | let mut writer = std::fs::OpenOptions::new() 136 | .create(true) 137 | .write(true) 138 | .truncate(true) 139 | .open(path)?; 140 | writer.write_all(filters_all.as_bytes())?; 141 | } else if let Err(e) = handle_processing(&preprocessor) { 142 | eprintln!("{}", e); 143 | process::exit(1); 144 | } 145 | Ok(()) 146 | } 147 | -------------------------------------------------------------------------------- /ublock-mdbook/src/ublock-filters.yml: -------------------------------------------------------------------------------- 1 | - YouTube: | 2 | ! block YouTube Shorts 3 | www.youtube.com##.ytdRichGridGroupHost 4 | 5 | ! block "Join" button 6 | www.youtube.com##div#sponsor-button 7 | 8 | ! Google sometimes adds an artifical 5 second delay on youtube 9 | ! for Firefox users... 10 | www.youtube.com##+js(nano-stb, resolve(1), *, 0.001) 11 | 12 | ! Block "Members Only" videos 13 | www.youtube.com##ytd-rich-item-renderer.ytd-rich-grid-renderer:has(.badge-style-type-members-only) 14 | 15 | ! Block "ambient mode" garbage 16 | youtube.com###cinematics.ytd-watch-flexy:remove() 17 | 18 | ! Block YouTube's cringe gigantic banner ads on the home screen 19 | www.youtube.com##ytd-statement-banner-renderer 20 | www.youtube.com##.ytd-brand-video-shelf-renderer 21 | 22 | ! Block YouTube's definitely totally unbiased "misinformation" banners, I can think for myself 23 | www.youtube.com##.ytd-clarification-renderer 24 | www.youtube.com###clarify-box 25 | 26 | ! Copied from https://raw.githubusercontent.com/mchangrh/yt-neuter/main/yt-neuter.txt 27 | 28 | ! voice search button 29 | youtube.com###voice-search-button 30 | 31 | ! create button 32 | youtube.com##ytd-topbar-menu-button-renderer:has(button[aria-label=Create]) 33 | 34 | ! block image/animations from being fetched (falls back to normal logo) 35 | ||www.gstatic.com/youtube/img/promos/*$image 36 | 37 | ! block child elements of yoodle 38 | youtube.com##.ytd-yoodle-renderer 39 | 40 | !! surveys (#24) 41 | ! survey answer card/ thanks 42 | youtube.com##yt-survey-answer-card-renderer 43 | 44 | ! checkbox survey? 45 | youtube.com##.ytd-checkbox-survey-renderer 46 | 47 | ! feedback survey? 48 | youtube.com##ytd-feedback-survey-renderer 49 | youtube.com##.ytd-feedback-survey-renderer 50 | 51 | ! follow up survey? 52 | youtube.com##.ytd-survey-follow-up-renderer 53 | youtube.com##ytd-survey-follow-up-renderer 54 | 55 | ! generic surveys 56 | youtube.com##ytd-inline-survey-renderer 57 | youtube.com##.ytd-inline-survey-renderer 58 | 59 | ! multistage survey? 60 | youtube.com##.ytd-multi-stage-survey-renderer 61 | youtube.com##ytd-multi-stage-survey-renderer 62 | 63 | ! ratings survey? 64 | youtube.com##ytd-rating-survey-renderer 65 | youtube.com##.ytd-rating-survey-renderer 66 | 67 | ! RED exit survey 68 | youtube.com##.ytd-red-cancel-survey-renderer 69 | youtube.com##ytd-red-cancel-survey-renderer 70 | 71 | ! "how are your recommendations" survey 72 | youtube.com##ytd-single-option-survey-renderer 73 | youtube.com##.ytd-single-option-survey-renderer 74 | 75 | ! shelves (#15) 76 | ! game shelf 77 | youtube.com##ytd-rich-shelf-renderer[has-rounded-box-art-thumbnail-style] 78 | 79 | ! free movies (targets movies channelID 80 | youtube.com##ytd-rich-shelf-renderer:has(a[href="/channel/UClgRkhTL3_hImCAmdLfDE4g"]) 81 | 82 | ! community posts 83 | youtube.com##ytd-rich-shelf-renderer:has(ytd-rich-item-renderer[is-post]) 84 | 85 | ! breaking news shelf 86 | youtube.com##ytd-rich-shelf-renderer:has(#title:has-text(Breaking news)) 87 | m.youtube.com##ytm-rich-section-renderer:has(.rich-shelf-title:has-text(Breaking news)) 88 | 89 | ! brand featured banner/ shelf #40 90 | youtube.com##ytd-rich-shelf-renderer:has(ytd-badge-supported-renderer#featured-badge) 91 | youtube.com##ytd-statement-banner-renderer 92 | youtube.com##ytd-brand-video-singleton-renderer 93 | youtube.com##ytd-brand-video-shelf-renderer 94 | youtube.com##.ytd-brand-video-shelf-renderer 95 | 96 | ! mixes 97 | youtube.com##ytd-rich-item-renderer:has(ytd-thumbnail-overlay-bottom-panel-renderer) 98 | 99 | ! movies free with ads 100 | youtube.com##ytd-rich-item-renderer:has(.badge-style-type-ypc> span:has-text(Free with Ads)) 101 | 102 | ! "new to you" (#2) 103 | youtube.com##ytd-rich-item-renderer.style-scope:has(>.ytd-feed-nudge-renderer) 104 | youtube.com##.ytd-rich-item-renderer:has(>.ytd-feed-nudge-renderer) 105 | 106 | ! explore categories 107 | youtube.com##ytd-feed-filter-chip-bar-renderer:has(yt-chip-cloud-chip-renderer[chip-style=STYLE_HOME_FILTER]) 108 | 109 | ! youtube shorts in sidebar on video player page 110 | youtube.com##ytd-reel-shelf-renderer 111 | 112 | - Stack Overflow / Stack Exchange: | 113 | ! Block the cookie banner that covers the entire bottom left corner even with JS disabled 114 | stackexchange.com##.js-consent-banner 115 | stackoverflow.com##.js-consent-banner 116 | askubuntu.com##.js-consent-banner 117 | 118 | - GitHub: | 119 | ! Block Copilot Workspace button 120 | github.com##[class*="CopilotWorkspaceButton"] 121 | 122 | ! Block all GitHub Copilot shit in the header 123 | github.com##.AppHeader-CopilotChat 124 | 125 | ! Copilot shit in the header on the mobile layout 126 | github.com##copilot-dashboard-entrypoint 127 | 128 | ! Block "Code 55% faster with GitHub Copilot" (lmao, stop lying) 129 | github.com##button:has-text(with GitHub Copilot) 130 | 131 | ! Block GitHub's cringe algorithmic social media style feed 132 | github.com###feed-next 133 | github.com##[aria-labelledby="feed-next"] 134 | 135 | ! Block the corporate changelog above the "Explore Repositories" section 136 | github.com##.dashboard-changelog 137 | 138 | ! Block GitHub's ads above the corporate changelog 139 | github.com##.js-notice 140 | 141 | ! Block "achievements" 142 | github.com##.d-block:has(a[href*="?tab=achievements"]) 143 | github.com##.d-md-block:has(a[href*="?tab=achievements"]) 144 | 145 | - Glassdoor: | 146 | ! Block the "hard sell" overlay that tries to force you to pay money just to read a review 147 | www.glassdoor.com##.hardsellOverlay 148 | 149 | - PayPal: | 150 | ! Block the cookie banner, same as rejecting cookies 151 | www.paypal.com##.ccpaCookieBanner_container-custom.ccpaCookieBanner_container 152 | 153 | - Discord: | 154 | ! Block "Add super reaction" button 155 | discord.com##div[aria-label="Add Super Reaction"] 156 | 157 | ! Block Nitro stuff in user settings 158 | discord.com##div[class*="premiumTab"] 159 | discord.com##div[class*="premiumFeatureBorder"] 160 | discord.com##div[class*="birthdayFeatureBorder"] 161 | 162 | ! Block "Super" reactions, paid emojis lmao 163 | discord.com##div[class*="reactionInner"][aria-label*="super"] 164 | 165 | ! Block "Download Apps" button 166 | discord.com##div[aria-label*="Download Apps"]:upward(div[class*="listItem"]) 167 | 168 | ! Block "Buy Boosts" bar 169 | discord.com##div[aria-label*="Buy Boosts to help unlock"] 170 | 171 | ! Block "Discord's Birthday" crap 172 | discord.com##li[role="listitem"]:has-text("Discord's Birthday") 173 | 174 | ! Block "Start an Activity" button 175 | discord.com##button[aria-label="Start an Activity"] 176 | --------------------------------------------------------------------------------