me like nix
0

Configure Feed

Select the types of activity you want to include in your feed.

move to dendritic pattern

author
Sean Aye
date (Mar 22, 2026, 8:03 PM -0400) commit 237e9f52 parent 457f7412 change-id lmswmwmo
+2949 -2724
+172
.claude/skills/dendritic-nix/SKILL.md
··· 1 + --- 2 + name: dendritic-nix 3 + description: Use when the user asks about or wants to implement the "dendritic nix pattern", aspect-oriented Nix configuration, organizing Nix config by feature/aspect instead of hostname, or migrating to flake-parts modules. Also applies when discussing cross-platform Nix configs (NixOS + Darwin + Home Manager) in a single file. 4 + version: 1.0.0 5 + --- 6 + 7 + # Dendritic Nix Pattern 8 + 9 + The dendritic pattern is an **aspect-oriented** approach to Nix configuration built on [flake-parts](https://flake.parts). Each `.nix` file provides configuration for the same **aspect** (feature/concern) across different configuration classes (NixOS, Darwin, Home Manager, etc.). 10 + 11 + It is a configuration **pattern** — not a library or framework. 12 + 13 + ## Core Principle 14 + 15 + Instead of organizing by host (`hosts/mira/default.nix`, `hosts/framework16/default.nix`), organize by **feature**. A single file like `modules/ssh.nix` contains the NixOS, Darwin, and Home Manager config for SSH all in one place. 16 + 17 + ## File Structure 18 + 19 + - No mandatory directory structure 20 + - Every `.nix` file is a **flake-parts module** — uniform semantics 21 + - Files are auto-loaded (e.g., via `vic/import-tree`) 22 + - Files with `/_` in their path are ignored by convention 23 + 24 + ``` 25 + modules/ 26 + ├── ssh.nix # SSH config across all platforms 27 + ├── vim.nix # Editor config across all platforms 28 + ├── vic.nix # User "vic" across all platforms 29 + └── desktop/ 30 + ├── basic.nix # Basic desktop features 31 + └── advanced.nix # Advanced desktop features (incremental) 32 + ``` 33 + 34 + ## Module Pattern 35 + 36 + Each file is a flake-parts module that defines config for multiple configuration classes: 37 + 38 + ```nix 39 + { inputs, config, ... }: let 40 + # Shared values — replaces specialArgs 41 + sharedPort = 2277; 42 + in { 43 + flake.modules.nixos.aspect-name = { 44 + # NixOS system configuration 45 + }; 46 + 47 + flake.modules.darwin.aspect-name = { 48 + # macOS system configuration 49 + }; 50 + 51 + flake.modules.homeManager.aspect-name = { 52 + # Home Manager user configuration 53 + }; 54 + 55 + perSystem = { pkgs, ... }: { 56 + # Per-system packages, devShells, etc. 57 + }; 58 + } 59 + ``` 60 + 61 + ## Complete Example: SSH 62 + 63 + ```nix 64 + # modules/ssh.nix 65 + { inputs, config, ... }: let 66 + scpPort = 2277; 67 + in { 68 + flake.modules.nixos.ssh = { 69 + services.openssh = { 70 + enable = true; 71 + ports = [ scpPort ]; 72 + }; 73 + networking.firewall.allowedTCPPorts = [ scpPort ]; 74 + }; 75 + 76 + flake.modules.darwin.ssh = { 77 + # macOS built-in SSH server config 78 + }; 79 + 80 + flake.modules.homeManager.ssh = { 81 + # ~/.ssh/config, authorized_keys, etc. 82 + }; 83 + 84 + perSystem = { pkgs, ... }: { 85 + # Custom packages using SSH facilities 86 + }; 87 + } 88 + ``` 89 + 90 + ## User Definition Example 91 + 92 + ```nix 93 + # modules/vic.nix 94 + let 95 + userName = "vic"; 96 + in { 97 + flake.modules.nixos.${userName} = { 98 + users.users.${userName} = { 99 + isNormalUser = true; 100 + extraGroups = [ "wheel" ]; 101 + }; 102 + }; 103 + 104 + flake.modules.darwin.${userName} = { 105 + system.primaryUser = userName; 106 + }; 107 + 108 + flake.modules.homeManager.${userName} = { pkgs, lib, ... }: { 109 + home.username = lib.mkDefault userName; 110 + home.homeDirectory = lib.mkDefault ( 111 + if pkgs.stdenvNoCC.isDarwin 112 + then "/Users/${userName}" 113 + else "/home/${userName}" 114 + ); 115 + home.stateVersion = lib.mkDefault "25.05"; 116 + }; 117 + } 118 + ``` 119 + 120 + ## Minimal flake.nix 121 + 122 + ```nix 123 + { 124 + inputs = { 125 + flake-parts.url = "github:hercules-ci/flake-parts"; 126 + import-tree.url = "github:vic/import-tree"; 127 + # other inputs... 128 + }; 129 + outputs = inputs: 130 + inputs.flake-parts.lib.mkFlake { inherit inputs; } 131 + (inputs.import-tree ./modules); 132 + } 133 + ``` 134 + 135 + ## Dynamic Inputs (Optional) 136 + 137 + With `vic/flake-file`, inputs can be declared per-module: 138 + 139 + ```nix 140 + # modules/home/vim.nix 141 + { inputs, ... }: { 142 + flake-file.inputs.nixvim.url = "github:nix-community/nixvim"; 143 + flake.modules.homeManager.vim = { 144 + # config using inputs.nixvim 145 + }; 146 + } 147 + ``` 148 + 149 + ## Key Advantages 150 + 151 + 1. **Feature closures**: Everything needed for a feature lives in one file 152 + 2. **No specialArgs**: Shared values use `let` bindings and flake-parts options 153 + 3. **Uniform file semantics**: Every `.nix` file is a flake-parts module 154 + 4. **Incremental features**: Add `feature/basic.nix` and `feature/advanced.nix` independently 155 + 5. **Cross-platform**: NixOS, Darwin, and Home Manager config coexist naturally 156 + 157 + ## Configuration Classes 158 + 159 + Common classes used in `flake.modules.<class>`: 160 + - `nixos` — NixOS system config 161 + - `darwin` — macOS system config 162 + - `homeManager` — Home Manager user config 163 + - `nixvim` — Editor config 164 + - Custom classes as needed 165 + 166 + ## When Helping Users 167 + 168 + - When migrating an existing config: map each "feature" (SSH, users, desktop, etc.) to its own flake-parts module file 169 + - Each module should define config for all relevant classes in one place 170 + - Use `let` bindings for values shared across classes instead of `specialArgs` 171 + - The pattern does NOT require flakes — see `vic/dendritic-unflake` for non-flake usage 172 + - Tools like `vic/import-tree` and `vic/flake-file` are recommendations, not requirements
+50
flake.lock
··· 249 249 "type": "github" 250 250 } 251 251 }, 252 + "flake-parts": { 253 + "inputs": { 254 + "nixpkgs-lib": "nixpkgs-lib" 255 + }, 256 + "locked": { 257 + "lastModified": 1772408722, 258 + "narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=", 259 + "owner": "hercules-ci", 260 + "repo": "flake-parts", 261 + "rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3", 262 + "type": "github" 263 + }, 264 + "original": { 265 + "owner": "hercules-ci", 266 + "repo": "flake-parts", 267 + "type": "github" 268 + } 269 + }, 252 270 "flake-utils": { 253 271 "locked": { 254 272 "lastModified": 1678901627, ··· 403 421 "type": "github" 404 422 } 405 423 }, 424 + "import-tree": { 425 + "locked": { 426 + "lastModified": 1773693634, 427 + "narHash": "sha256-BtZ2dtkBdSUnFPPFc+n0kcMbgaTxzFNPv2iaO326Ffg=", 428 + "owner": "vic", 429 + "repo": "import-tree", 430 + "rev": "c41e7d58045f9057880b0d85e1152d6a4430dbf1", 431 + "type": "github" 432 + }, 433 + "original": { 434 + "owner": "vic", 435 + "repo": "import-tree", 436 + "type": "github" 437 + } 438 + }, 406 439 "kaleidux": { 407 440 "inputs": { 408 441 "naersk": "naersk_2", ··· 647 680 "type": "github" 648 681 } 649 682 }, 683 + "nixpkgs-lib": { 684 + "locked": { 685 + "lastModified": 1772328832, 686 + "narHash": "sha256-e+/T/pmEkLP6BHhYjx6GmwP5ivonQQn0bJdH9YrRB+Q=", 687 + "owner": "nix-community", 688 + "repo": "nixpkgs.lib", 689 + "rev": "c185c7a5e5dd8f9add5b2f8ebeff00888b070742", 690 + "type": "github" 691 + }, 692 + "original": { 693 + "owner": "nix-community", 694 + "repo": "nixpkgs.lib", 695 + "type": "github" 696 + } 697 + }, 650 698 "nixpkgs-stable": { 651 699 "locked": { 652 700 "lastModified": 1773814637, ··· 801 849 "berkeley-mono": "berkeley-mono", 802 850 "catppuccin": "catppuccin", 803 851 "copyparty": "copyparty", 852 + "flake-parts": "flake-parts", 804 853 "fsel": "fsel", 805 854 "home-manager": "home-manager_2", 855 + "import-tree": "import-tree", 806 856 "kaleidux": "kaleidux", 807 857 "mako-tui": "mako-tui", 808 858 "niri": "niri",
+5 -165
flake.nix
··· 2 2 description = "My NixOS Flake Configuration"; 3 3 4 4 inputs = { 5 - # Nixpkgs (stable or unstable) 6 5 nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 + flake-parts.url = "github:hercules-ci/flake-parts"; 7 + import-tree.url = "github:vic/import-tree"; 7 8 8 - # Home Manager 9 9 home-manager = { 10 10 url = "github:nix-community/home-manager/master"; 11 - inputs.nixpkgs.follows = "nixpkgs"; # Ensures Home Manager uses the same nixpkgs 11 + inputs.nixpkgs.follows = "nixpkgs"; 12 12 }; 13 13 catppuccin.url = "github:catppuccin/nix"; 14 14 niri.url = "github:sodiboo/niri-flake"; ··· 31 31 url = "github:Mjoyufull/fsel"; 32 32 inputs.nixpkgs.follows = "nixpkgs"; 33 33 }; 34 - # organelle-hello = { 35 - # url = "path:/home/sean/dev/organelle-hello"; 36 - # inputs.nixpkgs.follows = "nixpkgs"; 37 - # }; 38 34 trmnl-rs = { 39 35 url = "github:seanaye/trmnl-rs"; 40 36 inputs.nixpkgs.follows = "nixpkgs"; ··· 55 51 }; 56 52 57 53 outputs = 58 - { 59 - self, 60 - nixpkgs, 61 - home-manager, 62 - catppuccin, 63 - niri, 64 - nixarr, 65 - copyparty, 66 - nixos-hardware, 67 - agenix, 68 - nixos-raspberrypi, 69 - ... 70 - }@inputs: 71 - { 72 - nixosConfigurations = { 73 - # Replace "nixos" with your actual desired hostname if it's different 74 - # This "nixos" must match the `networking.hostName` in your configuration.nix 75 - mira = nixpkgs.lib.nixosSystem { 76 - system = "x86_64-linux"; 77 - modules = [ 78 - # Your main configuration file 79 - ./hosts/mira/configuration.nix 80 - catppuccin.nixosModules.catppuccin 81 - agenix.nixosModules.default 82 - 83 - copyparty.nixosModules.default 84 - 85 - ({ 86 - nixpkgs.overlays = [ 87 - niri.overlays.niri 88 - copyparty.overlays.default 89 - ]; 90 - }) 91 - 92 - # nixarr module 93 - nixarr.nixosModules.default 94 - 95 - # Home Manager module 96 - home-manager.nixosModules.home-manager 97 - { 98 - home-manager.extraSpecialArgs = { inherit inputs; }; 99 - home-manager.users.sean = import ./hosts/mira/home.nix; 100 - } 101 - ]; 102 - specialArgs = { inherit inputs; }; 103 - }; 104 - framework16 = nixpkgs.lib.nixosSystem { 105 - system = "x86_64-linux"; 106 - modules = [ 107 - # Your main configuration file 108 - ./hosts/framework16/configuration.nix 109 - catppuccin.nixosModules.catppuccin 110 - nixos-hardware.nixosModules.framework-16-7040-amd 111 - 112 - ({ 113 - nixpkgs.overlays = [ niri.overlays.niri ]; 114 - }) 115 - 116 - # Home Manager module 117 - home-manager.nixosModules.home-manager 118 - { 119 - home-manager.extraSpecialArgs = { inherit inputs; }; 120 - home-manager.users.sean = import ./hosts/framework16/home.nix; 121 - } 122 - ]; 123 - specialArgs = { inherit inputs; }; 124 - }; 125 - 126 - pi = nixpkgs.lib.nixosSystem { 127 - system = "aarch64-linux"; 128 - modules = [ 129 - "${nixpkgs}/nixos/modules/installer/sd-card/sd-image-aarch64.nix" 130 - nixos-hardware.nixosModules.raspberry-pi-4 131 - agenix.nixosModules.default 132 - ./hosts/pi/configuration.nix 133 - 134 - # Allow missing kernel modules (Pi kernel doesn't have all x86 modules) 135 - # See: https://github.com/NixOS/nixpkgs/issues/154163 136 - ({ 137 - nixpkgs.overlays = [ 138 - (final: super: { 139 - makeModulesClosure = x: super.makeModulesClosure (x // { allowMissing = true; }); 140 - }) 141 - ]; 142 - }) 143 - ]; 144 - specialArgs = { inherit inputs; }; 145 - }; 146 - 147 - pizero = nixpkgs.lib.nixosSystem { 148 - system = "aarch64-linux"; 149 - modules = [ 150 - "${nixpkgs}/nixos/modules/installer/sd-card/sd-image-aarch64.nix" 151 - agenix.nixosModules.default 152 - ./hosts/pizero/configuration.nix 153 - 154 - # Allow missing kernel modules (Pi kernel doesn't have all x86 modules) 155 - ({ 156 - nixpkgs.overlays = [ 157 - (final: super: { 158 - makeModulesClosure = x: super.makeModulesClosure (x // { allowMissing = true; }); 159 - }) 160 - ]; 161 - }) 162 - ]; 163 - specialArgs = { inherit inputs; }; 164 - }; 165 - 166 - # organelle = nixpkgs.lib.nixosSystem { 167 - # system = "aarch64-linux"; 168 - # modules = [ 169 - # "${nixpkgs}/nixos/modules/installer/sd-card/sd-image-aarch64.nix" 170 - # agenix.nixosModules.default 171 - # ./hosts/organelle/configuration.nix 172 - # 173 - # ({ 174 - # nixpkgs.overlays = [ 175 - # (final: super: { 176 - # makeModulesClosure = x: super.makeModulesClosure (x // { allowMissing = true; }); 177 - # }) 178 - # ]; 179 - # }) 180 - # ]; 181 - # specialArgs = { inherit inputs; }; 182 - # }; 183 - 184 - kodi-pi = nixos-raspberrypi.lib.nixosSystemFull { 185 - specialArgs = { inherit nixos-raspberrypi inputs; }; 186 - modules = [ 187 - ( 188 - { nixos-raspberrypi, ... }: 189 - { 190 - imports = with nixos-raspberrypi.nixosModules; [ 191 - raspberry-pi-5.base 192 - raspberry-pi-5.page-size-16k 193 - raspberry-pi-5.display-vc4 194 - ]; 195 - } 196 - ) 197 - # Disable SDL3 test suite (testprocess fails in Nix sandbox) 198 - ( 199 - { pkgs, ... }: 200 - { 201 - nixpkgs.overlays = [ 202 - (final: prev: { 203 - sdl3 = prev.sdl3.overrideAttrs (old: { 204 - doCheck = false; 205 - }); 206 - }) 207 - ]; 208 - } 209 - ) 210 - agenix.nixosModules.default 211 - ./hosts/kodi-pi/configuration.nix 212 - ]; 213 - }; 214 - }; 215 - }; 54 + inputs: 55 + inputs.flake-parts.lib.mkFlake { inherit inputs; } (inputs.import-tree ./modules); 216 56 }
-227
hosts/common/common.nix
··· 1 - { pkgs, lib, inputs, ... }: 2 - 3 - let 4 - hasBerkeleyMono = inputs ? berkeley-mono && !(inputs.berkeley-mono ? isStub); 5 - berkeley-mono-typeface = 6 - if hasBerkeleyMono then inputs.berkeley-mono.packages.${pkgs.system}.default else null; 7 - 8 - in 9 - { 10 - 11 - nix.settings.experimental-features = [ 12 - "nix-command" 13 - "flakes" 14 - ]; 15 - nix.settings.download-buffer-size = 268435456; 16 - 17 - hardware.bluetooth.enable = true; 18 - hardware.bluetooth.powerOnBoot = true; 19 - services.blueman.enable = true; 20 - 21 - # Bootloader. 22 - boot.loader.systemd-boot.enable = true; 23 - boot.loader.systemd-boot.configurationLimit = 10; 24 - boot.loader.efi.canTouchEfiVariables = true; 25 - 26 - services.fwupd.enable = true; 27 - # Use latest kernel. 28 - boot.kernelPackages = pkgs.linuxPackages_latest; 29 - boot.kernel.sysctl."kernel.task_delayacct" = 1; 30 - 31 - # Fix USB disconnection issues with Dell U4025QW Thunderbolt dock 32 - # Disables autosuspend and LPM for Dell hub (vendor ID: 1d5c, product ID: 5801) 33 - # boot.kernelParams = [ "usbcore.quirks=1d5c:5801:gk" ]; 34 - # If USB disconnects persist, try disabling UCSI ACPI instead: 35 - # boot.kernelParams = [ "module_blacklist=ucsi_acpi" ]; 36 - 37 - # Enable networking 38 - networking.networkmanager.enable = true; 39 - systemd.services.NetworkManager-wait-online.enable = false; 40 - 41 - # Set your time zone. 42 - # time.timeZone = "America/New_York"; 43 - time.timeZone = "America/Toronto"; 44 - 45 - # Select internationalisation properties. 46 - i18n.defaultLocale = "en_US.UTF-8"; 47 - 48 - i18n.extraLocaleSettings = { 49 - LC_ADDRESS = "en_US.UTF-8"; 50 - LC_IDENTIFICATION = "en_US.UTF-8"; 51 - LC_MEASUREMENT = "en_US.UTF-8"; 52 - LC_MONETARY = "en_US.UTF-8"; 53 - LC_NAME = "en_US.UTF-8"; 54 - LC_NUMERIC = "en_US.UTF-8"; 55 - LC_PAPER = "en_US.UTF-8"; 56 - LC_TELEPHONE = "en_US.UTF-8"; 57 - LC_TIME = "en_US.UTF-8"; 58 - }; 59 - 60 - # Font configuration 61 - fonts = { 62 - fontDir.enable = true; 63 - fontconfig = { 64 - enable = true; 65 - defaultFonts = { 66 - monospace = 67 - lib.optionals hasBerkeleyMono [ 68 - "BerkeleyMono Nerd Font" 69 - "BerkeleyMono" 70 - ] 71 - ++ [ "JetBrainsMono Nerd Font" ]; 72 - }; 73 - }; 74 - packages = lib.optionals hasBerkeleyMono [ berkeley-mono-typeface ]; 75 - }; 76 - 77 - programs.niri = { 78 - enable = true; 79 - }; 80 - 81 - catppuccin = { 82 - enable = true; 83 - flavor = "frappe"; 84 - }; 85 - 86 - programs.regreet = { 87 - enable = true; 88 - cageArgs = [ "-s" "-d" ]; 89 - settings.GTK.application_prefer_dark_theme = true; 90 - theme = { 91 - package = pkgs.catppuccin-gtk.override { 92 - variant = "frappe"; 93 - accents = [ "lavender" ]; 94 - size = "standard"; 95 - }; 96 - name = "catppuccin-frappe-lavender-standard"; 97 - }; 98 - iconTheme = { 99 - package = pkgs.catppuccin-papirus-folders.override { 100 - flavor = "frappe"; 101 - accent = "lavender"; 102 - }; 103 - name = "Papirus-Dark"; 104 - }; 105 - cursorTheme = { 106 - package = pkgs.catppuccin-cursors.frappeDark; 107 - name = "catppuccin-frappe-dark-cursors"; 108 - }; 109 - }; 110 - 111 - # Scale regreet's greeter for HiDPI displays 112 - services.greetd.settings.default_session.command = lib.mkForce 113 - "${pkgs.dbus}/bin/dbus-run-session ${lib.getExe pkgs.cage} -s -d -- env GDK_SCALE=2 ${lib.getExe pkgs.greetd.regreet}"; 114 - 115 - # Enable CUPS to print documents. 116 - services.printing.enable = true; 117 - 118 - security.polkit.enable = true; 119 - security.rtkit.enable = true; 120 - services.pipewire = { 121 - enable = true; 122 - alsa.enable = true; 123 - alsa.support32Bit = true; 124 - pulse.enable = true; 125 - # If you want to use JACK applications, uncomment this 126 - #jack.enable = true; 127 - 128 - # use the example session manager (no others are packaged yet so this is enabled by default, 129 - # no need to redefine it in your config for now) 130 - #media-session.enable = true; 131 - }; 132 - 133 - services.gnome.gnome-keyring.enable = true; 134 - security.pam.services.greetd.enableGnomeKeyring = true; 135 - 136 - services.udisks2.enable = true; 137 - services.tailscale.enable = true; 138 - services.pcscd.enable = true; # Smart card daemon for Yubikey 139 - 140 - security.pam.u2f = { 141 - enable = true; 142 - control = "sufficient"; 143 - cue = true; 144 - settings = { 145 - origin = "pam://nixos"; 146 - appid = "pam://nixos"; 147 - }; 148 - }; 149 - security.pam.services.sudo.u2fAuth = true; 150 - 151 - programs.yubikey-touch-detector.enable = true; 152 - 153 - # ZSA Keyboard udev rules for Oryx web flashing and live training 154 - services.udev.extraRules = '' 155 - # Rules for Oryx web flashing and live training 156 - KERNEL=="hidraw*", ATTRS{idVendor}=="16c0", MODE="0664", GROUP="plugdev" 157 - KERNEL=="hidraw*", ATTRS{idVendor}=="3297", MODE="0664", GROUP="plugdev" 158 - 159 - # Legacy rules for live training over webusb (Not needed for firmware v21+) 160 - # Rule for all ZSA keyboards 161 - SUBSYSTEM=="usb", ATTR{idVendor}=="3297", GROUP="plugdev" 162 - # Rule for the Moonlander 163 - SUBSYSTEM=="usb", ATTR{idVendor}=="3297", ATTR{idProduct}=="1969", GROUP="plugdev" 164 - # Rule for the Ergodox EZ 165 - SUBSYSTEM=="usb", ATTR{idVendor}=="feed", ATTR{idProduct}=="1307", GROUP="plugdev" 166 - # Rule for the Planck EZ 167 - SUBSYSTEM=="usb", ATTR{idVendor}=="feed", ATTR{idProduct}=="6060", GROUP="plugdev" 168 - 169 - # Wally Flashing rules for the Ergodox EZ 170 - ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789B]?", ENV{ID_MM_DEVICE_IGNORE}="1" 171 - ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789A]?", ENV{MTP_NO_PROBE}="1" 172 - SUBSYSTEMS=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789ABCD]?", MODE:="0666" 173 - KERNEL=="ttyACM*", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789B]?", MODE:="0666" 174 - 175 - # Keymapp / Wally Flashing rules for the Moonlander and Planck EZ 176 - SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="df11", MODE:="0666", SYMLINK+="stm32_dfu" 177 - # Keymapp Flashing rules for the Voyager 178 - SUBSYSTEMS=="usb", ATTRS{idVendor}=="3297", MODE:="0666", SYMLINK+="ignition_dfu" 179 - # USB serial device access via Chrome/Chromium 180 - KERNEL=="ttyUSB[0-9]*", MODE:="0666", GROUP="dialout" 181 - KERNEL=="ttyACM[0-9]*", MODE:="0666", GROUP="dialout" 182 - ''; 183 - 184 - # Define a user account. Don't forget to set a password with ‘passwd’. 185 - users.groups.storage = { }; 186 - users.groups.plugdev = { }; 187 - users.users.sean = { 188 - isNormalUser = true; 189 - description = "Sean Aye"; 190 - extraGroups = [ 191 - "docker" 192 - "networkmanager" 193 - "wheel" 194 - "video" 195 - "disk" 196 - "storage" 197 - "input" 198 - "plugdev" 199 - "dialout" 200 - ]; 201 - shell = pkgs.fish; 202 - linger = true; 203 - }; 204 - 205 - programs.fish.enable = true; 206 - programs._1password.enable = true; 207 - programs._1password-gui = { 208 - enable = true; 209 - polkitPolicyOwners = [ "sean" ]; 210 - }; 211 - 212 - virtualisation.docker.enable = true; 213 - 214 - # Allow unfree packages 215 - nixpkgs.config.allowUnfree = true; 216 - 217 - # List packages installed in system profile. To search, run: 218 - # $ nix search wget 219 - environment.systemPackages = with pkgs; [ 220 - wl-clipboard 221 - ]; 222 - environment.variables = { 223 - EDITOR = "hx"; 224 - VISUAL = "hx"; 225 - SUDO_EDITOR = "hx"; 226 - }; 227 - }
-1029
hosts/common/home.nix
··· 1 - { 2 - pkgs, 3 - inputs, 4 - config, 5 - ... 6 - }: 7 - 8 - let 9 - zjctl = import ../../packages/zjctl.nix { inherit pkgs; }; 10 - zrpc-wasm = import ../../packages/zrpc-wasm.nix { inherit pkgs; }; 11 - in 12 - { 13 - # Import the home-manager modules you want to use 14 - imports = [ 15 - inputs.catppuccin.homeModules.catppuccin 16 - inputs.niri.homeModules.niri 17 - inputs.zen-browser.homeModules.beta 18 - inputs.agenix.homeManagerModules.default 19 - ]; 20 - 21 - # All your user-specific packages 22 - home.packages = with pkgs; [ 23 - helix 24 - git 25 - jujutsu # jj-cli 26 - htop 27 - iotop 28 - ncdu 29 - youtube-tui 30 - yt-dlp # youtube-tui and mpv need this to resolve YouTube URLs 31 - zellij # terminal multiplexer 32 - alacritty 33 - inputs.fsel.packages.${pkgs.system}.default # App launcher / fuzzy finder 34 - inputs.mako-tui.packages.${pkgs.system}.default # Notification TUI 35 - bemoji # emoji picker 36 - networkmanager_dmenu # network picker for fuzzel 37 - quickshell # Status bar (QML-based) 38 - inputs.kaleidux.packages.${pkgs.system}.default # Dynamic wallpaper daemon 39 - (import ../../packages/cclip.nix { inherit pkgs; }) # Clipboard history manager 40 - pavucontrol # GUI for PulseAudio/PipeWire volume control 41 - playerctl # MPRIS media player control 42 - (element-desktop.override { 43 - commandLineArgs = "--password-store=gnome-libsecret"; 44 - }) 45 - fd 46 - ripgrep 47 - # inputs.opencodex.packages.${pkgs.system}.default 48 - yazi # tui file browser 49 - gh # github cli 50 - forgejo-cli 51 - gh-dash # github dashboard TUI 52 - diffnav # git diff viewer 53 - signal-desktop 54 - xwayland-satellite # for running x11 apps 55 - nixfmt # nix formatter 56 - nil # nix language server 57 - atac # postman-like TUI 58 - trippy # network analyzer 59 - rsync # file sync utility 60 - udiskie # for mounting external drives 61 - darktable # photo editing 62 - zoxide 63 - chromium 64 - claude-code 65 - libnotify # for notify-send (claude-notify) 66 - zjctl # Programmatic Zellij control 67 - (pkgs.writeShellScriptBin "claude-notify" '' 68 - PANE_ID="$ZELLIJ_PANE_ID" 69 - SESSION="$ZELLIJ_SESSION_NAME" 70 - ID_FILE="/tmp/claude-notify-''${PANE_ID}" 71 - ( 72 - { 73 - read -r NOTIFY_ID 74 - echo "$NOTIFY_ID" > "$ID_FILE" 75 - while read -r line; do 76 - if [ "$line" = "default" ]; then 77 - WIN_ID=$(niri msg windows | ${pkgs.gnugrep}/bin/grep -B1 "$SESSION" | ${pkgs.gnugrep}/bin/grep -oP '(?<=Window ID )\d+') 78 - if [ -n "$WIN_ID" ]; then 79 - niri msg action focus-window --id "$WIN_ID" 80 - fi 81 - zjctl pane focus --pane "id:terminal:$PANE_ID" 82 - fi 83 - done 84 - } < <(${pkgs.libnotify}/bin/notify-send "Claude Code" "Waiting for your approval" \ 85 - --app-name=claude-code \ 86 - --action=default=Open \ 87 - --print-id \ 88 - --wait) 89 - rm -f "$ID_FILE" 90 - ) & 91 - '') 92 - (pkgs.writeShellScriptBin "claude-notify-clear" '' 93 - PANE_ID="$ZELLIJ_PANE_ID" 94 - ID_FILE="/tmp/claude-notify-''${PANE_ID}" 95 - if [ -f "$ID_FILE" ]; then 96 - NOTIFY_ID=$(cat "$ID_FILE") 97 - ${pkgs.dbus}/bin/dbus-send --session \ 98 - --print-reply \ 99 - --dest=org.freedesktop.Notifications \ 100 - --type=method_call \ 101 - /org/freedesktop/Notifications \ 102 - org.freedesktop.Notifications.CloseNotification \ 103 - "uint32:$NOTIFY_ID" >/dev/null 2>&1 104 - rm -f "$ID_FILE" 105 - fi 106 - '') 107 - nautilus # file browser 108 - sqlitebrowser 109 - gnome-characters # symbol picker 110 - sendme # file transfer 111 - desktop-file-utils # for managing .desktop files 112 - flyctl # fly.io cli 113 - vscode-json-languageserver 114 - gnome-network-displays 115 - rainfrog # db tui 116 - just 117 - loupe # image viewer 118 - glycin-loaders # various format loaders for loupe 119 - docker-compose 120 - discord 121 - mangohud 122 - prismlauncher # minecraft launcher 123 - fastfetch 124 - inputs.agenix.packages.${pkgs.system}.default # agenix CLI 125 - age-plugin-yubikey # Yubikey support for agenix 126 - # --- FONTS ARE IMPORTANT --- 127 - # Berkeley Mono is the main system font, keeping JetBrains and Font Awesome for icons 128 - font-awesome 129 - noto-fonts 130 - noto-fonts-cjk-sans 131 - noto-fonts-color-emoji 132 - nerd-fonts.jetbrains-mono 133 - nerd-fonts.symbols-only 134 - # --- POLKIT AGENT (for 1Password GUI, etc.) --- 135 - lxqt.lxqt-policykit # Lightweight polkit agent 136 - ]; 137 - 138 - programs.niri = { 139 - enable = true; 140 - settings = { 141 - window-rules = [ 142 - { 143 - geometry-corner-radius = { 144 - top-left = 5.0; 145 - top-right = 5.0; 146 - bottom-left = 5.0; 147 - bottom-right = 5.0; 148 - }; 149 - clip-to-geometry = true; 150 - draw-border-with-background = false; 151 - } 152 - { 153 - matches = [ { app-id = "^steam_app_"; } ]; 154 - border.enable = false; 155 - open-fullscreen = true; 156 - } 157 - { 158 - matches = [ { app-id = "^fsel$"; } ]; 159 - open-floating = true; 160 - default-column-width.fixed = 800; 161 - default-window-height.fixed = 500; 162 - } 163 - { 164 - matches = [ { app-id = "^mako-tui$"; } ]; 165 - open-floating = true; 166 - default-column-width.fixed = 800; 167 - default-window-height.fixed = 500; 168 - } 169 - ]; 170 - debug = { 171 - honor-xdg-activation-with-invalid-serial = { }; 172 - }; 173 - layout = { 174 - focus-ring = { 175 - width = 2; 176 - active.color = "#8caaee"; 177 - inactive.color = "#414559"; 178 - }; 179 - struts = { 180 - top = -6; 181 - bottom = -6; 182 - left = 0; 183 - right = 0; 184 - }; 185 - gaps = 8; 186 - }; 187 - gestures = { 188 - hot-corners = { 189 - enable = false; 190 - }; 191 - }; 192 - binds = { 193 - "Mod+d".action.spawn = [ 194 - "alacritty" 195 - "--class" 196 - "fsel" 197 - "-e" 198 - "fsel" 199 - "--detach" 200 - ]; 201 - "Mod+c".action.spawn = [ 202 - "alacritty" 203 - "--class" 204 - "fsel" 205 - "-e" 206 - "fsel" 207 - "--cclip" 208 - ]; 209 - "Mod+e".action.spawn = "bemoji"; 210 - "Mod+n".action.spawn = [ 211 - "alacritty" 212 - "--class" 213 - "mako-tui" 214 - "-e" 215 - "mako-tui" 216 - ]; 217 - "Mod+a".action.spawn = "alacritty"; 218 - "Mod+h".action = { 219 - focus-column-left = { }; 220 - }; 221 - "Mod+j".action = { 222 - focus-workspace-down = { }; 223 - }; 224 - "Mod+k".action = { 225 - focus-workspace-up = { }; 226 - }; 227 - "Mod+l".action = { 228 - focus-column-right = { }; 229 - }; 230 - "Mod+Shift+h".action = { 231 - move-column-left = { }; 232 - }; 233 - "Mod+Shift+j".action = { 234 - move-window-down-or-to-workspace-down = { }; 235 - }; 236 - "Mod+Shift+k".action = { 237 - move-window-up-or-to-workspace-up = { }; 238 - }; 239 - "Mod+Shift+l".action = { 240 - move-column-right = { }; 241 - }; 242 - "Mod+Down".action = { 243 - move-workspace-down = { }; 244 - }; 245 - "Mod+Up".action = { 246 - move-workspace-up = { }; 247 - }; 248 - "Mod+p".action = { 249 - show-hotkey-overlay = { }; 250 - }; 251 - "Mod+o".action = { 252 - toggle-overview = { }; 253 - }; 254 - "Mod+q".action = { 255 - close-window = { }; 256 - }; 257 - "Mod+f".action = { 258 - toggle-window-floating = { }; 259 - }; 260 - "Mod+Shift+f".action = { 261 - switch-focus-between-floating-and-tiling = { }; 262 - }; 263 - "Mod+m".action = { 264 - fullscreen-window = { }; 265 - }; 266 - "Mod+s".action = { 267 - screenshot = { 268 - show-pointer = true; 269 - }; 270 - }; 271 - "Mod+1".action = { 272 - set-column-width = "100%"; 273 - }; 274 - "Mod+2".action = { 275 - set-column-width = "50%"; 276 - }; 277 - "Mod+Minus".action = { 278 - set-column-width = "-10%"; 279 - }; 280 - "Mod+Equal".action = { 281 - set-column-width = "+10%"; 282 - }; 283 - "Mod+Shift+q".action = { 284 - quit = { }; 285 - }; 286 - "Mod+Shift+r".action.spawn = [ 287 - "systemctl" 288 - "--user" 289 - "restart" 290 - "quickshell.service" 291 - ]; 292 - "XF86AudioPlay".action.spawn = [ 293 - "playerctl" 294 - "play-pause" 295 - ]; 296 - "XF86AudioStop".action.spawn = [ 297 - "playerctl" 298 - "stop" 299 - ]; 300 - "XF86AudioNext".action.spawn = [ 301 - "playerctl" 302 - "next" 303 - ]; 304 - "XF86AudioPrev".action.spawn = [ 305 - "playerctl" 306 - "previous" 307 - ]; 308 - "XF86MonBrightnessDown".action.spawn = [ 309 - "brightnessctl" 310 - "set" 311 - "5%-" 312 - ]; 313 - "XF86MonBrightnessUp".action.spawn = [ 314 - "brightnessctl" 315 - "set" 316 - "+5%" 317 - ]; 318 - }; 319 - outputs = { 320 - # External monitor - primary display at position (0, 0) 321 - "DP-5" = { 322 - scale = 2.0; 323 - mode = { 324 - width = 5120; 325 - height = 2160; 326 - refresh = 120.0; 327 - }; 328 - position = { 329 - x = 0; 330 - y = 0; 331 - }; 332 - }; 333 - "DP-1" = { 334 - scale = 2.0; 335 - mode = { 336 - width = 5120; 337 - height = 2160; 338 - refresh = 120.0; 339 - }; 340 - position = { 341 - x = 0; 342 - y = 0; 343 - }; 344 - }; 345 - "DP-2" = { 346 - scale = 1.0; 347 - mode = { 348 - width = 5120; 349 - height = 2160; 350 - refresh = 120.0; 351 - }; 352 - position = { 353 - x = 0; 354 - y = 0; 355 - }; 356 - }; 357 - "DP-6" = { 358 - scale = 2.0; 359 - mode = { 360 - width = 5120; 361 - height = 2160; 362 - refresh = 120.0; 363 - }; 364 - position = { 365 - x = 0; 366 - y = 0; 367 - }; 368 - }; 369 - "DP-7" = { 370 - scale = 2.0; 371 - mode = { 372 - width = 5120; 373 - height = 2160; 374 - refresh = 120.0; 375 - }; 376 - position = { 377 - x = 0; 378 - y = 0; 379 - }; 380 - }; 381 - # Laptop display - secondary display positioned underneath 382 - "eDP-1" = { 383 - scale = 1.5; 384 - mode = { 385 - width = 2560; 386 - height = 1600; 387 - refresh = 165.0; 388 - }; 389 - position = { 390 - x = 0; 391 - y = 1080; # Position underneath the external monitor (2160 / 2 scale = 1080 logical height) 392 - }; 393 - }; 394 - }; 395 - spawn-at-startup = [ 396 - { command = [ "xwayland-satellite" ]; } 397 - { command = [ "cclipd" ]; } 398 - ]; 399 - environment = { 400 - DISPLAY = ":0"; 401 - }; 402 - }; 403 - }; 404 - 405 - # Allow unfree packages 406 - nixpkgs.config.allowUnfree = true; 407 - 408 - nixpkgs.config.permittedInsecurePackages = [ 409 - "libsoup-2.74.3" 410 - ]; 411 - 412 - # Download wallpapers at activation time (skips dead URLs gracefully) 413 - home.activation.downloadWallpapers = 414 - let 415 - wallpapers = import ./wallpapers.nix; 416 - downloads = builtins.concatStringsSep "\n" ( 417 - map (wp: '' 418 - if [ ! -f "$DIR/${wp.filename}" ]; then 419 - echo "Downloading ${wp.filename}..." 420 - ${pkgs.curl}/bin/curl -fsSL -o "$DIR/${wp.filename}" ${ 421 - builtins.replaceStrings [ "\"" ] [ "\\\"" ] wp.url 422 - } || echo "WARNING: Failed to download ${wp.filename}, skipping" 423 - fi 424 - '') wallpapers 425 - ); 426 - in 427 - config.lib.dag.entryAfter [ "writeBoundary" ] '' 428 - DIR="${config.home.homeDirectory}/Pictures/Wallpapers" 429 - mkdir -p "$DIR" 430 - ${downloads} 431 - ''; 432 - 433 - # Kaleidux wallpaper daemon config 434 - xdg.configFile."kaleidux/config.toml".text = '' 435 - [global] 436 - monitor-behavior = "independent" 437 - video-ratio = 50 438 - sorting = "loveit" 439 - transition-time = 1000 440 - 441 - [any] 442 - path = "${config.home.homeDirectory}/Pictures/Wallpapers" 443 - duration = "15m" 444 - transition = { type = "fade" } 445 - ''; 446 - 447 - # Quickshell status bar 448 - xdg.configFile."quickshell" = { 449 - source = ./quickshell; 450 - recursive = true; 451 - }; 452 - 453 - systemd.user.services.kaleidux = { 454 - Unit = { 455 - Description = "Kaleidux dynamic wallpaper daemon"; 456 - After = [ "graphical-session.target" ]; 457 - PartOf = [ "graphical-session.target" ]; 458 - }; 459 - Service = { 460 - ExecStart = "${inputs.kaleidux.packages.${pkgs.system}.default}/bin/kaleidux-daemon"; 461 - Restart = "on-failure"; 462 - RestartSec = 2; 463 - }; 464 - Install = { 465 - WantedBy = [ "graphical-session.target" ]; 466 - }; 467 - }; 468 - 469 - systemd.user.services.quickshell = { 470 - Unit = { 471 - Description = "QuickShell status bar"; 472 - After = [ "graphical-session.target" ]; 473 - PartOf = [ "graphical-session.target" ]; 474 - }; 475 - Service = { 476 - ExecStart = "${pkgs.quickshell}/bin/quickshell"; 477 - Restart = "on-failure"; 478 - RestartSec = 2; 479 - }; 480 - Install = { 481 - WantedBy = [ "graphical-session.target" ]; 482 - }; 483 - }; 484 - 485 - systemd.user.services.quickshell-reload = { 486 - Unit = { 487 - Description = "Reload QuickShell on wake or display change"; 488 - After = [ 489 - "quickshell.service" 490 - "graphical-session.target" 491 - ]; 492 - PartOf = [ "graphical-session.target" ]; 493 - }; 494 - Service = { 495 - Type = "simple"; 496 - ExecStart = "${pkgs.writeShellScript "quickshell-reload" '' 497 - LOCKFILE="/tmp/quickshell-reload.lock" 498 - 499 - do_restart() { 500 - ( 501 - ${pkgs.util-linux}/bin/flock -xn 200 || exit 0 502 - sleep 2 503 - ${pkgs.systemd}/bin/systemctl --user restart quickshell.service 504 - sleep 3 505 - ) 200>"$LOCKFILE" 506 - } 507 - 508 - # Sleep/wake monitor 509 - ${pkgs.dbus}/bin/dbus-monitor --system \ 510 - "type='signal',interface='org.freedesktop.login1.Manager',member='PrepareForSleep'" 2>/dev/null | \ 511 - while IFS= read -r line; do 512 - if [[ "$line" == *"boolean false"* ]]; then 513 - do_restart 514 - fi 515 - done & 516 - 517 - # Display hotplug monitor 518 - ${pkgs.systemd}/bin/udevadm monitor --property --subsystem-match=drm 2>/dev/null | \ 519 - while IFS= read -r line; do 520 - if [[ "$line" == *"HOTPLUG=1"* ]]; then 521 - do_restart 522 - fi 523 - done & 524 - 525 - wait 526 - ''}"; 527 - Restart = "on-failure"; 528 - RestartSec = 5; 529 - }; 530 - Install = { 531 - WantedBy = [ "graphical-session.target" ]; 532 - }; 533 - }; 534 - 535 - programs.ssh = { 536 - enable = true; 537 - enableDefaultConfig = false; 538 - matchBlocks = { 539 - "*" = { 540 - identityFile = [ 541 - "${config.home.homeDirectory}/.ssh/id_ed25519_sk_rk" 542 - "${config.home.homeDirectory}/.ssh/id_rsa.pub" 543 - ]; 544 - }; 545 - }; 546 - }; 547 - 548 - programs.awscli = { 549 - enable = true; 550 - settings = { 551 - "default" = { 552 - region = "us-east-1"; 553 - }; 554 - }; 555 - }; 556 - 557 - services.udiskie = { 558 - enable = true; 559 - tray = "never"; 560 - automount = true; 561 - }; 562 - 563 - services.mako = { 564 - enable = true; 565 - settings = { 566 - border-radius = 8; 567 - border-size = 2; 568 - padding = "12"; 569 - margin = "12"; 570 - font = "BerkeleyMono Nerd Font 11"; 571 - on-button-left = "invoke-default-action"; 572 - on-button-right = "dismiss"; 573 - }; 574 - }; 575 - 576 - catppuccin = { 577 - enable = true; 578 - flavor = "frappe"; 579 - }; 580 - 581 - programs.direnv.enable = true; 582 - 583 - programs.atuin = { 584 - enable = true; 585 - enableFishIntegration = true; 586 - daemon.enable = true; 587 - settings = { 588 - filter_mode_shell_up_key_binding = "session"; 589 - }; 590 - }; 591 - 592 - programs.zellij = { 593 - enable = true; 594 - settings = { 595 - keybinds = { 596 - unbind = [ 597 - "Ctrl q" 598 - "Ctrl o" 599 - ]; 600 - normal = { 601 - "bind \"Ctrl m\"" = { 602 - SwitchToMode = "Session"; 603 - }; 604 - }; 605 - }; 606 - load_plugins = { 607 - "\"file:~/.config/zellij/plugins/zrpc.wasm\"" = { }; 608 - }; 609 - pane_frames = false; 610 - show_startup_tips = false; 611 - ui = { 612 - pane_frames.hide_session_name = true; 613 - }; 614 - }; 615 - }; 616 - 617 - xdg.configFile."zellij/plugins/zrpc.wasm".source = "${zrpc-wasm}/zrpc.wasm"; 618 - 619 - home.file.".claude/settings.json".text = builtins.toJSON { 620 - enabledPlugins = { 621 - "rust-analyzer-lsp@claude-plugins-official" = true; 622 - "typescript-lsp@claude-plugins-official" = true; 623 - }; 624 - hooks = { 625 - Notification = [ 626 - { 627 - matcher = "permission_prompt"; 628 - hooks = [ 629 - { 630 - type = "command"; 631 - command = "claude-notify"; 632 - } 633 - ]; 634 - } 635 - ]; 636 - Stop = [ 637 - { 638 - hooks = [ 639 - { 640 - type = "command"; 641 - command = "claude-notify-clear"; 642 - } 643 - ]; 644 - } 645 - ]; 646 - }; 647 - }; 648 - 649 - xdg.configFile."zellij/layouts/split.kdl".text = '' 650 - layout { 651 - tab { 652 - pane size="50%" 653 - pane split_direction="vertical" size="50%" { 654 - pane 655 - pane 656 - } 657 - } 658 - } 659 - ''; 660 - 661 - xdg.configFile."gh-dash/config.yml".text = '' 662 - prSections: 663 - - title: My Pull Requests 664 - filters: is:open author:@me 665 - - title: Review Requested 666 - filters: is:open review-requested:@me 667 - issuesSections: 668 - - title: My Issues 669 - filters: is:open author:@me 670 - pager: 671 - diff: diffnav 672 - keybindings: 673 - prs: 674 - - key: T 675 - name: enhance 676 - command: >- 677 - zellij run -- gh enhance -R {{.RepoName}} {{.PrNumber}} 678 - ''; 679 - 680 - programs.zen-browser.enable = true; 681 - # programs.swww.enable = true; 682 - programs.zoxide = { 683 - enable = true; 684 - enableFishIntegration = true; 685 - }; 686 - 687 - programs.obs-studio = { 688 - enable = true; 689 - plugins = with pkgs.obs-studio-plugins; [ 690 - obs-backgroundremoval 691 - ]; 692 - }; 693 - 694 - # Program configurations 695 - programs.mpv = { 696 - enable = true; 697 - scripts = [ pkgs.mpvScripts.mpris ]; 698 - config.af = "loudnorm=I=-16:TP=-1.5:LRA=11"; 699 - }; 700 - xdg.configFile."youtube-tui/commands.yml".source = ./youtube-tui/commands.yml; 701 - programs.git = { 702 - enable = true; 703 - settings = { 704 - user = { 705 - name = "seanaye"; 706 - email = "hello@seanaye.ca"; 707 - }; 708 - init.defaultBranch = "main"; 709 - commit.gpgSign = true; 710 - gpg.format = "ssh"; 711 - user.signingKey = "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIOIgEteUEW06dnBHe2z8vNLwz2iMKe8bba6JgMmOUpcBAAAABHNzaDo= sean@framework16"; 712 - gpg.ssh.allowedSignersFile = "${config.home.homeDirectory}/.ssh/allowed_signers"; 713 - diff.tool = "diffnav"; 714 - difftool.prompt = false; 715 - "difftool \"diffnav\"".cmd = "diffnav \"$LOCAL\" \"$REMOTE\""; 716 - }; 717 - }; 718 - programs.jujutsu = { 719 - enable = true; 720 - settings = { 721 - user = { 722 - email = "hello@seanaye.ca"; 723 - name = "Sean Aye"; 724 - }; 725 - ui."diff-formatter" = ":git"; 726 - signing = { 727 - sign-all = true; 728 - behavior = "own"; 729 - backend = "ssh"; 730 - key = "${config.home.homeDirectory}/.ssh/id_ed25519_sk_rk"; 731 - backends.ssh.allowed-signers = "${config.home.homeDirectory}/.ssh/allowed_signers"; 732 - }; 733 - }; 734 - }; 735 - xdg.configFile."jj/conf.d/diffnav.toml".text = '' 736 - [[--scope]] 737 - --when.commands = ["diff", "show"] 738 - [--scope.ui] 739 - pager = "diffnav" 740 - ''; 741 - 742 - programs.home-manager.enable = true; 743 - 744 - programs.fish = { 745 - enable = true; 746 - shellAliases = { 747 - agenix = "agenix -i ~/.config/agenix/yubikey-identity.txt"; 748 - }; 749 - interactiveShellInit = '' 750 - set fish_greeting 751 - # Set 1Password SSH agent socket 752 - set -gx SSH_AUTH_SOCK ${config.home.homeDirectory}/.1password/agent.sock 753 - # Load 1Password CLI plugins 754 - if test -f ~/.config/op/plugins.sh 755 - source ~/.config/op/plugins.sh 756 - end 757 - # Auto-launch zellij if not already inside a session 758 - # Use systemd-run to escape niri's session scope so the server 759 - # survives logout 760 - if not set -q ZELLIJ 761 - systemd-run --user --scope zellij 762 - else 763 - fastfetch --logo small 764 - end 765 - 766 - function y 767 - set tmp (mktemp -t "yazi-cwd.XXXXXX") 768 - yazi $argv --cwd-file="$tmp" 769 - if read -z cwd < "$tmp"; and [ -n "$cwd" ]; and [ "$cwd" != "$PWD" ] 770 - builtin cd -- "$cwd" 771 - end 772 - rm -f -- "$tmp" 773 - end 774 - ''; 775 - functions = { 776 - s3edit = '' 777 - set file (basename $argv[1]) 778 - set tmpfile /tmp/$file 779 - aws s3 cp $argv[1] $tmpfile 780 - and $EDITOR $tmpfile 781 - and aws s3 cp $tmpfile $argv[1] 782 - ''; 783 - }; 784 - }; 785 - 786 - programs.starship = { 787 - enable = true; 788 - enableFishIntegration = true; 789 - }; 790 - 791 - programs.alacritty = { 792 - enable = true; 793 - settings = { 794 - terminal.shell.program = "fish"; 795 - window = { 796 - decorations = "none"; 797 - opacity = 0.9; 798 - }; 799 - font = { 800 - normal = { 801 - family = "BerkeleyMono Nerd Font"; 802 - style = "Regular"; 803 - }; 804 - size = 12.0; 805 - }; 806 - }; 807 - 808 - }; 809 - 810 - programs.helix = { 811 - enable = true; 812 - settings = { 813 - editor = { 814 - bufferline = "multiple"; 815 - file-picker = { 816 - hidden = false; 817 - git-ignore = true; 818 - }; 819 - cursor-shape = { 820 - insert = "bar"; 821 - normal = "block"; 822 - select = "underline"; 823 - }; 824 - line-number = "relative"; 825 - cursorline = true; 826 - auto-format = true; 827 - end-of-line-diagnostics = "hint"; 828 - soft-wrap = { 829 - enable = true; 830 - }; 831 - lsp = { 832 - display-inlay-hints = true; 833 - display-messages = true; 834 - display-progress-messages = true; 835 - }; 836 - inline-diagnostics = { 837 - cursor-line = "hint"; 838 - }; 839 - }; 840 - keys = { 841 - normal = { 842 - esc = [ 843 - "keep_primary_selection" 844 - "collapse_selection" 845 - ]; 846 - }; 847 - 848 - }; 849 - }; 850 - languages = { 851 - 852 - language-server.rust-analyzer = { 853 - config = { 854 - check = { 855 - command = "clippy"; 856 - }; 857 - checkOnSave = true; 858 - cargo = { 859 - allFeatures = true; 860 - }; 861 - }; 862 - }; 863 - language-server.deno-lsp = { 864 - command = "deno"; 865 - args = [ "lsp" ]; 866 - config.deno.enable = true; 867 - }; 868 - 869 - language = [ 870 - { 871 - name = "html"; 872 - formatter = { 873 - command = "prettier"; 874 - args = [ 875 - "--parser" 876 - "html" 877 - ]; 878 - }; 879 - } 880 - { 881 - name = "nix"; 882 - auto-format = true; 883 - formatter = { 884 - command = "${pkgs.nixfmt}/bin/nixfmt"; 885 - }; 886 - } 887 - { 888 - name = "kotlin"; 889 - auto-format = true; 890 - } 891 - { 892 - name = "rust"; 893 - auto-format = true; 894 - formatter = { 895 - command = "rustfmt"; 896 - args = [ 897 - "--edition" 898 - "2024" 899 - ]; 900 - }; 901 - indent = { 902 - tab-width = 4; 903 - unit = "t"; 904 - }; 905 - } 906 - { 907 - name = "astro"; 908 - auto-format = true; 909 - formatter = { 910 - command = "npx"; 911 - args = [ 912 - "prettier" 913 - "--plugin" 914 - "prettier-plugin-astro" 915 - "--parser" 916 - "astro" 917 - ]; 918 - }; 919 - } 920 - { 921 - name = "json"; 922 - auto-format = true; 923 - } 924 - { 925 - name = "just"; 926 - auto-format = true; 927 - formatter = { 928 - command = "just"; 929 - args = [ 930 - "--justfile" 931 - "/dev/stdin" 932 - "--dump" 933 - ]; 934 - }; 935 - } 936 - { 937 - name = "toml"; 938 - auto-format = true; 939 - formatter = { 940 - command = "taplo"; 941 - args = [ 942 - "format" 943 - "-" 944 - ]; 945 - }; 946 - } 947 - # { 948 - # name = "typescript"; 949 - # roots = [ 950 - # "deno.json" 951 - # "deno.jsonc" 952 - # ]; 953 - # file-types = [ 954 - # "ts" 955 - # "tsx" 956 - # ]; 957 - # auto-format = true; 958 - # language-servers = [ "deno-lsp" ]; 959 - # } 960 - ]; 961 - }; 962 - }; 963 - 964 - dconf.settings = { 965 - "org/gnome/desktop/interface" = { 966 - color-scheme = "prefer-dark"; 967 - enable-hot-corners = false; 968 - }; 969 - }; 970 - 971 - # Font rendering configuration 972 - fonts.fontconfig = { 973 - enable = true; 974 - defaultFonts = { 975 - monospace = [ "BerkeleyMono Nerd Font" ]; 976 - sansSerif = [ "Noto Sans" ]; 977 - serif = [ "Noto Serif" ]; 978 - }; 979 - }; 980 - 981 - # Cursor configuration 982 - home.pointerCursor = { 983 - name = "Adwaita"; 984 - package = pkgs.adwaita-icon-theme; 985 - size = 16; 986 - x11.enable = true; 987 - gtk.enable = true; 988 - }; 989 - 990 - # Session variables 991 - home.sessionVariables = { 992 - EDITOR = "hx"; 993 - VISUAL = "hx"; 994 - SUDO_EDITOR = "hx"; 995 - SSH_AUTH_SOCK = "${config.home.homeDirectory}/.1password/agent.sock"; 996 - SSH_ASKPASS = "${pkgs.openssh-askpass}/bin/gnome-ssh-askpass3"; 997 - SSH_ASKPASS_REQUIRE = "prefer"; 998 - }; 999 - 1000 - # SSH allowed signers for commit signature verification 1001 - home.file.".ssh/allowed_signers".text = '' 1002 - hello@seanaye.ca ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDCIqgZ7kedxo+mOW7YG73Vp3zel3h180y3GKvHtRsXfGlpIIvRDy7pgCBQ4AGXYD4y78URQmFohYSAPqCPOPaWcU2un3XG9KvCzEsHmsbskPonitUmCiKvrKkb6oW4jCBtd7AEtBn+AiajAQFtPZ7NN2Df3AmTypvR6Irg7R+nxnfc9NTIHmGvxSDyWcbb4pguL20sctUSqGL6xGh8q/bqhdOThSimM+z9bEUNxK/5rPhwkNniMrp4pJcUrUiAh5/4DiRFG6KT+oeg+/myoz/Z1sPvAs7u/8JDQI4RshRD8Hu0oTkRBN6Hxj478q2SXbeBUZlD6IdjP3RhGpmSecoDdtWqKbpuV3eVRtQtba3KL86GBeV/bugaOdJ1Aud+1SOFJreAAuvxzMMKT+cdQZk6oOPP148DA/No+mDm/2S43lcdCXh79wA6YRAmKQ8jmZxTCtPutrvuZK1rguvvUlEoG/vhdNHh7eDa4Td07V6bjCRPUl8qk/e4M0E3pwsTlZc= 1003 - hello@seanaye.ca sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAILdilHXHdAP/V8Zq28EzHKtLAMMaFPu4+1det2N50QfhAAAABHNzaDo= sean@framework16 1004 - ''; 1005 - 1006 - # Yubikey identity for agenix (not secret - just a reference to the hardware key) 1007 - home.file.".config/agenix/yubikey-identity.txt".text = '' 1008 - # Serial: 26930059, Slot: 1 1009 - # Name: agenix 1010 - # Recipient: age1yubikey1qw64ag5lzvn9ekrflu5ruj4a6ucycscl6ctk39fjzf76jptsay39z442pxv 1011 - AGE-PLUGIN-YUBIKEY-1304E5QVZZD74FKSP8FMCT 1012 - ''; 1013 - 1014 - # Same identity for sops (expects keys at this path by default) 1015 - home.file.".config/sops/age/keys.txt".text = '' 1016 - # Serial: 26930059, Slot: 1 1017 - # Name: agenix 1018 - # Recipient: age1yubikey1qw64ag5lzvn9ekrflu5ruj4a6ucycscl6ctk39fjzf76jptsay39z442pxv 1019 - AGE-PLUGIN-YUBIKEY-1304E5QVZZD74FKSP8FMCT 1020 - ''; 1021 - 1022 - # yubikey sudo access 1023 - home.file.".config/Yubico/u2f_keys".text = '' 1024 - sean:2HY//CedY0ZSrKf57lT7abxG8+8bkPyxCfp/0HMlk/il/5W8pn4R5xLiZDcJtvL85U24h9IEIxa4CS22mpaDSA==,gcD/dpLdwvUFcGGPHS4qNsarH4lOEy1AJAT7zoC6BPlFRUYEa8DpVVKFTcvT6PotjnSHSrWWGb/f3U2k2jIOIw==,es256,+presence 1025 - ''; 1026 - 1027 - # Set the state version for Home Manager 1028 - home.stateVersion = "25.05"; 1029 - }
hosts/common/quickshell/components/Anim.qml modules/_config/quickshell/components/Anim.qml
hosts/common/quickshell/components/CAnim.qml modules/_config/quickshell/components/CAnim.qml
hosts/common/quickshell/components/StateLayer.qml modules/_config/quickshell/components/StateLayer.qml
hosts/common/quickshell/components/StyledRect.qml modules/_config/quickshell/components/StyledRect.qml
hosts/common/quickshell/components/StyledText.qml modules/_config/quickshell/components/StyledText.qml
hosts/common/quickshell/components/qmldir modules/_config/quickshell/components/qmldir
hosts/common/quickshell/config/Appearance.qml modules/_config/quickshell/config/Appearance.qml
hosts/common/quickshell/config/Colours.qml modules/_config/quickshell/config/Colours.qml
hosts/common/quickshell/config/qmldir modules/_config/quickshell/config/qmldir
hosts/common/quickshell/modules/dashboard/Dashboard.qml modules/_config/quickshell/modules/dashboard/Dashboard.qml
hosts/common/quickshell/modules/dashboard/qmldir modules/_config/quickshell/modules/dashboard/qmldir
hosts/common/quickshell/modules/dashboard/tabs/DashTab.qml modules/_config/quickshell/modules/dashboard/tabs/DashTab.qml
hosts/common/quickshell/modules/dashboard/tabs/MediaTab.qml modules/_config/quickshell/modules/dashboard/tabs/MediaTab.qml
hosts/common/quickshell/modules/dashboard/tabs/PerformanceTab.qml modules/_config/quickshell/modules/dashboard/tabs/PerformanceTab.qml
hosts/common/quickshell/modules/dashboard/tabs/WeatherTab.qml modules/_config/quickshell/modules/dashboard/tabs/WeatherTab.qml
hosts/common/quickshell/modules/dashboard/tabs/qmldir modules/_config/quickshell/modules/dashboard/tabs/qmldir
hosts/common/quickshell/modules/rightpanel/RightPanel.qml modules/_config/quickshell/modules/rightpanel/RightPanel.qml
hosts/common/quickshell/modules/rightpanel/qmldir modules/_config/quickshell/modules/rightpanel/qmldir
hosts/common/quickshell/modules/sidebar/Sidebar.qml modules/_config/quickshell/modules/sidebar/Sidebar.qml
hosts/common/quickshell/modules/sidebar/components/LogoButton.qml modules/_config/quickshell/modules/sidebar/components/LogoButton.qml
hosts/common/quickshell/modules/sidebar/components/PowerButton.qml modules/_config/quickshell/modules/sidebar/components/PowerButton.qml
hosts/common/quickshell/modules/sidebar/components/SidebarIcon.qml modules/_config/quickshell/modules/sidebar/components/SidebarIcon.qml
hosts/common/quickshell/modules/sidebar/components/Tray.qml modules/_config/quickshell/modules/sidebar/components/Tray.qml
hosts/common/quickshell/modules/sidebar/components/TrayItem.qml modules/_config/quickshell/modules/sidebar/components/TrayItem.qml
hosts/common/quickshell/modules/sidebar/components/VerticalClock.qml modules/_config/quickshell/modules/sidebar/components/VerticalClock.qml
hosts/common/quickshell/modules/sidebar/components/qmldir modules/_config/quickshell/modules/sidebar/components/qmldir
hosts/common/quickshell/modules/sidebar/popouts/AudioPopout.qml modules/_config/quickshell/modules/sidebar/popouts/AudioPopout.qml
hosts/common/quickshell/modules/sidebar/popouts/DashboardPopout.qml modules/_config/quickshell/modules/sidebar/popouts/DashboardPopout.qml
hosts/common/quickshell/modules/sidebar/popouts/NetworkPopout.qml modules/_config/quickshell/modules/sidebar/popouts/NetworkPopout.qml
hosts/common/quickshell/modules/sidebar/popouts/PowerProfilePopout.qml modules/_config/quickshell/modules/sidebar/popouts/PowerProfilePopout.qml
hosts/common/quickshell/modules/sidebar/popouts/SessionPopout.qml modules/_config/quickshell/modules/sidebar/popouts/SessionPopout.qml
hosts/common/quickshell/modules/sidebar/popouts/qmldir modules/_config/quickshell/modules/sidebar/popouts/qmldir
hosts/common/quickshell/modules/sidebar/qmldir modules/_config/quickshell/modules/sidebar/qmldir
hosts/common/quickshell/services/Audio.qml modules/_config/quickshell/services/Audio.qml
hosts/common/quickshell/services/Battery.qml modules/_config/quickshell/services/Battery.qml
hosts/common/quickshell/services/Brightness.qml modules/_config/quickshell/services/Brightness.qml
hosts/common/quickshell/services/Mpris.qml modules/_config/quickshell/services/Mpris.qml
hosts/common/quickshell/services/Network.qml modules/_config/quickshell/services/Network.qml
hosts/common/quickshell/services/PowerProfile.qml modules/_config/quickshell/services/PowerProfile.qml
hosts/common/quickshell/services/Storage.qml modules/_config/quickshell/services/Storage.qml
hosts/common/quickshell/services/SystemUsage.qml modules/_config/quickshell/services/SystemUsage.qml
hosts/common/quickshell/services/Weather.qml modules/_config/quickshell/services/Weather.qml
hosts/common/quickshell/services/qmldir modules/_config/quickshell/services/qmldir
hosts/common/quickshell/shell.qml modules/_config/quickshell/shell.qml
hosts/common/wallpapers.nix modules/_data/wallpapers.nix
hosts/common/youtube-tui/commands.yml modules/_config/youtube-tui/commands.yml
-101
hosts/framework16/configuration.nix
··· 1 - # Edit this configuration file to define what should be installed on 2 - # your system. Help is available in the configuration.nix(5) man page 3 - # and in the NixOS manual (accessible by running ‘nixos-help’). 4 - 5 - { pkgs, ... }: 6 - 7 - { 8 - imports = [ 9 - # Include the results of the hardware scan. 10 - ./hardware-configuration.nix 11 - ../common/common.nix 12 - ]; 13 - 14 - boot.initrd.luks.devices."luks-ee306bda-c450-4a56-b4fe-537899e38e0d" = { 15 - device = "/dev/disk/by-uuid/ee306bda-c450-4a56-b4fe-537899e38e0d"; 16 - bypassWorkqueues = true; 17 - }; 18 - networking.hostName = "framework16"; # Define your hostname. 19 - 20 - # Reduce swap pressure to avoid thrashing through dm-crypt 21 - boot.kernel.sysctl."vm.swappiness" = 10; 22 - 23 - # Framework 16 specific configuration 24 - # Disable ABM (Active Backlight Management) to maintain color accuracy 25 - boot.kernelParams = [ "amdgpu.abmlevel=0" ]; 26 - 27 - # Enable QEMU emulation for aarch64 (for building Pi images) 28 - boot.binfmt.emulatedSystems = [ "aarch64-linux" ]; 29 - 30 - # nixos-raspberrypi binary cache (pre-built Pi 5 kernel, kodi, etc.) 31 - nix.settings.extra-substituters = [ "https://nixos-raspberrypi.cachix.org" ]; 32 - nix.settings.extra-trusted-public-keys = [ "nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI=" ]; 33 - nix.settings.trusted-users = [ "root" "sean" ]; 34 - 35 - # Use power-profiles-daemon instead of TLP (recommended for AMD Framework) 36 - services.power-profiles-daemon.enable = true; 37 - 38 - # Disable keyboard/touchpad wake from suspend (prevents wake in bags) 39 - services.udev.extraRules = '' 40 - # Framework Laptop 16 - Disable wakeup for internal keyboard to prevent wake in bags 41 - ACTION=="add", SUBSYSTEM=="usb", DRIVERS=="usb", ATTRS{idVendor}=="32ac", ATTR{power/wakeup}="disabled" 42 - ''; 43 - # networking.wireless.enable = true; # Enables wireless support via wpa_supplicant. 44 - 45 - # Configure network proxy if necessary 46 - # networking.proxy.default = "http://user:password@proxy:port/"; 47 - # networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain"; 48 - 49 - # Enable touchpad support (enabled default in most desktopManager). 50 - # services.xserver.libinput.enable = true; 51 - 52 - # List packages installed in system profile. To search, run: 53 - # $ nix search wget 54 - environment.systemPackages = with pkgs; [ 55 - brightnessctl 56 - gdm 57 - ]; 58 - 59 - # Some programs need SUID wrappers, can be configured further or are 60 - # started in user sessions. 61 - # programs.mtr.enable = true; 62 - # programs.gnupg.agent = { 63 - # enable = true; 64 - # enableSSHSupport = true; 65 - # }; 66 - 67 - # List services that you want to enable: 68 - 69 - services.openssh = { 70 - enable = true; 71 - settings = { 72 - PasswordAuthentication = false; 73 - KbdInteractiveAuthentication = false; 74 - PermitRootLogin = "no"; 75 - AllowUsers = [ "sean" ]; 76 - }; 77 - }; 78 - 79 - users.users.sean.openssh.authorizedKeys.keys = [ 80 - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDCIqgZ7kedxo+mOW7YG73Vp3zel3h180y3GKvHtRsXfGlpIIvRDy7pgCBQ4AGXYD4y78URQmFohYSAPqCPOPaWcU2un3XG9KvCzEsHmsbskPonitUmCiKvrKkb6oW4jCBtd7AEtBn+AiajAQFtPZ7NN2Df3AmTypvR6Irg7R+nxnfc9NTIHmGvxSDyWcbb4pguL20sctUSqGL6xGh8q/bqhdOThSimM+z9bEUNxK/5rPhwkNniMrp4pJcUrUiAh5/4DiRFG6KT+oeg+/myoz/Z1sPvAs7u/8JDQI4RshRD8Hu0oTkRBN6Hxj478q2SXbeBUZlD6IdjP3RhGpmSecoDdtWqKbpuV3eVRtQtba3KL86GBeV/bugaOdJ1Aud+1SOFJreAAuvxzMMKT+cdQZk6oOPP148DA/No+mDm/2S43lcdCXh79wA6YRAmKQ8jmZxTCtPutrvuZK1rguvvUlEoG/vhdNHh7eDa4Td07V6bjCRPUl8qk/e4M0E3pwsTlZc=" 81 - "no-touch-required sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAILdilHXHdAP/V8Zq28EzHKtLAMMaFPu4+1det2N50QfhAAAABHNzaDo= sean@framework16" 82 - ]; 83 - 84 - # Open ports in the firewall. 85 - # networking.firewall.allowedTCPPorts = [ 86 - # 3000 87 - # ]; 88 - networking.firewall.allowedUDPPorts = [ 89 - ]; 90 - # Or disable the firewall altogether. 91 - networking.firewall.enable = false; 92 - 93 - # This value determines the NixOS release from which the default 94 - # settings for stateful data, like file locations and database versions 95 - # on your system were taken. It‘s perfectly fine and recommended to leave 96 - # this value at the release version of the first install of this system. 97 - # Before changing this value read the documentation for this option 98 - # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). 99 - system.stateVersion = "25.05"; # Did you read the comment? 100 - 101 - }
-7
hosts/framework16/home.nix
··· 1 - { ... }: 2 - 3 - { 4 - imports = [ 5 - ../common/home.nix 6 - ]; 7 - }
-99
hosts/kodi-pi/configuration.nix
··· 1 - { pkgs, lib, ... }: 2 - 3 - { 4 - imports = [ ../pi-common/wifi.nix ]; 5 - 6 - networking.hostName = "kodi-pi"; 7 - 8 - # Use the new generational bootloader (matches nixos-raspberrypi installer) 9 - boot.loader.raspberry-pi.bootloader = "kernel"; 10 - 11 - # Force 4K30 output when EDID read fails (TV may not be ready at boot) 12 - boot.kernelParams = [ "video=HDMI-A-1:3840x2160@30D" ]; 13 - 14 - # Filesystems (matching nixos-raspberrypi installer layout) 15 - fileSystems."/" = { 16 - device = "/dev/disk/by-label/NIXOS_SD"; 17 - fsType = "ext4"; 18 - options = [ "noatime" ]; 19 - }; 20 - fileSystems."/boot/firmware" = { 21 - device = "/dev/disk/by-label/FIRMWARE"; 22 - fsType = "vfat"; 23 - options = [ "noatime" "noauto" "x-systemd.automount" "x-systemd.idle-timeout=1min" ]; 24 - }; 25 - 26 - # Graphics 27 - hardware.graphics.enable = true; 28 - 29 - # Pi firmware config.txt settings 30 - hardware.raspberry-pi.config.all.options = { 31 - gpu_mem = { enable = true; value = 256; }; 32 - hdmi_force_hotplug = { enable = true; value = true; }; 33 - }; 34 - 35 - # Audio via PipeWire (needed for Jellyfin media player) 36 - services.pipewire = { 37 - enable = true; 38 - alsa.enable = true; 39 - pulse.enable = true; 40 - }; 41 - 42 - # Jellyfin Media Player in TV mode via cage (Wayland kiosk compositor) 43 - services.cage = { 44 - enable = true; 45 - user = "kiosk"; 46 - program = "${pkgs.jellyfin-media-player}/bin/jellyfinmediaplayer --tv"; 47 - }; 48 - 49 - # Kiosk user 50 - users.users.kiosk = { 51 - isNormalUser = true; 52 - extraGroups = [ "video" "audio" "input" "render" ]; 53 - }; 54 - 55 - # Admin user 56 - users.users.sean = { 57 - isNormalUser = true; 58 - extraGroups = [ "wheel" ]; 59 - openssh.authorizedKeys.keys = [ 60 - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDCIqgZ7kedxo+mOW7YG73Vp3zel3h180y3GKvHtRsXfGlpIIvRDy7pgCBQ4AGXYD4y78URQmFohYSAPqCPOPaWcU2un3XG9KvCzEsHmsbskPonitUmCiKvrKkb6oW4jCBtd7AEtBn+AiajAQFtPZ7NN2Df3AmTypvR6Irg7R+nxnfc9NTIHmGvxSDyWcbb4pguL20sctUSqGL6xGh8q/bqhdOThSimM+z9bEUNxK/5rPhwkNniMrp4pJcUrUiAh5/4DiRFG6KT+oeg+/myoz/Z1sPvAs7u/8JDQI4RshRD8Hu0oTkRBN6Hxj478q2SXbeBUZlD6IdjP3RhGpmSecoDdtWqKbpuV3eVRtQtba3KL86GBeV/bugaOdJ1Aud+1SOFJreAAuvxzMMKT+cdQZk6oOPP148DA/No+mDm/2S43lcdCXh79wA6YRAmKQ8jmZxTCtPutrvuZK1rguvvUlEoG/vhdNHh7eDa4Td07V6bjCRPUl8qk/e4M0E3pwsTlZc=" 61 - "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIOIgEteUEW06dnBHe2z8vNLwz2iMKe8bba6JgMmOUpcBAAAABHNzaDo= sean@framework16" 62 - ]; 63 - }; 64 - 65 - # SSH 66 - services.openssh = { 67 - enable = true; 68 - settings = { 69 - PasswordAuthentication = false; 70 - PermitRootLogin = "no"; 71 - }; 72 - }; 73 - 74 - # Accept unsigned store paths from local builds 75 - nix.settings.require-sigs = false; 76 - 77 - # Binary caches 78 - nix.settings.substituters = [ 79 - "https://nixos-raspberrypi.cachix.org" 80 - "https://seanaye.cachix.org" 81 - ]; 82 - nix.settings.trusted-public-keys = [ 83 - "nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI=" 84 - "seanaye.cachix.org-1:0Qf3cZ1SwnTwqaiNGltYySksjGHnemzRPiodThnvibA=" 85 - ]; 86 - 87 - # Networking 88 - networking.useDHCP = true; 89 - security.sudo.wheelNeedsPassword = false; 90 - 91 - # Firewall 92 - networking.firewall.allowedTCPPorts = [ 93 - 22 # SSH 94 - ]; 95 - 96 - environment.systemPackages = [ pkgs.jellyfin-media-player ]; 97 - 98 - system.stateVersion = "24.11"; 99 - }
-609
hosts/mira/configuration.nix
··· 1 - { 2 - pkgs, 3 - lib, 4 - config, 5 - inputs, 6 - ... 7 - }: 8 - 9 - let 10 - jellyfinKodiSyncQueue = pkgs.fetchzip { 11 - url = "https://repo.jellyfin.org/releases/plugin/kodi-sync-queue/kodi-sync-queue_15.0.0.0.zip"; 12 - stripRoot = false; 13 - hash = "sha256-xtlG3UQ/WClt/Hvxe+oId2CeJ+PWMDXBUJXh5+k+mZQ="; 14 - }; 15 - 16 - bambu-studio = 17 - let 18 - pname = "bambu-studio"; 19 - version = "02.05.00.67"; 20 - ubuntu_version = "24.04_PR-9540"; 21 - 22 - src = pkgs.fetchurl { 23 - url = "https://github.com/bambulab/BambuStudio/releases/download/v${version}/Bambu_Studio_ubuntu-${ubuntu_version}.AppImage"; 24 - hash = "sha256-3ubZblrsOJzz1p34QiiwiagKaB7nI8xDeadFWHBkWfg="; 25 - }; 26 - 27 - appimage-contents = pkgs.appimageTools.extractType2 { 28 - inherit src pname version; 29 - }; 30 - 31 - wrapped = pkgs.appimageTools.wrapType2 { 32 - inherit src pname version; 33 - 34 - profile = '' 35 - export SSL_CERT_FILE="${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" 36 - export GIO_MODULE_DIR="${pkgs.glib-networking}/lib/gio/modules/" 37 - export __GLX_VENDOR_LIBRARY_NAME=nvidia 38 - ''; 39 - 40 - extraPkgs = 41 - p: with p; [ 42 - cacert 43 - glib 44 - glib-networking 45 - gst_all_1.gst-plugins-bad 46 - gst_all_1.gst-plugins-base 47 - gst_all_1.gst-plugins-good 48 - webkitgtk_4_1 49 - ]; 50 - }; 51 - in 52 - pkgs.runCommand "bambu-studio-${version}" { } '' 53 - mkdir -p $out/bin 54 - ln -s ${wrapped}/bin/${pname} $out/bin/bambu-studio 55 - ln -s ${wrapped}/bin/${pname} $out/bin/BambuStudio 56 - 57 - # Install desktop file with correct exec path 58 - mkdir -p $out/share/applications 59 - substitute ${appimage-contents}/BambuStudio.desktop $out/share/applications/BambuStudio.desktop \ 60 - --replace-fail "Exec=AppRun" "Exec=$out/bin/BambuStudio" 61 - 62 - # Install icons 63 - if [ -d ${appimage-contents}/usr/share/icons ]; then 64 - cp -r ${appimage-contents}/usr/share/icons $out/share/ 65 - fi 66 - ''; 67 - # Steam/gamescope calls steamos-session-select when the user presses 68 - # "Switch to Desktop". Without this script, the button does nothing. 69 - # Returning 0 lets gamescope proceed to exit, returning to greetd/regreet. 70 - steamos-session-select = pkgs.writeShellScriptBin "steamos-session-select" '' 71 - echo "Switching session to: $1" 72 - ''; 73 - 74 - # Pin gamescope to 3.14.29 — versions 3.15+ crash on NVIDIA due to 75 - # a confirmed driver bug (#5701801) in Vulkan YCbCr sampler pipeline compilation. 76 - gamescope_3_14 = pkgs.gamescope.overrideAttrs (old: { 77 - version = "3.14.29"; 78 - src = pkgs.fetchFromGitHub { 79 - owner = "ValveSoftware"; 80 - repo = "gamescope"; 81 - tag = "3.14.29"; 82 - fetchSubmodules = true; 83 - hash = "sha256-q3HEbFqUeNczKYUlou+quxawCTjpM5JNLrML84tZVYE="; 84 - }; 85 - # Drop the system-libraries fetchpatch (3rd patch) — only applies to 3.16.x 86 - patches = lib.take 2 (old.patches or []); 87 - # 3.14.29 uses pkg-config for glm/stb, not meson subprojects 88 - mesonFlags = builtins.filter 89 - (f: builtins.match ".*glm_include_dir.*" f == null 90 - && builtins.match ".*stb_include_dir.*" f == null) 91 - (old.mesonFlags or []); 92 - # Fix vendored OpenVR CMakeLists.txt requiring CMake < 3.5 (incompatible with CMake 4.x) 93 - env = (old.env or { }) // { CMAKE_POLICY_VERSION_MINIMUM = "3.5"; }; 94 - # default_extras_install.sh doesn't exist in 3.14.29; 95 - # also provide glm/stb as meson subprojects pointing to system packages 96 - postPatch = builtins.replaceStrings 97 - [ "patchShebangs default_extras_install.sh" ] [ "" ] 98 - (old.postPatch or "") 99 - + '' 100 - rm -rf subprojects/glm subprojects/glm.wrap subprojects/stb subprojects/stb.wrap 101 - mkdir -p subprojects/glm subprojects/stb 102 - 103 - cat > subprojects/glm/meson.build << 'GLMEOF' 104 - project('glm', 'cpp', version: '1.0.0') 105 - glm_dep = declare_dependency(include_directories: include_directories('${lib.getInclude pkgs.glm}/include', is_system: true)) 106 - meson.override_dependency('glm', glm_dep) 107 - GLMEOF 108 - 109 - cat > subprojects/stb/meson.build << 'STBEOF' 110 - project('stb', 'c', version: '0.0.1') 111 - stb_dep = declare_dependency(include_directories: include_directories('${lib.getInclude pkgs.stb}/include/stb', is_system: true)) 112 - meson.override_dependency('stb', stb_dep) 113 - STBEOF 114 - ''; 115 - }); 116 - in 117 - { 118 - imports = [ 119 - # Include the results of the hardware scan. 120 - ./hardware-configuration.nix 121 - ../common/common.nix 122 - ]; 123 - 124 - networking.hostName = "mira"; # Define your hostname. 125 - 126 - # Prevent NetworkManager from managing USB Ethernet 127 - networking.networkmanager.unmanaged = [ "interface-name:enp0s20f0u4u3" ]; 128 - 129 - # ZRAM swap to prevent OOM freezes 130 - zramSwap = { 131 - enable = true; 132 - memoryPercent = 50; 133 - }; 134 - 135 - # Kill runaway processes before the system locks up 136 - services.earlyoom = { 137 - enable = true; 138 - freeMemThreshold = 5; 139 - freeSwapThreshold = 5; 140 - enableNotifications = true; 141 - }; 142 - # networking.wireless.enable = true; # Enables wireless support via wpa_supplicant. 143 - 144 - # Configure network proxy if necessary 145 - # networking.proxy.default = "http://user:password@proxy:port/"; 146 - # networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain"; 147 - 148 - # this is like a network devices discovery thing 149 - services.avahi = { 150 - enable = true; 151 - nssmdns4 = true; 152 - openFirewall = true; 153 - }; 154 - 155 - services.copyparty.enable = true; 156 - 157 - services.openssh = { 158 - enable = true; 159 - settings = { 160 - PasswordAuthentication = false; 161 - KbdInteractiveAuthentication = false; 162 - PermitRootLogin = "no"; 163 - AllowUsers = [ "sean" ]; 164 - }; 165 - }; 166 - 167 - users.users.sean.openssh.authorizedKeys.keys = [ 168 - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDCIqgZ7kedxo+mOW7YG73Vp3zel3h180y3GKvHtRsXfGlpIIvRDy7pgCBQ4AGXYD4y78URQmFohYSAPqCPOPaWcU2un3XG9KvCzEsHmsbskPonitUmCiKvrKkb6oW4jCBtd7AEtBn+AiajAQFtPZ7NN2Df3AmTypvR6Irg7R+nxnfc9NTIHmGvxSDyWcbb4pguL20sctUSqGL6xGh8q/bqhdOThSimM+z9bEUNxK/5rPhwkNniMrp4pJcUrUiAh5/4DiRFG6KT+oeg+/myoz/Z1sPvAs7u/8JDQI4RshRD8Hu0oTkRBN6Hxj478q2SXbeBUZlD6IdjP3RhGpmSecoDdtWqKbpuV3eVRtQtba3KL86GBeV/bugaOdJ1Aud+1SOFJreAAuvxzMMKT+cdQZk6oOPP148DA/No+mDm/2S43lcdCXh79wA6YRAmKQ8jmZxTCtPutrvuZK1rguvvUlEoG/vhdNHh7eDa4Td07V6bjCRPUl8qk/e4M0E3pwsTlZc=" 169 - "no-touch-required sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAILdilHXHdAP/V8Zq28EzHKtLAMMaFPu4+1det2N50QfhAAAABHNzaDo= sean@framework16" 170 - ]; 171 - 172 - # List services that you want to enable: 173 - services.flaresolverr.enable = true; 174 - age.secrets.wireguard.file = ../../secrets/wireguard.age; 175 - 176 - nixarr = { 177 - enable = true; 178 - mediaDir = "/mnt/storage1/nixarr/media"; 179 - vpn = { 180 - enable = true; 181 - wgConf = config.age.secrets.wireguard.path; 182 - }; 183 - 184 - jellyfin = { 185 - enable = true; 186 - openFirewall = true; 187 - }; 188 - 189 - transmission = { 190 - enable = true; 191 - vpn.enable = true; 192 - }; 193 - sabnzbd = { 194 - enable = true; 195 - vpn.enable = true; 196 - openFirewall = true; 197 - }; 198 - 199 - prowlarr.enable = true; 200 - radarr.enable = true; 201 - sonarr.enable = true; 202 - jellyseerr = { 203 - enable = true; 204 - openFirewall = true; 205 - }; 206 - 207 - recyclarr = { 208 - enable = true; 209 - configuration = { 210 - sonarr = { 211 - series = { 212 - base_url = "http://localhost:8989"; 213 - api_key = "!env_var SONARR_API_KEY"; 214 - quality_definition = { 215 - type = "series"; 216 - }; 217 - delete_old_custom_formats = true; 218 - custom_formats = [ 219 - { 220 - trash_ids = [ 221 - "85c61753df5da1fb2aab6f2a47426b09" # BR-DISK 222 - "9c11cd3f07101cdba90a2d81cf0e56b4" # LQ 223 - ]; 224 - assign_scores_to = [ 225 - { 226 - name = "WEB-DL (1080p)"; 227 - score = -10000; 228 - } 229 - ]; 230 - } 231 - ]; 232 - }; 233 - }; 234 - radarr = { 235 - movies = { 236 - base_url = "http://localhost:7878"; 237 - api_key = "!env_var RADARR_API_KEY"; 238 - quality_definition = { 239 - type = "movie"; 240 - }; 241 - delete_old_custom_formats = true; 242 - custom_formats = [ 243 - { 244 - trash_ids = [ 245 - "570bc9ebecd92723d2d21500f4be314c" # Remaster 246 - "eca37840c13c6ef2dd0262b141a5482f" # 4K Remaster 247 - ]; 248 - assign_scores_to = [ 249 - { 250 - name = "HD Bluray + WEB"; 251 - score = 25; 252 - } 253 - ]; 254 - } 255 - ]; 256 - }; 257 - }; 258 - }; 259 - }; 260 - }; 261 - 262 - # Install Kodi Sync Queue plugin into Jellyfin 263 - systemd.services.jellyfin.serviceConfig.ExecStartPre = 264 - let 265 - pluginDir = "/data/.state/nixarr/jellyfin/data/plugins/Kodi Sync Queue/15.0.0.0"; 266 - in 267 - pkgs.writeShellScript "install-jellyfin-plugins" '' 268 - mkdir -p "${pluginDir}" 269 - cp -f ${jellyfinKodiSyncQueue}/*.dll ${jellyfinKodiSyncQueue}/meta.json "${pluginDir}/" 270 - ''; 271 - 272 - # MQTT broker for Home Assistant (Tasmota devices, Frigate) 273 - services.mosquitto = { 274 - enable = true; 275 - listeners = [ 276 - { 277 - acl = [ "pattern readwrite #" ]; 278 - omitPasswordAuth = true; 279 - settings.allow_anonymous = true; 280 - } 281 - ]; 282 - }; 283 - 284 - # Frigate NVR for camera recording and AI object detection 285 - services.frigate = { 286 - enable = true; 287 - hostname = "frigate"; 288 - settings = { 289 - mqtt = { 290 - enabled = true; 291 - host = "localhost"; 292 - port = 1883; 293 - }; 294 - 295 - detectors = { 296 - cpu = { 297 - type = "cpu"; 298 - num_threads = 2; 299 - }; 300 - }; 301 - 302 - cameras = { 303 - picam = { 304 - enabled = true; 305 - ffmpeg = { 306 - hwaccel_args = "preset-nvidia-h264"; 307 - inputs = [ 308 - { 309 - path = "rtsp://pi:8554/picam"; 310 - roles = [ 311 - "detect" 312 - "record" 313 - ]; 314 - } 315 - ]; 316 - }; 317 - detect = { 318 - enabled = true; 319 - width = 1920; 320 - height = 1080; 321 - fps = 3; 322 - }; 323 - record = { 324 - enabled = true; 325 - retain = { 326 - days = 7; 327 - mode = "active_objects"; 328 - }; 329 - }; 330 - snapshots = { 331 - enabled = true; 332 - bounding_box = true; 333 - retain = { 334 - default = 14; 335 - }; 336 - }; 337 - objects = { 338 - track = [ 339 - "person" 340 - "dog" 341 - "cat" 342 - "car" 343 - ]; 344 - }; 345 - zones = { 346 - driveway = { 347 - coordinates = "0,0.243,1,0.544,1,1,0,1,0,0.75"; 348 - objects = [ 349 - "person" 350 - "car" 351 - "dog" 352 - "cat" 353 - ]; 354 - }; 355 - }; 356 - }; 357 - 358 - pizerocam = { 359 - enabled = true; 360 - ffmpeg = { 361 - hwaccel_args = "preset-nvidia-h264"; 362 - inputs = [ 363 - { 364 - path = "rtsp://pizero:8554/pizerocam"; 365 - roles = [ "record" ]; 366 - } 367 - ]; 368 - }; 369 - detect = { 370 - enabled = false; 371 - }; 372 - record = { 373 - enabled = true; 374 - retain = { 375 - days = 2; 376 - mode = "all"; 377 - }; 378 - }; 379 - }; 380 - }; 381 - 382 - record = { 383 - enabled = true; 384 - retain = { 385 - days = 7; 386 - mode = "active_objects"; 387 - }; 388 - }; 389 - }; 390 - }; 391 - 392 - # Frigate manages many ffmpeg child processes that ignore SIGTERM; 393 - # shorten the stop timeout so it doesn't block shutdown for 90s. 394 - systemd.services.frigate.serviceConfig = { 395 - TimeoutStopSec = 5; 396 - KillMode = "mixed"; # SIGTERM to main, SIGKILL to children after timeout 397 - }; 398 - 399 - # Home Assistant service 400 - services.home-assistant = { 401 - enable = true; 402 - customComponents = with pkgs.home-assistant-custom-components; [ 403 - frigate 404 - ]; 405 - extraComponents = [ 406 - "esphome" 407 - "met" 408 - "radio_browser" 409 - "homekit" 410 - "homekit_controller" 411 - "isal" 412 - "mqtt" 413 - "tasmota" 414 - "wiz" 415 - "google_translate" # TTS - was missing gtts module 416 - "ecobee" # Was missing pyecobee module 417 - "ibeacon" # Was missing ibeacon_ble module 418 - "go2rtc" # Camera streaming 419 - "generic" # Generic camera integration 420 - ]; 421 - config = { 422 - homeassistant = { 423 - time_zone = "America/Toronto"; 424 - }; 425 - default_config = { }; 426 - zeroconf = { }; 427 - # MQTT configuration - broker must be set up via UI 428 - mqtt = { }; 429 - # Automations 430 - automation = [ 431 - { 432 - id = "1761448856909"; 433 - alias = "Lower heat at night"; 434 - trigger = [ 435 - { 436 - platform = "time"; 437 - at = "23:00:00"; 438 - } 439 - ]; 440 - condition = [ ]; 441 - action = [ 442 - { 443 - action = "climate.set_temperature"; 444 - target.device_id = "bfe22d32a4532f8ae991d6daffb48267"; 445 - data = { 446 - hvac_mode = "heat"; 447 - temperature = 18; 448 - }; 449 - } 450 - ]; 451 - mode = "single"; 452 - } 453 - { 454 - id = "1766200000001"; 455 - alias = "Raise heat in morning"; 456 - trigger = [ 457 - { 458 - platform = "time"; 459 - at = "06:00:00"; 460 - } 461 - ]; 462 - condition = [ ]; 463 - action = [ 464 - { 465 - action = "climate.set_temperature"; 466 - target.device_id = "bfe22d32a4532f8ae991d6daffb48267"; 467 - data = { 468 - hvac_mode = "heat"; 469 - temperature = 21; 470 - }; 471 - } 472 - ]; 473 - mode = "single"; 474 - } 475 - { 476 - id = "1766153071796"; 477 - alias = "Close Garage Door"; 478 - trigger = [ 479 - { 480 - platform = "device"; 481 - device_id = "d8dedd8cd0ce1488d9830c455bb0a761"; 482 - domain = "cover"; 483 - entity_id = "cf36763543169888aa106b1acb02ad72"; 484 - type = "opened"; 485 - for = { 486 - hours = 0; 487 - minutes = 10; 488 - seconds = 0; 489 - }; 490 - } 491 - ]; 492 - condition = [ ]; 493 - action = [ 494 - { 495 - device_id = "d8dedd8cd0ce1488d9830c455bb0a761"; 496 - domain = "cover"; 497 - entity_id = "cf36763543169888aa106b1acb02ad72"; 498 - type = "close"; 499 - } 500 - ]; 501 - mode = "single"; 502 - } 503 - ]; 504 - }; 505 - }; 506 - 507 - 508 - # NVIDIA needs GBM/EGL env vars for cage (wlroots) to initialize GPU on greetd restart 509 - services.greetd.settings.default_session.command = lib.mkOverride 49 510 - "${pkgs.dbus}/bin/dbus-run-session ${lib.getExe pkgs.cage} -s -d -- env GBM_BACKEND=nvidia-drm __GLX_VENDOR_LIBRARY_NAME=nvidia GDK_SCALE=2 ${lib.getExe pkgs.greetd.regreet}"; 511 - 512 - programs.steam = { 513 - enable = true; 514 - remotePlay.openFirewall = true; 515 - gamescopeSession = { 516 - enable = true; 517 - args = [ 518 - "-r" "120" 519 - "-R" "120" 520 - ]; 521 - env = { 522 - STEAM_DESKTOP_SESSION = "niri"; 523 - ENABLE_GAMESCOPE_WSI = "0"; 524 - }; 525 - }; 526 - extraCompatPackages = with pkgs; [ 527 - proton-ge-bin 528 - ]; 529 - }; 530 - 531 - programs.gamemode.enable = true; 532 - 533 - programs.gamescope = { 534 - enable = true; 535 - capSysNice = false; 536 - package = gamescope_3_14; 537 - }; 538 - 539 - # Manually add the gamescope capability wrapper without triggering 540 - # the steam module's setuid bwrap override (which zeros CapBnd 541 - # inside the FHS sandbox, preventing games from launching) 542 - security.wrappers.gamescope = { 543 - owner = "root"; 544 - group = "root"; 545 - source = "${gamescope_3_14}/bin/gamescope"; 546 - capabilities = "cap_sys_nice+pie"; 547 - }; 548 - 549 - environment.systemPackages = [ 550 - pkgs.lm_sensors 551 - bambu-studio 552 - steamos-session-select 553 - ]; 554 - 555 - # Enable the OpenSSH daemon. 556 - # services.openssh.enable = true; 557 - 558 - security.pam.loginLimits = [ 559 - { 560 - domain = "*"; 561 - type = "soft"; 562 - item = "nofile"; 563 - value = "8192"; 564 - } 565 - ]; 566 - 567 - # trmnl-rs server 568 - systemd.services.trmnl-rs = { 569 - description = "TRMNL Server"; 570 - wantedBy = [ "multi-user.target" ]; 571 - wants = [ "network-online.target" ]; 572 - after = [ 573 - "network-online.target" 574 - "nss-lookup.target" 575 - ]; 576 - serviceConfig = { 577 - ExecStart = "${inputs.trmnl-rs.packages.x86_64-linux.default}/bin/server"; 578 - Restart = "on-failure"; 579 - RestartSec = 5; 580 - DynamicUser = true; 581 - StateDirectory = "trmnl-rs"; 582 - WorkingDirectory = "/var/lib/trmnl-rs"; 583 - }; 584 - }; 585 - 586 - # Open ports in the firewall. 587 - networking.firewall.allowedTCPPorts = [ 588 - 8096 # jellyfin 589 - 5055 # jellyseer 590 - 3000 # vite dev port 591 - 1883 # MQTT for Tasmota devices 592 - 2300 # trmnl 593 - 5000 # Frigate web UI 594 - 8971 # Frigate API 595 - config.services.home-assistant.config.http.server_port 596 - ]; 597 - networking.firewall.allowedUDPPorts = [ 598 - ]; 599 - # networking.firewall.enable = false; 600 - 601 - # This value determines the NixOS release from which the default 602 - # settings for stateful data, like file locations and database versions 603 - # on your system were taken. It‘s perfectly fine and recommended to leave 604 - # this value at the release version of the first install of this system. 605 - # Before changing this value read the documentation for this option 606 - # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html). 607 - system.stateVersion = "25.05"; # Did you read the comment? 608 - 609 - }
+1 -40
hosts/mira/hardware-configuration.nix
··· 1 - # Do not modify this file! It was generated by ‘nixos-generate-config’ 1 + # Do not modify this file! It was generated by 'nixos-generate-config' 2 2 # and may be overwritten by future invocations. Please make changes 3 3 # to /etc/nixos/configuration.nix instead. 4 4 { ··· 21 21 "usb_storage" 22 22 "sd_mod" 23 23 ]; 24 - boot.initrd.kernelModules = [ 25 - "nvidia" 26 - "nvidia_modeset" 27 - "nvidia_uvm" 28 - "nvidia_drm" 29 - ]; 30 24 boot.kernelModules = [ "kvm-intel" ]; 31 25 boot.extraModulePackages = [ ]; 32 26 fileSystems."/" = { ··· 45 39 fileSystems."/mnt/storage1" = { 46 40 device = "/dev/disk/by-uuid/3bb5c3d3-f3ce-4c54-a86c-589b477eda20"; 47 41 fsType = "ext4"; 48 - # reduce write ops 49 42 options = [ "noatime" ]; 50 43 }; 51 44 fileSystems."/mnt/storage2" = { 52 45 device = "/dev/disk/by-uuid/40b9cab0-9f25-4b36-9f22-6ebea1fe6e7a"; 53 46 fsType = "ext4"; 54 - # reduce write ops 55 47 options = [ "noatime" ]; 56 48 }; 57 49 fileSystems."/mnt/ssd" = { ··· 64 56 "d /mnt/storage1 0755 sean users -" 65 57 "d /mnt/storage2 0755 sean users -" 66 58 "d /mnt/ssd 0755 sean users -" 67 - # Fix ownership on mounted filesystems (d only affects the hidden mount point) 68 59 "z /mnt/ssd 0755 sean users -" 69 60 ]; 70 61 71 62 swapDevices = [ ]; 72 63 73 - hardware.system76.kernel-modules.enable = true; 74 - hardware.system76.firmware-daemon.enable = true; 75 - hardware.system76.power-daemon.enable = true; 76 - 77 - hardware.graphics = { 78 - enable = true; 79 - enable32Bit = true; # crucial for many steam games 80 - }; 81 - 82 - # NVIDIA 4070 Ti (Ada Lovelace) 83 - services.xserver.videoDrivers = [ "nvidia" ]; 84 - hardware.nvidia = { 85 - modesetting.enable = true; 86 - powerManagement.enable = true; 87 - open = true; 88 - nvidiaSettings = true; 89 - package = config.boot.kernelPackages.nvidiaPackages.stable; 90 - }; 91 - hardware.keyboard.zsa.enable = true; 92 - 93 - # Enables DHCP on each ethernet and wireless interface. In case of scripted networking 94 - # (the default) this is the recommended approach. When using systemd-networkd it's 95 - # still possible to use this option, but it's recommended to use it in conjunction 96 - # with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`. 97 64 networking.useDHCP = lib.mkDefault true; 98 - # Disable USB Ethernet (connected via switch causing WiFi issues). 99 - # Using networkmanager unmanaged instead of networking.interfaces to 100 - # avoid generating a systemd device unit that blocks boot for 90s. 101 - networking.networkmanager.unmanaged = [ "enp0s20f0u4u3" ]; 102 - # networking.interfaces.enp3s0.useDHCP = lib.mkDefault true; 103 - # networking.interfaces.wlo1.useDHCP = lib.mkDefault true; 104 65 105 66 nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; 106 67 hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
-8
hosts/mira/home.nix
··· 1 - { ... }: 2 - 3 - { 4 - imports = [ 5 - ../common/home.nix 6 - ]; 7 - 8 - }
-344
hosts/pi-common/default.nix
··· 1 - { pkgs, lib, config, ... }: 2 - 3 - let 4 - cfg = config.pi; 5 - 6 - # Workaround from https://github.com/NixOS/nixos-hardware/blob/master/raspberry-pi/4/apply-overlays-dtmerge.nix 7 - deviceTree_overlay = _final: prev: { 8 - deviceTree = { 9 - applyOverlays = prev.callPackage ./overlays/apply-overlays-dtmerge.nix { }; 10 - compileDTS = prev.deviceTree.compileDTS; 11 - }; 12 - }; 13 - 14 - # Custom libcamera with Raspberry Pi IPA/pipeline support 15 - libcamera-rpi = pkgs.libcamera.overrideAttrs (old: { 16 - mesonFlags = (old.mesonFlags or [ ]) ++ [ 17 - "-Dipas=rpi/vc4,rpi/pisp" 18 - "-Dpipelines=rpi/vc4,rpi/pisp" 19 - ]; 20 - }); 21 - 22 - # rpicam-apps using custom libcamera 23 - rpicam-apps = pkgs.stdenv.mkDerivation rec { 24 - pname = "rpicam-apps"; 25 - version = "1.11.1"; 26 - 27 - src = pkgs.fetchFromGitHub { 28 - owner = "raspberrypi"; 29 - repo = "rpicam-apps"; 30 - rev = "v${version}"; 31 - hash = "sha256-hVoKbvWFeramPkHuibJwUgFOPS9v588+K8828a1fNnA="; 32 - }; 33 - 34 - nativeBuildInputs = with pkgs; [ 35 - meson 36 - ninja 37 - pkg-config 38 - ]; 39 - 40 - buildInputs = [ 41 - libcamera-rpi 42 - pkgs.libdrm 43 - pkgs.libexif 44 - pkgs.libjpeg 45 - pkgs.libpng 46 - pkgs.libtiff 47 - pkgs.boost 48 - pkgs.ffmpeg 49 - ]; 50 - 51 - mesonFlags = [ 52 - "-Denable_libav=enabled" 53 - "-Denable_drm=enabled" 54 - "-Denable_egl=disabled" 55 - "-Denable_qt=disabled" 56 - "-Denable_opencv=disabled" 57 - "-Denable_tflite=disabled" 58 - "-Denable_hailo=disabled" 59 - ]; 60 - 61 - meta = with lib; { 62 - description = "Raspberry Pi camera applications"; 63 - homepage = "https://github.com/raspberrypi/rpicam-apps"; 64 - license = licenses.bsd2; 65 - platforms = [ "aarch64-linux" ]; 66 - }; 67 - }; 68 - in 69 - { 70 - imports = [ ./wifi.nix ]; 71 - 72 - options.pi = { 73 - streamName = lib.mkOption { 74 - type = lib.types.str; 75 - description = "Name of the camera stream"; 76 - }; 77 - 78 - resolution = { 79 - width = lib.mkOption { 80 - type = lib.types.int; 81 - default = 1920; 82 - description = "Camera resolution width"; 83 - }; 84 - height = lib.mkOption { 85 - type = lib.types.int; 86 - default = 1080; 87 - description = "Camera resolution height"; 88 - }; 89 - }; 90 - 91 - framerate = lib.mkOption { 92 - type = lib.types.int; 93 - default = 30; 94 - description = "Camera framerate"; 95 - }; 96 - 97 - deviceTreeFilter = lib.mkOption { 98 - type = lib.types.str; 99 - description = "Device tree filter pattern"; 100 - }; 101 - 102 - deviceTreeCompatible = lib.mkOption { 103 - type = lib.types.str; 104 - description = "Device tree compatible string (e.g., brcm,bcm2711)"; 105 - }; 106 - 107 - gpuMem = lib.mkOption { 108 - type = lib.types.int; 109 - default = 256; 110 - description = "GPU memory allocation in MB"; 111 - }; 112 - 113 - flipCamera = lib.mkOption { 114 - type = lib.types.bool; 115 - default = false; 116 - description = "Flip camera image vertically and horizontally (180 degree rotation)"; 117 - }; 118 - 119 - }; 120 - 121 - config = { 122 - nix.settings.trusted-users = [ "sean" ]; 123 - 124 - # Enable DHCP for ethernet 125 - networking.useDHCP = true; 126 - # Add device tree overlay for dtmerge support 127 - nixpkgs.overlays = [ deviceTree_overlay ]; 128 - 129 - # Disable ZFS which isn't supported on Pi 130 - boot.supportedFilesystems = lib.mkForce [ "vfat" "ext4" ]; 131 - 132 - # Pi kernel lacks device-mapper, so use legacy initrd (not systemd) 133 - boot.initrd.systemd.enable = false; 134 - 135 - # Enable SSH for headless setup 136 - services.openssh = { 137 - enable = true; 138 - settings = { 139 - PasswordAuthentication = false; 140 - PermitRootLogin = "no"; 141 - }; 142 - }; 143 - 144 - # User config 145 - users.users.sean = { 146 - isNormalUser = true; 147 - extraGroups = [ "wheel" "video" ]; 148 - openssh.authorizedKeys.keys = [ 149 - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDCIqgZ7kedxo+mOW7YG73Vp3zel3h180y3GKvHtRsXfGlpIIvRDy7pgCBQ4AGXYD4y78URQmFohYSAPqCPOPaWcU2un3XG9KvCzEsHmsbskPonitUmCiKvrKkb6oW4jCBtd7AEtBn+AiajAQFtPZ7NN2Df3AmTypvR6Irg7R+nxnfc9NTIHmGvxSDyWcbb4pguL20sctUSqGL6xGh8q/bqhdOThSimM+z9bEUNxK/5rPhwkNniMrp4pJcUrUiAh5/4DiRFG6KT+oeg+/myoz/Z1sPvAs7u/8JDQI4RshRD8Hu0oTkRBN6Hxj478q2SXbeBUZlD6IdjP3RhGpmSecoDdtWqKbpuV3eVRtQtba3KL86GBeV/bugaOdJ1Aud+1SOFJreAAuvxzMMKT+cdQZk6oOPP148DA/No+mDm/2S43lcdCXh79wA6YRAmKQ8jmZxTCtPutrvuZK1rguvvUlEoG/vhdNHh7eDa4Td07V6bjCRPUl8qk/e4M0E3pwsTlZc=" 150 - "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIOIgEteUEW06dnBHe2z8vNLwz2iMKe8bba6JgMmOUpcBAAAABHNzaDo= sean@framework16" 151 - ]; 152 - }; 153 - 154 - # Allow sudo without password for wheel group 155 - security.sudo.wheelNeedsPassword = false; 156 - 157 - # go2rtc for camera streaming to Home Assistant 158 - services.go2rtc = { 159 - enable = true; 160 - settings = { 161 - ffmpeg.bin = "${pkgs.ffmpeg}/bin/ffmpeg"; 162 - streams = { 163 - "${cfg.streamName}" = "exec:${rpicam-apps}/bin/rpicam-vid -t 0 --width ${toString cfg.resolution.width} --height ${toString cfg.resolution.height} --framerate ${toString cfg.framerate} --codec h264 --inline${lib.optionalString cfg.flipCamera " --vflip --hflip"} -o -"; 164 - }; 165 - }; 166 - }; 167 - 168 - # udev rule to give video group access to DMA heap devices (required for libcamera) 169 - services.udev.extraRules = '' 170 - SUBSYSTEM=="dma_heap", GROUP="video", MODE="0660" 171 - ''; 172 - 173 - # Override go2rtc systemd service to run as root for camera access 174 - systemd.services.go2rtc.serviceConfig = { 175 - User = lib.mkForce "root"; 176 - }; 177 - 178 - # Camera and system tools 179 - environment.systemPackages = [ 180 - pkgs.ffmpeg 181 - pkgs.libraspberrypi 182 - libcamera-rpi 183 - rpicam-apps 184 - pkgs.v4l-utils 185 - ]; 186 - 187 - # Device tree configuration for Pi Camera v3 (IMX708) 188 - hardware.deviceTree.filter = cfg.deviceTreeFilter; 189 - hardware.deviceTree.overlays = [ 190 - { 191 - name = "imx708-overlay"; 192 - dtsText = '' 193 - // SPDX-License-Identifier: GPL-2.0-only 194 - // Definitions for IMX708 camera module on VC I2C bus 195 - /dts-v1/; 196 - /plugin/; 197 - 198 - /{ 199 - compatible = "${cfg.deviceTreeCompatible}"; 200 - 201 - fragment@0 { 202 - target = <&i2c0if>; 203 - __overlay__ { 204 - status = "okay"; 205 - }; 206 - }; 207 - 208 - clk_frag: fragment@1 { 209 - target = <&cam1_clk>; 210 - __overlay__ { 211 - status = "okay"; 212 - clock-frequency = <24000000>; 213 - }; 214 - }; 215 - 216 - fragment@2 { 217 - target = <&i2c0mux>; 218 - __overlay__ { 219 - status = "okay"; 220 - }; 221 - }; 222 - 223 - reg_frag: fragment@3 { 224 - target = <&cam1_reg>; 225 - cam_reg: __overlay__ { 226 - startup-delay-us = <70000>; 227 - off-on-delay-us = <30000>; 228 - regulator-min-microvolt = <2700000>; 229 - regulator-max-microvolt = <2700000>; 230 - }; 231 - }; 232 - 233 - i2c_frag: fragment@100 { 234 - target = <&i2c_csi_dsi>; 235 - __overlay__ { 236 - #address-cells = <1>; 237 - #size-cells = <0>; 238 - status = "okay"; 239 - 240 - // IMX708 sensor configuration (from imx708.dtsi) 241 - cam_node: imx708@1a { 242 - compatible = "sony,imx708"; 243 - reg = <0x1a>; 244 - status = "okay"; 245 - 246 - clocks = <&cam1_clk>; 247 - clock-names = "inclk"; 248 - 249 - vana1-supply = <&cam1_reg>; 250 - vana2-supply = <&cam_dummy_reg>; 251 - vdig-supply = <&cam_dummy_reg>; 252 - vddl-supply = <&cam_dummy_reg>; 253 - 254 - rotation = <180>; 255 - orientation = <2>; 256 - 257 - port { 258 - cam_endpoint: endpoint { 259 - clock-lanes = <0>; 260 - data-lanes = <1 2>; 261 - clock-noncontinuous; 262 - link-frequencies = 263 - /bits/ 64 <450000000>; 264 - }; 265 - }; 266 - }; 267 - 268 - // VCM (autofocus motor) configuration 269 - vcm_node: dw9817@c { 270 - compatible = "dongwoon,dw9817-vcm"; 271 - reg = <0x0c>; 272 - status = "okay"; 273 - VDD-supply = <&cam1_reg>; 274 - }; 275 - }; 276 - }; 277 - 278 - csi_frag: fragment@101 { 279 - target = <&csi1>; 280 - csi: __overlay__ { 281 - status = "okay"; 282 - brcm,media-controller; 283 - 284 - port { 285 - csi_ep: endpoint { 286 - remote-endpoint = <&cam_endpoint>; 287 - clock-lanes = <0>; 288 - data-lanes = <1 2>; 289 - clock-noncontinuous; 290 - }; 291 - }; 292 - }; 293 - }; 294 - 295 - __overrides__ { 296 - rotation = <&cam_node>,"rotation:0"; 297 - orientation = <&cam_node>,"orientation:0"; 298 - media-controller = <&csi>,"brcm,media-controller?"; 299 - cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, 300 - <&csi_frag>, "target:0=",<&csi0>, 301 - <&clk_frag>, "target:0=",<&cam0_clk>, 302 - <&reg_frag>, "target:0=",<&cam0_reg>, 303 - <&cam_node>, "clocks:0=",<&cam0_clk>, 304 - <&cam_node>, "vana1-supply:0=",<&cam0_reg>, 305 - <&vcm_node>, "VDD-supply:0=",<&cam0_reg>; 306 - vcm = <&vcm_node>, "status", 307 - <0>, "=4"; 308 - link-frequency = <&cam_endpoint>,"link-frequencies#0"; 309 - }; 310 - }; 311 - 312 - &cam_endpoint { 313 - remote-endpoint = <&csi_ep>; 314 - }; 315 - ''; 316 - } 317 - ]; 318 - 319 - # Raspberry Pi firmware for camera 320 - hardware.enableRedistributableFirmware = true; 321 - 322 - # Add camera config and overlays to firmware partition 323 - sdImage.populateFirmwareCommands = lib.mkAfter '' 324 - chmod u+w ./firmware/config.txt 325 - cat >> ./firmware/config.txt << EOF 326 - 327 - # Camera support - Pi Camera v3 (IMX708) 328 - gpu_mem=${toString cfg.gpuMem} 329 - EOF 330 - 331 - # Copy device tree overlays for camera auto-detect 332 - if [ -d ${pkgs.raspberrypifw}/share/raspberrypi/boot/overlays ]; then 333 - cp -r ${pkgs.raspberrypifw}/share/raspberrypi/boot/overlays ./firmware/ 334 - fi 335 - ''; 336 - 337 - # Firewall 338 - networking.firewall.allowedTCPPorts = [ 339 - 22 # SSH 340 - 1984 # go2rtc API 341 - 8554 # RTSP 342 - ]; 343 - }; 344 - }
-43
hosts/pi-common/wifi.nix
··· 1 - { config, pkgs, ... }: 2 - 3 - { 4 - # Pre-generated SSH host key for agenix decryption (shared across all Pis) 5 - services.openssh.hostKeys = [ 6 - { 7 - path = "/etc/ssh/ssh_host_ed25519_key"; 8 - type = "ed25519"; 9 - } 10 - ]; 11 - 12 - # Agenix configuration - reference the host key at its runtime path 13 - # (the key lives on disk, never passes through the world-readable Nix store) 14 - age.identityPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; 15 - age.secrets.wifi = { 16 - file = ../../secrets/wifi.age; 17 - mode = "0444"; 18 - }; 19 - 20 - # WiFi configuration using wpa_supplicant with agenix credentials 21 - networking.wireless = { 22 - enable = true; 23 - secretsFile = config.age.secrets.wifi.path; 24 - networks."GL-MT6000-6a6" = { 25 - pskRaw = "ext:WIFI_PSK"; 26 - extraConfig = '' 27 - freq_list=5180 5200 5220 5240 5260 5280 5300 5320 5500 5520 5540 5560 5580 5600 5620 5640 5660 5680 5700 5720 5745 5765 5785 5805 5825 28 - ''; 29 - }; 30 - }; 31 - 32 - # Disable WiFi power save to prevent brcmfmac firmware hangs 33 - systemd.services.wifi-powersave-off = { 34 - description = "Disable WiFi power save"; 35 - after = [ "network.target" ]; 36 - wantedBy = [ "multi-user.target" ]; 37 - serviceConfig = { 38 - Type = "oneshot"; 39 - ExecStart = "${pkgs.iw}/bin/iw dev wlan0 set power_save off"; 40 - RemainAfterExit = true; 41 - }; 42 - }; 43 - }
-19
hosts/pi/configuration.nix
··· 1 - { pkgs, lib, config, ... }: 2 - 3 - { 4 - imports = [ ../pi-common ]; 5 - 6 - networking.hostName = "pi"; 7 - 8 - # Pi 4 specific settings 9 - pi = { 10 - streamName = "picam"; 11 - resolution = { width = 1920; height = 1080; }; 12 - framerate = 30; 13 - deviceTreeFilter = "bcm2711-rpi-4*.dtb"; 14 - deviceTreeCompatible = "brcm,bcm2711"; 15 - gpuMem = 256; 16 - }; 17 - 18 - system.stateVersion = "24.11"; 19 - }
-33
hosts/pizero/configuration.nix
··· 1 - { pkgs, lib, config, ... }: 2 - 3 - { 4 - imports = [ ../pi-common ]; 5 - 6 - networking.hostName = "pizero"; 7 - 8 - # Pi Zero 2 W specific settings (512MB RAM, BCM2837 SoC) 9 - pi = { 10 - streamName = "pizerocam"; 11 - resolution = { width = 1280; height = 720; }; 12 - framerate = 15; 13 - deviceTreeFilter = "bcm2837-rpi-zero-2-w.dtb"; 14 - deviceTreeCompatible = "brcm,bcm2837"; 15 - gpuMem = 128; 16 - flipCamera = true; 17 - }; 18 - 19 - # Use RPi kernel which includes IMX708 camera driver (not in default kernel) 20 - boot.kernelPackages = pkgs.linuxPackages_rpi3; 21 - 22 - # Mainline kernel's BCM2837 DTB lacks RPi-specific labels (i2c0if, cam1_clk, etc.) 23 - # needed by the camera overlay. Disable NixOS DTB so U-Boot uses the firmware's 24 - # DTB (which has all RPi labels and the imx708 overlay applied via config.txt). 25 - hardware.deviceTree.enable = lib.mkForce false; 26 - sdImage.populateFirmwareCommands = lib.mkAfter '' 27 - chmod u+w ./firmware/config.txt 28 - echo "dtoverlay=imx708" >> ./firmware/config.txt 29 - echo "camera_auto_detect=1" >> ./firmware/config.txt 30 - ''; 31 - 32 - system.stateVersion = "24.11"; 33 - }
+141
modules/apps.nix
··· 1 + { inputs, ... }: 2 + 3 + { 4 + flake.modules.homeManager.apps = 5 + { pkgs, ... }: 6 + { 7 + home.packages = with pkgs; [ 8 + # Browsers 9 + chromium 10 + 11 + # Communication 12 + (element-desktop.override { 13 + commandLineArgs = "--password-store=gnome-libsecret"; 14 + }) 15 + signal-desktop 16 + discord 17 + 18 + # Media 19 + youtube-tui 20 + yt-dlp 21 + darktable 22 + loupe 23 + glycin-loaders 24 + 25 + # Dev tools 26 + git 27 + jujutsu 28 + htop 29 + iotop 30 + ncdu 31 + atac 32 + trippy 33 + just 34 + docker-compose 35 + flyctl 36 + rainfrog 37 + sqlitebrowser 38 + 39 + # System tools 40 + nautilus 41 + gnome-characters 42 + sendme 43 + desktop-file-utils 44 + gnome-network-displays 45 + fastfetch 46 + mangohud 47 + prismlauncher 48 + libnotify 49 + 50 + # Claude Code 51 + claude-code 52 + (pkgs.writeShellScriptBin "claude-notify" '' 53 + PANE_ID="$ZELLIJ_PANE_ID" 54 + SESSION="$ZELLIJ_SESSION_NAME" 55 + ID_FILE="/tmp/claude-notify-''${PANE_ID}" 56 + ( 57 + { 58 + read -r NOTIFY_ID 59 + echo "$NOTIFY_ID" > "$ID_FILE" 60 + while read -r line; do 61 + if [ "$line" = "default" ]; then 62 + WIN_ID=$(niri msg windows | ${pkgs.gnugrep}/bin/grep -B1 "$SESSION" | ${pkgs.gnugrep}/bin/grep -oP '(?<=Window ID )\d+') 63 + if [ -n "$WIN_ID" ]; then 64 + niri msg action focus-window --id "$WIN_ID" 65 + fi 66 + zjctl pane focus --pane "id:terminal:$PANE_ID" 67 + fi 68 + done 69 + } < <(${pkgs.libnotify}/bin/notify-send "Claude Code" "Waiting for your approval" \ 70 + --app-name=claude-code \ 71 + --action=default=Open \ 72 + --print-id \ 73 + --wait) 74 + rm -f "$ID_FILE" 75 + ) & 76 + '') 77 + (pkgs.writeShellScriptBin "claude-notify-clear" '' 78 + PANE_ID="$ZELLIJ_PANE_ID" 79 + ID_FILE="/tmp/claude-notify-''${PANE_ID}" 80 + if [ -f "$ID_FILE" ]; then 81 + NOTIFY_ID=$(cat "$ID_FILE") 82 + ${pkgs.dbus}/bin/dbus-send --session \ 83 + --print-reply \ 84 + --dest=org.freedesktop.Notifications \ 85 + --type=method_call \ 86 + /org/freedesktop/Notifications \ 87 + org.freedesktop.Notifications.CloseNotification \ 88 + "uint32:$NOTIFY_ID" >/dev/null 2>&1 89 + rm -f "$ID_FILE" 90 + fi 91 + '') 92 + ]; 93 + 94 + home.file.".claude/settings.json".text = builtins.toJSON { 95 + enabledPlugins = { 96 + "rust-analyzer-lsp@claude-plugins-official" = true; 97 + "typescript-lsp@claude-plugins-official" = true; 98 + }; 99 + hooks = { 100 + Notification = [ 101 + { 102 + matcher = "permission_prompt"; 103 + hooks = [ 104 + { 105 + type = "command"; 106 + command = "claude-notify"; 107 + } 108 + ]; 109 + } 110 + ]; 111 + Stop = [ 112 + { 113 + hooks = [ 114 + { 115 + type = "command"; 116 + command = "claude-notify-clear"; 117 + } 118 + ]; 119 + } 120 + ]; 121 + }; 122 + }; 123 + 124 + programs.zen-browser.enable = true; 125 + 126 + programs.obs-studio = { 127 + enable = true; 128 + plugins = with pkgs.obs-studio-plugins; [ 129 + obs-backgroundremoval 130 + ]; 131 + }; 132 + 133 + programs.mpv = { 134 + enable = true; 135 + scripts = [ pkgs.mpvScripts.mpris ]; 136 + config.af = "loudnorm=I=-16:TP=-1.5:LRA=11"; 137 + }; 138 + 139 + xdg.configFile."youtube-tui/commands.yml".source = ./_config/youtube-tui/commands.yml; 140 + }; 141 + }
+70
modules/base.nix
··· 1 + { ... }: { 2 + flake.modules.nixos.base = 3 + { pkgs, ... }: 4 + { 5 + # Bluetooth 6 + hardware.bluetooth.enable = true; 7 + hardware.bluetooth.powerOnBoot = true; 8 + services.blueman.enable = true; 9 + 10 + # Bootloader 11 + boot.loader.systemd-boot.enable = true; 12 + boot.loader.systemd-boot.configurationLimit = 10; 13 + boot.loader.efi.canTouchEfiVariables = true; 14 + 15 + # Firmware updates 16 + services.fwupd.enable = true; 17 + 18 + # Latest kernel 19 + boot.kernelPackages = pkgs.linuxPackages_latest; 20 + boot.kernel.sysctl."kernel.task_delayacct" = 1; 21 + 22 + # Networking 23 + networking.networkmanager.enable = true; 24 + systemd.services.NetworkManager-wait-online.enable = false; 25 + 26 + # Timezone & locale 27 + time.timeZone = "America/Toronto"; 28 + i18n.defaultLocale = "en_US.UTF-8"; 29 + i18n.extraLocaleSettings = { 30 + LC_ADDRESS = "en_US.UTF-8"; 31 + LC_IDENTIFICATION = "en_US.UTF-8"; 32 + LC_MEASUREMENT = "en_US.UTF-8"; 33 + LC_MONETARY = "en_US.UTF-8"; 34 + LC_NAME = "en_US.UTF-8"; 35 + LC_NUMERIC = "en_US.UTF-8"; 36 + LC_PAPER = "en_US.UTF-8"; 37 + LC_TELEPHONE = "en_US.UTF-8"; 38 + LC_TIME = "en_US.UTF-8"; 39 + }; 40 + 41 + # Printing 42 + services.printing.enable = true; 43 + 44 + # Audio 45 + security.rtkit.enable = true; 46 + services.pipewire = { 47 + enable = true; 48 + alsa.enable = true; 49 + alsa.support32Bit = true; 50 + pulse.enable = true; 51 + }; 52 + 53 + # Services 54 + services.udisks2.enable = true; 55 + services.tailscale.enable = true; 56 + 57 + # Docker 58 + virtualisation.docker.enable = true; 59 + 60 + # System packages 61 + environment.systemPackages = with pkgs; [ 62 + wl-clipboard 63 + ]; 64 + environment.variables = { 65 + EDITOR = "hx"; 66 + VISUAL = "hx"; 67 + SUDO_EDITOR = "hx"; 68 + }; 69 + }; 70 + }
+521
modules/desktop.nix
··· 1 + { inputs, ... }: 2 + 3 + { 4 + flake.modules.nixos.desktop = 5 + { pkgs, lib, ... }: 6 + { 7 + programs.niri.enable = true; 8 + 9 + catppuccin = { 10 + enable = true; 11 + flavor = "frappe"; 12 + }; 13 + 14 + programs.regreet = { 15 + enable = true; 16 + cageArgs = [ 17 + "-s" 18 + "-d" 19 + ]; 20 + settings.GTK.application_prefer_dark_theme = true; 21 + theme = { 22 + package = pkgs.catppuccin-gtk.override { 23 + variant = "frappe"; 24 + accents = [ "lavender" ]; 25 + size = "standard"; 26 + }; 27 + name = "catppuccin-frappe-lavender-standard"; 28 + }; 29 + iconTheme = { 30 + package = pkgs.catppuccin-papirus-folders.override { 31 + flavor = "frappe"; 32 + accent = "lavender"; 33 + }; 34 + name = "Papirus-Dark"; 35 + }; 36 + cursorTheme = { 37 + package = pkgs.catppuccin-cursors.frappeDark; 38 + name = "catppuccin-frappe-dark-cursors"; 39 + }; 40 + }; 41 + 42 + # Scale regreet's greeter for HiDPI displays 43 + services.greetd.settings.default_session.command = lib.mkForce 44 + "${pkgs.dbus}/bin/dbus-run-session ${lib.getExe pkgs.cage} -s -d -- env GDK_SCALE=2 ${lib.getExe pkgs.greetd.regreet}"; 45 + 46 + # ZSA Keyboard udev rules for Oryx web flashing and live training 47 + services.udev.extraRules = '' 48 + # Rules for Oryx web flashing and live training 49 + KERNEL=="hidraw*", ATTRS{idVendor}=="16c0", MODE="0664", GROUP="plugdev" 50 + KERNEL=="hidraw*", ATTRS{idVendor}=="3297", MODE="0664", GROUP="plugdev" 51 + 52 + # Legacy rules for live training over webusb (Not needed for firmware v21+) 53 + # Rule for all ZSA keyboards 54 + SUBSYSTEM=="usb", ATTR{idVendor}=="3297", GROUP="plugdev" 55 + # Rule for the Moonlander 56 + SUBSYSTEM=="usb", ATTR{idVendor}=="3297", ATTR{idProduct}=="1969", GROUP="plugdev" 57 + # Rule for the Ergodox EZ 58 + SUBSYSTEM=="usb", ATTR{idVendor}=="feed", ATTR{idProduct}=="1307", GROUP="plugdev" 59 + # Rule for the Planck EZ 60 + SUBSYSTEM=="usb", ATTR{idVendor}=="feed", ATTR{idProduct}=="6060", GROUP="plugdev" 61 + 62 + # Wally Flashing rules for the Ergodox EZ 63 + ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789B]?", ENV{ID_MM_DEVICE_IGNORE}="1" 64 + ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789A]?", ENV{MTP_NO_PROBE}="1" 65 + SUBSYSTEMS=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789ABCD]?", MODE:="0666" 66 + KERNEL=="ttyACM*", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789B]?", MODE:="0666" 67 + 68 + # Keymapp / Wally Flashing rules for the Moonlander and Planck EZ 69 + SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="df11", MODE:="0666", SYMLINK+="stm32_dfu" 70 + # Keymapp Flashing rules for the Voyager 71 + SUBSYSTEMS=="usb", ATTRS{idVendor}=="3297", MODE:="0666", SYMLINK+="ignition_dfu" 72 + # USB serial device access via Chrome/Chromium 73 + KERNEL=="ttyUSB[0-9]*", MODE:="0666", GROUP="dialout" 74 + KERNEL=="ttyACM[0-9]*", MODE:="0666", GROUP="dialout" 75 + ''; 76 + }; 77 + 78 + flake.modules.homeManager.desktop = 79 + { pkgs, config, ... }: 80 + let 81 + wallpapers = import ./_data/wallpapers.nix; 82 + downloads = builtins.concatStringsSep "\n" ( 83 + map (wp: '' 84 + if [ ! -f "$DIR/${wp.filename}" ]; then 85 + echo "Downloading ${wp.filename}..." 86 + ${pkgs.curl}/bin/curl -fsSL -o "$DIR/${wp.filename}" ${ 87 + builtins.replaceStrings [ "\"" ] [ "\\\"" ] wp.url 88 + } || echo "WARNING: Failed to download ${wp.filename}, skipping" 89 + fi 90 + '') wallpapers 91 + ); 92 + in 93 + { 94 + home.packages = with pkgs; [ 95 + inputs.fsel.packages.${pkgs.system}.default 96 + inputs.mako-tui.packages.${pkgs.system}.default 97 + bemoji 98 + networkmanager_dmenu 99 + quickshell 100 + inputs.kaleidux.packages.${pkgs.system}.default 101 + (import ../packages/cclip.nix { inherit pkgs; }) 102 + pavucontrol 103 + playerctl 104 + xwayland-satellite 105 + ]; 106 + 107 + programs.niri = { 108 + enable = true; 109 + settings = { 110 + window-rules = [ 111 + { 112 + geometry-corner-radius = { 113 + top-left = 5.0; 114 + top-right = 5.0; 115 + bottom-left = 5.0; 116 + bottom-right = 5.0; 117 + }; 118 + clip-to-geometry = true; 119 + draw-border-with-background = false; 120 + } 121 + { 122 + matches = [ { app-id = "^steam_app_"; } ]; 123 + border.enable = false; 124 + open-fullscreen = true; 125 + } 126 + { 127 + matches = [ { app-id = "^fsel$"; } ]; 128 + open-floating = true; 129 + default-column-width.fixed = 800; 130 + default-window-height.fixed = 500; 131 + } 132 + { 133 + matches = [ { app-id = "^mako-tui$"; } ]; 134 + open-floating = true; 135 + default-column-width.fixed = 800; 136 + default-window-height.fixed = 500; 137 + } 138 + ]; 139 + debug = { 140 + honor-xdg-activation-with-invalid-serial = { }; 141 + }; 142 + layout = { 143 + focus-ring = { 144 + width = 2; 145 + active.color = "#8caaee"; 146 + inactive.color = "#414559"; 147 + }; 148 + struts = { 149 + top = -6; 150 + bottom = -6; 151 + left = 0; 152 + right = 0; 153 + }; 154 + gaps = 8; 155 + }; 156 + gestures = { 157 + hot-corners = { 158 + enable = false; 159 + }; 160 + }; 161 + binds = { 162 + "Mod+d".action.spawn = [ 163 + "alacritty" 164 + "--class" 165 + "fsel" 166 + "-e" 167 + "fsel" 168 + "--detach" 169 + ]; 170 + "Mod+c".action.spawn = [ 171 + "alacritty" 172 + "--class" 173 + "fsel" 174 + "-e" 175 + "fsel" 176 + "--cclip" 177 + ]; 178 + "Mod+e".action.spawn = "bemoji"; 179 + "Mod+n".action.spawn = [ 180 + "alacritty" 181 + "--class" 182 + "mako-tui" 183 + "-e" 184 + "mako-tui" 185 + ]; 186 + "Mod+a".action.spawn = "alacritty"; 187 + "Mod+h".action = { 188 + focus-column-left = { }; 189 + }; 190 + "Mod+j".action = { 191 + focus-workspace-down = { }; 192 + }; 193 + "Mod+k".action = { 194 + focus-workspace-up = { }; 195 + }; 196 + "Mod+l".action = { 197 + focus-column-right = { }; 198 + }; 199 + "Mod+Shift+h".action = { 200 + move-column-left = { }; 201 + }; 202 + "Mod+Shift+j".action = { 203 + move-window-down-or-to-workspace-down = { }; 204 + }; 205 + "Mod+Shift+k".action = { 206 + move-window-up-or-to-workspace-up = { }; 207 + }; 208 + "Mod+Shift+l".action = { 209 + move-column-right = { }; 210 + }; 211 + "Mod+Down".action = { 212 + move-workspace-down = { }; 213 + }; 214 + "Mod+Up".action = { 215 + move-workspace-up = { }; 216 + }; 217 + "Mod+p".action = { 218 + show-hotkey-overlay = { }; 219 + }; 220 + "Mod+o".action = { 221 + toggle-overview = { }; 222 + }; 223 + "Mod+q".action = { 224 + close-window = { }; 225 + }; 226 + "Mod+f".action = { 227 + toggle-window-floating = { }; 228 + }; 229 + "Mod+Shift+f".action = { 230 + switch-focus-between-floating-and-tiling = { }; 231 + }; 232 + "Mod+m".action = { 233 + fullscreen-window = { }; 234 + }; 235 + "Mod+s".action = { 236 + screenshot = { 237 + show-pointer = true; 238 + }; 239 + }; 240 + "Mod+1".action = { 241 + set-column-width = "100%"; 242 + }; 243 + "Mod+2".action = { 244 + set-column-width = "50%"; 245 + }; 246 + "Mod+Minus".action = { 247 + set-column-width = "-10%"; 248 + }; 249 + "Mod+Equal".action = { 250 + set-column-width = "+10%"; 251 + }; 252 + "Mod+Shift+q".action = { 253 + quit = { }; 254 + }; 255 + "Mod+Shift+r".action.spawn = [ 256 + "systemctl" 257 + "--user" 258 + "restart" 259 + "quickshell.service" 260 + ]; 261 + "XF86AudioPlay".action.spawn = [ 262 + "playerctl" 263 + "play-pause" 264 + ]; 265 + "XF86AudioStop".action.spawn = [ 266 + "playerctl" 267 + "stop" 268 + ]; 269 + "XF86AudioNext".action.spawn = [ 270 + "playerctl" 271 + "next" 272 + ]; 273 + "XF86AudioPrev".action.spawn = [ 274 + "playerctl" 275 + "previous" 276 + ]; 277 + "XF86MonBrightnessDown".action.spawn = [ 278 + "brightnessctl" 279 + "set" 280 + "5%-" 281 + ]; 282 + "XF86MonBrightnessUp".action.spawn = [ 283 + "brightnessctl" 284 + "set" 285 + "+5%" 286 + ]; 287 + }; 288 + outputs = { 289 + "DP-5" = { 290 + scale = 2.0; 291 + mode = { 292 + width = 5120; 293 + height = 2160; 294 + refresh = 120.0; 295 + }; 296 + position = { 297 + x = 0; 298 + y = 0; 299 + }; 300 + }; 301 + "DP-1" = { 302 + scale = 2.0; 303 + mode = { 304 + width = 5120; 305 + height = 2160; 306 + refresh = 120.0; 307 + }; 308 + position = { 309 + x = 0; 310 + y = 0; 311 + }; 312 + }; 313 + "DP-2" = { 314 + scale = 1.0; 315 + mode = { 316 + width = 5120; 317 + height = 2160; 318 + refresh = 120.0; 319 + }; 320 + position = { 321 + x = 0; 322 + y = 0; 323 + }; 324 + }; 325 + "DP-6" = { 326 + scale = 2.0; 327 + mode = { 328 + width = 5120; 329 + height = 2160; 330 + refresh = 120.0; 331 + }; 332 + position = { 333 + x = 0; 334 + y = 0; 335 + }; 336 + }; 337 + "DP-7" = { 338 + scale = 2.0; 339 + mode = { 340 + width = 5120; 341 + height = 2160; 342 + refresh = 120.0; 343 + }; 344 + position = { 345 + x = 0; 346 + y = 0; 347 + }; 348 + }; 349 + "eDP-1" = { 350 + scale = 1.5; 351 + mode = { 352 + width = 2560; 353 + height = 1600; 354 + refresh = 165.0; 355 + }; 356 + position = { 357 + x = 0; 358 + y = 1080; 359 + }; 360 + }; 361 + }; 362 + spawn-at-startup = [ 363 + { command = [ "xwayland-satellite" ]; } 364 + { command = [ "cclipd" ]; } 365 + ]; 366 + environment = { 367 + DISPLAY = ":0"; 368 + }; 369 + }; 370 + }; 371 + 372 + catppuccin = { 373 + enable = true; 374 + flavor = "frappe"; 375 + }; 376 + 377 + # Wallpaper downloads 378 + home.activation.downloadWallpapers = 379 + config.lib.dag.entryAfter [ "writeBoundary" ] '' 380 + DIR="${config.home.homeDirectory}/Pictures/Wallpapers" 381 + mkdir -p "$DIR" 382 + ${downloads} 383 + ''; 384 + 385 + # Kaleidux wallpaper daemon config 386 + xdg.configFile."kaleidux/config.toml".text = '' 387 + [global] 388 + monitor-behavior = "independent" 389 + video-ratio = 50 390 + sorting = "loveit" 391 + transition-time = 1000 392 + 393 + [any] 394 + path = "${config.home.homeDirectory}/Pictures/Wallpapers" 395 + duration = "15m" 396 + transition = { type = "fade" } 397 + ''; 398 + 399 + # Quickshell status bar 400 + xdg.configFile."quickshell" = { 401 + source = ./_config/quickshell; 402 + recursive = true; 403 + }; 404 + 405 + systemd.user.services.kaleidux = { 406 + Unit = { 407 + Description = "Kaleidux dynamic wallpaper daemon"; 408 + After = [ "graphical-session.target" ]; 409 + PartOf = [ "graphical-session.target" ]; 410 + }; 411 + Service = { 412 + ExecStart = "${inputs.kaleidux.packages.${pkgs.system}.default}/bin/kaleidux-daemon"; 413 + Restart = "on-failure"; 414 + RestartSec = 2; 415 + }; 416 + Install = { 417 + WantedBy = [ "graphical-session.target" ]; 418 + }; 419 + }; 420 + 421 + systemd.user.services.quickshell = { 422 + Unit = { 423 + Description = "QuickShell status bar"; 424 + After = [ "graphical-session.target" ]; 425 + PartOf = [ "graphical-session.target" ]; 426 + }; 427 + Service = { 428 + ExecStart = "${pkgs.quickshell}/bin/quickshell"; 429 + Restart = "on-failure"; 430 + RestartSec = 2; 431 + }; 432 + Install = { 433 + WantedBy = [ "graphical-session.target" ]; 434 + }; 435 + }; 436 + 437 + systemd.user.services.quickshell-reload = { 438 + Unit = { 439 + Description = "Reload QuickShell on wake or display change"; 440 + After = [ 441 + "quickshell.service" 442 + "graphical-session.target" 443 + ]; 444 + PartOf = [ "graphical-session.target" ]; 445 + }; 446 + Service = { 447 + Type = "simple"; 448 + ExecStart = "${pkgs.writeShellScript "quickshell-reload" '' 449 + LOCKFILE="/tmp/quickshell-reload.lock" 450 + 451 + do_restart() { 452 + ( 453 + ${pkgs.util-linux}/bin/flock -xn 200 || exit 0 454 + sleep 2 455 + ${pkgs.systemd}/bin/systemctl --user restart quickshell.service 456 + sleep 3 457 + ) 200>"$LOCKFILE" 458 + } 459 + 460 + # Sleep/wake monitor 461 + ${pkgs.dbus}/bin/dbus-monitor --system \ 462 + "type='signal',interface='org.freedesktop.login1.Manager',member='PrepareForSleep'" 2>/dev/null | \ 463 + while IFS= read -r line; do 464 + if [[ "$line" == *"boolean false"* ]]; then 465 + do_restart 466 + fi 467 + done & 468 + 469 + # Display hotplug monitor 470 + ${pkgs.systemd}/bin/udevadm monitor --property --subsystem-match=drm 2>/dev/null | \ 471 + while IFS= read -r line; do 472 + if [[ "$line" == *"HOTPLUG=1"* ]]; then 473 + do_restart 474 + fi 475 + done & 476 + 477 + wait 478 + ''}"; 479 + Restart = "on-failure"; 480 + RestartSec = 5; 481 + }; 482 + Install = { 483 + WantedBy = [ "graphical-session.target" ]; 484 + }; 485 + }; 486 + 487 + services.udiskie = { 488 + enable = true; 489 + tray = "never"; 490 + automount = true; 491 + }; 492 + 493 + services.mako = { 494 + enable = true; 495 + settings = { 496 + border-radius = 8; 497 + border-size = 2; 498 + padding = "12"; 499 + margin = "12"; 500 + font = "BerkeleyMono Nerd Font 11"; 501 + on-button-left = "invoke-default-action"; 502 + on-button-right = "dismiss"; 503 + }; 504 + }; 505 + 506 + dconf.settings = { 507 + "org/gnome/desktop/interface" = { 508 + color-scheme = "prefer-dark"; 509 + enable-hot-corners = false; 510 + }; 511 + }; 512 + 513 + home.pointerCursor = { 514 + name = "Adwaita"; 515 + package = pkgs.adwaita-icon-theme; 516 + size = 16; 517 + x11.enable = true; 518 + gtk.enable = true; 519 + }; 520 + }; 521 + }
+217
modules/editor.nix
··· 1 + { ... }: { 2 + flake.modules.homeManager.editor = 3 + { pkgs, config, ... }: 4 + { 5 + home.packages = with pkgs; [ 6 + helix 7 + diffnav 8 + gh 9 + forgejo-cli 10 + gh-dash 11 + nixfmt 12 + nil 13 + vscode-json-languageserver 14 + ]; 15 + 16 + programs.helix = { 17 + enable = true; 18 + settings = { 19 + editor = { 20 + bufferline = "multiple"; 21 + file-picker = { 22 + hidden = false; 23 + git-ignore = true; 24 + }; 25 + cursor-shape = { 26 + insert = "bar"; 27 + normal = "block"; 28 + select = "underline"; 29 + }; 30 + line-number = "relative"; 31 + cursorline = true; 32 + auto-format = true; 33 + end-of-line-diagnostics = "hint"; 34 + soft-wrap = { 35 + enable = true; 36 + }; 37 + lsp = { 38 + display-inlay-hints = true; 39 + display-messages = true; 40 + display-progress-messages = true; 41 + }; 42 + inline-diagnostics = { 43 + cursor-line = "hint"; 44 + }; 45 + }; 46 + keys = { 47 + normal = { 48 + esc = [ 49 + "keep_primary_selection" 50 + "collapse_selection" 51 + ]; 52 + }; 53 + }; 54 + }; 55 + languages = { 56 + language-server.rust-analyzer = { 57 + config = { 58 + check = { 59 + command = "clippy"; 60 + }; 61 + checkOnSave = true; 62 + cargo = { 63 + allFeatures = true; 64 + }; 65 + }; 66 + }; 67 + language-server.deno-lsp = { 68 + command = "deno"; 69 + args = [ "lsp" ]; 70 + config.deno.enable = true; 71 + }; 72 + 73 + language = [ 74 + { 75 + name = "html"; 76 + formatter = { 77 + command = "prettier"; 78 + args = [ 79 + "--parser" 80 + "html" 81 + ]; 82 + }; 83 + } 84 + { 85 + name = "nix"; 86 + auto-format = true; 87 + formatter = { 88 + command = "${pkgs.nixfmt}/bin/nixfmt"; 89 + }; 90 + } 91 + { 92 + name = "kotlin"; 93 + auto-format = true; 94 + } 95 + { 96 + name = "rust"; 97 + auto-format = true; 98 + formatter = { 99 + command = "rustfmt"; 100 + args = [ 101 + "--edition" 102 + "2024" 103 + ]; 104 + }; 105 + indent = { 106 + tab-width = 4; 107 + unit = "t"; 108 + }; 109 + } 110 + { 111 + name = "astro"; 112 + auto-format = true; 113 + formatter = { 114 + command = "npx"; 115 + args = [ 116 + "prettier" 117 + "--plugin" 118 + "prettier-plugin-astro" 119 + "--parser" 120 + "astro" 121 + ]; 122 + }; 123 + } 124 + { 125 + name = "json"; 126 + auto-format = true; 127 + } 128 + { 129 + name = "just"; 130 + auto-format = true; 131 + formatter = { 132 + command = "just"; 133 + args = [ 134 + "--justfile" 135 + "/dev/stdin" 136 + "--dump" 137 + ]; 138 + }; 139 + } 140 + { 141 + name = "toml"; 142 + auto-format = true; 143 + formatter = { 144 + command = "taplo"; 145 + args = [ 146 + "format" 147 + "-" 148 + ]; 149 + }; 150 + } 151 + ]; 152 + }; 153 + }; 154 + 155 + programs.git = { 156 + enable = true; 157 + settings = { 158 + user = { 159 + name = "seanaye"; 160 + email = "hello@seanaye.ca"; 161 + }; 162 + init.defaultBranch = "main"; 163 + commit.gpgSign = true; 164 + gpg.format = "ssh"; 165 + user.signingKey = "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIOIgEteUEW06dnBHe2z8vNLwz2iMKe8bba6JgMmOUpcBAAAABHNzaDo= sean@framework16"; 166 + gpg.ssh.allowedSignersFile = "${config.home.homeDirectory}/.ssh/allowed_signers"; 167 + diff.tool = "diffnav"; 168 + difftool.prompt = false; 169 + "difftool \"diffnav\"".cmd = "diffnav \"$LOCAL\" \"$REMOTE\""; 170 + }; 171 + }; 172 + 173 + programs.jujutsu = { 174 + enable = true; 175 + settings = { 176 + user = { 177 + email = "hello@seanaye.ca"; 178 + name = "Sean Aye"; 179 + }; 180 + ui."diff-formatter" = ":git"; 181 + signing = { 182 + sign-all = true; 183 + behavior = "own"; 184 + backend = "ssh"; 185 + key = "${config.home.homeDirectory}/.ssh/id_ed25519_sk_rk"; 186 + backends.ssh.allowed-signers = "${config.home.homeDirectory}/.ssh/allowed_signers"; 187 + }; 188 + }; 189 + }; 190 + 191 + xdg.configFile."jj/conf.d/diffnav.toml".text = '' 192 + [[--scope]] 193 + --when.commands = ["diff", "show"] 194 + [--scope.ui] 195 + pager = "diffnav" 196 + ''; 197 + 198 + xdg.configFile."gh-dash/config.yml".text = '' 199 + prSections: 200 + - title: My Pull Requests 201 + filters: is:open author:@me 202 + - title: Review Requested 203 + filters: is:open review-requested:@me 204 + issuesSections: 205 + - title: My Issues 206 + filters: is:open author:@me 207 + pager: 208 + diff: diffnav 209 + keybindings: 210 + prs: 211 + - key: T 212 + name: enhance 213 + command: >- 214 + zellij run -- gh enhance -R {{.RepoName}} {{.PrNumber}} 215 + ''; 216 + }; 217 + }
+52
modules/fonts.nix
··· 1 + { inputs, ... }: 2 + 3 + let 4 + hasBerkeleyMono = inputs ? berkeley-mono && !(inputs.berkeley-mono ? isStub); 5 + in 6 + { 7 + flake.modules.nixos.fonts = 8 + { pkgs, lib, ... }: 9 + let 10 + berkeley-mono-typeface = 11 + if hasBerkeleyMono then inputs.berkeley-mono.packages.${pkgs.system}.default else null; 12 + in 13 + { 14 + fonts = { 15 + fontDir.enable = true; 16 + fontconfig = { 17 + enable = true; 18 + defaultFonts = { 19 + monospace = 20 + lib.optionals hasBerkeleyMono [ 21 + "BerkeleyMono Nerd Font" 22 + "BerkeleyMono" 23 + ] 24 + ++ [ "JetBrainsMono Nerd Font" ]; 25 + }; 26 + }; 27 + packages = lib.optionals hasBerkeleyMono [ berkeley-mono-typeface ]; 28 + }; 29 + }; 30 + 31 + flake.modules.homeManager.fonts = 32 + { pkgs, ... }: 33 + { 34 + home.packages = with pkgs; [ 35 + font-awesome 36 + noto-fonts 37 + noto-fonts-cjk-sans 38 + noto-fonts-color-emoji 39 + nerd-fonts.jetbrains-mono 40 + nerd-fonts.symbols-only 41 + ]; 42 + 43 + fonts.fontconfig = { 44 + enable = true; 45 + defaultFonts = { 46 + monospace = [ "BerkeleyMono Nerd Font" ]; 47 + sansSerif = [ "Noto Sans" ]; 48 + serif = [ "Noto Serif" ]; 49 + }; 50 + }; 51 + }; 52 + }
+60
modules/framework-laptop.nix
··· 1 + { ... }: { 2 + flake.modules.nixos.framework-laptop = 3 + { pkgs, ... }: 4 + { 5 + networking.hostName = "framework16"; 6 + 7 + boot.initrd.luks.devices."luks-ee306bda-c450-4a56-b4fe-537899e38e0d" = { 8 + device = "/dev/disk/by-uuid/ee306bda-c450-4a56-b4fe-537899e38e0d"; 9 + bypassWorkqueues = true; 10 + }; 11 + 12 + # Reduce swap pressure to avoid thrashing through dm-crypt 13 + boot.kernel.sysctl."vm.swappiness" = 10; 14 + 15 + # Disable ABM (Active Backlight Management) to maintain color accuracy 16 + boot.kernelParams = [ "amdgpu.abmlevel=0" ]; 17 + 18 + # Enable QEMU emulation for aarch64 (for building Pi images) 19 + boot.binfmt.emulatedSystems = [ "aarch64-linux" ]; 20 + 21 + # nixos-raspberrypi binary cache 22 + nix.settings.extra-substituters = [ "https://nixos-raspberrypi.cachix.org" ]; 23 + nix.settings.extra-trusted-public-keys = [ "nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI=" ]; 24 + nix.settings.trusted-users = [ 25 + "root" 26 + "sean" 27 + ]; 28 + 29 + # Use power-profiles-daemon instead of TLP (recommended for AMD Framework) 30 + services.power-profiles-daemon.enable = true; 31 + 32 + # Disable keyboard/touchpad wake from suspend (prevents wake in bags) 33 + services.udev.extraRules = '' 34 + # Framework Laptop 16 - Disable wakeup for internal keyboard to prevent wake in bags 35 + ACTION=="add", SUBSYSTEM=="usb", DRIVERS=="usb", ATTRS{idVendor}=="32ac", ATTR{power/wakeup}="disabled" 36 + ''; 37 + 38 + environment.systemPackages = with pkgs; [ 39 + brightnessctl 40 + gdm 41 + ]; 42 + 43 + # SSH 44 + services.openssh = { 45 + enable = true; 46 + settings = { 47 + PasswordAuthentication = false; 48 + KbdInteractiveAuthentication = false; 49 + PermitRootLogin = "no"; 50 + AllowUsers = [ "sean" ]; 51 + }; 52 + }; 53 + 54 + # Firewall 55 + networking.firewall.allowedUDPPorts = [ ]; 56 + networking.firewall.enable = false; 57 + 58 + system.stateVersion = "25.05"; 59 + }; 60 + }
+119
modules/gaming.nix
··· 1 + { ... }: { 2 + flake.modules.nixos.gaming = 3 + { pkgs, lib, ... }: 4 + let 5 + # Steam/gamescope calls steamos-session-select when the user presses 6 + # "Switch to Desktop". Without this script, the button does nothing. 7 + steamos-session-select = pkgs.writeShellScriptBin "steamos-session-select" '' 8 + echo "Switching session to: $1" 9 + ''; 10 + 11 + # Pin gamescope to 3.14.29 — versions 3.15+ crash on NVIDIA due to 12 + # a confirmed driver bug (#5701801) in Vulkan YCbCr sampler pipeline compilation. 13 + gamescope_3_14 = pkgs.gamescope.overrideAttrs (old: { 14 + version = "3.14.29"; 15 + src = pkgs.fetchFromGitHub { 16 + owner = "ValveSoftware"; 17 + repo = "gamescope"; 18 + tag = "3.14.29"; 19 + fetchSubmodules = true; 20 + hash = "sha256-q3HEbFqUeNczKYUlou+quxawCTjpM5JNLrML84tZVYE="; 21 + }; 22 + patches = lib.take 2 (old.patches or [ ]); 23 + mesonFlags = builtins.filter 24 + ( 25 + f: 26 + builtins.match ".*glm_include_dir.*" f == null 27 + && builtins.match ".*stb_include_dir.*" f == null 28 + ) 29 + (old.mesonFlags or [ ]); 30 + env = (old.env or { }) // { 31 + CMAKE_POLICY_VERSION_MINIMUM = "3.5"; 32 + }; 33 + postPatch = 34 + builtins.replaceStrings [ "patchShebangs default_extras_install.sh" ] [ "" ] ( 35 + old.postPatch or "" 36 + ) 37 + + '' 38 + rm -rf subprojects/glm subprojects/glm.wrap subprojects/stb subprojects/stb.wrap 39 + mkdir -p subprojects/glm subprojects/stb 40 + 41 + cat > subprojects/glm/meson.build << 'GLMEOF' 42 + project('glm', 'cpp', version: '1.0.0') 43 + glm_dep = declare_dependency(include_directories: include_directories('${lib.getInclude pkgs.glm}/include', is_system: true)) 44 + meson.override_dependency('glm', glm_dep) 45 + GLMEOF 46 + 47 + cat > subprojects/stb/meson.build << 'STBEOF' 48 + project('stb', 'c', version: '0.0.1') 49 + stb_dep = declare_dependency(include_directories: include_directories('${lib.getInclude pkgs.stb}/include/stb', is_system: true)) 50 + meson.override_dependency('stb', stb_dep) 51 + STBEOF 52 + ''; 53 + }); 54 + in 55 + { 56 + # ZRAM swap to prevent OOM freezes 57 + zramSwap = { 58 + enable = true; 59 + memoryPercent = 50; 60 + }; 61 + 62 + # Kill runaway processes before the system locks up 63 + services.earlyoom = { 64 + enable = true; 65 + freeMemThreshold = 5; 66 + freeSwapThreshold = 5; 67 + enableNotifications = true; 68 + }; 69 + 70 + programs.steam = { 71 + enable = true; 72 + remotePlay.openFirewall = true; 73 + gamescopeSession = { 74 + enable = true; 75 + args = [ 76 + "-r" 77 + "120" 78 + "-R" 79 + "120" 80 + ]; 81 + env = { 82 + STEAM_DESKTOP_SESSION = "niri"; 83 + ENABLE_GAMESCOPE_WSI = "0"; 84 + }; 85 + }; 86 + extraCompatPackages = with pkgs; [ 87 + proton-ge-bin 88 + ]; 89 + }; 90 + 91 + programs.gamemode.enable = true; 92 + 93 + programs.gamescope = { 94 + enable = true; 95 + capSysNice = false; 96 + package = gamescope_3_14; 97 + }; 98 + 99 + security.wrappers.gamescope = { 100 + owner = "root"; 101 + group = "root"; 102 + source = "${gamescope_3_14}/bin/gamescope"; 103 + capabilities = "cap_sys_nice+pie"; 104 + }; 105 + 106 + environment.systemPackages = [ 107 + steamos-session-select 108 + ]; 109 + 110 + security.pam.loginLimits = [ 111 + { 112 + domain = "*"; 113 + type = "soft"; 114 + item = "nofile"; 115 + value = "8192"; 116 + } 117 + ]; 118 + }; 119 + }
+237
modules/home-automation.nix
··· 1 + { ... }: { 2 + flake.modules.nixos.home-automation = 3 + { pkgs, ... }: 4 + { 5 + # MQTT broker for Home Assistant (Tasmota devices, Frigate) 6 + services.mosquitto = { 7 + enable = true; 8 + listeners = [ 9 + { 10 + acl = [ "pattern readwrite #" ]; 11 + omitPasswordAuth = true; 12 + settings.allow_anonymous = true; 13 + } 14 + ]; 15 + }; 16 + 17 + # Frigate NVR for camera recording and AI object detection 18 + services.frigate = { 19 + enable = true; 20 + hostname = "frigate"; 21 + settings = { 22 + mqtt = { 23 + enabled = true; 24 + host = "localhost"; 25 + port = 1883; 26 + }; 27 + 28 + detectors = { 29 + cpu = { 30 + type = "cpu"; 31 + num_threads = 2; 32 + }; 33 + }; 34 + 35 + cameras = { 36 + picam = { 37 + enabled = true; 38 + ffmpeg = { 39 + hwaccel_args = "preset-nvidia-h264"; 40 + inputs = [ 41 + { 42 + path = "rtsp://pi:8554/picam"; 43 + roles = [ 44 + "detect" 45 + "record" 46 + ]; 47 + } 48 + ]; 49 + }; 50 + detect = { 51 + enabled = true; 52 + width = 1920; 53 + height = 1080; 54 + fps = 3; 55 + }; 56 + record = { 57 + enabled = true; 58 + retain = { 59 + days = 7; 60 + mode = "active_objects"; 61 + }; 62 + }; 63 + snapshots = { 64 + enabled = true; 65 + bounding_box = true; 66 + retain = { 67 + default = 14; 68 + }; 69 + }; 70 + objects = { 71 + track = [ 72 + "person" 73 + "dog" 74 + "cat" 75 + "car" 76 + ]; 77 + }; 78 + zones = { 79 + driveway = { 80 + coordinates = "0,0.243,1,0.544,1,1,0,1,0,0.75"; 81 + objects = [ 82 + "person" 83 + "car" 84 + "dog" 85 + "cat" 86 + ]; 87 + }; 88 + }; 89 + }; 90 + 91 + pizerocam = { 92 + enabled = true; 93 + ffmpeg = { 94 + hwaccel_args = "preset-nvidia-h264"; 95 + inputs = [ 96 + { 97 + path = "rtsp://pizero:8554/pizerocam"; 98 + roles = [ "record" ]; 99 + } 100 + ]; 101 + }; 102 + detect = { 103 + enabled = false; 104 + }; 105 + record = { 106 + enabled = true; 107 + retain = { 108 + days = 2; 109 + mode = "all"; 110 + }; 111 + }; 112 + }; 113 + }; 114 + 115 + record = { 116 + enabled = true; 117 + retain = { 118 + days = 7; 119 + mode = "active_objects"; 120 + }; 121 + }; 122 + }; 123 + }; 124 + 125 + # Frigate manages many ffmpeg child processes that ignore SIGTERM 126 + systemd.services.frigate.serviceConfig = { 127 + TimeoutStopSec = 5; 128 + KillMode = "mixed"; 129 + }; 130 + 131 + # Home Assistant service 132 + services.home-assistant = { 133 + enable = true; 134 + customComponents = with pkgs.home-assistant-custom-components; [ 135 + frigate 136 + ]; 137 + extraComponents = [ 138 + "esphome" 139 + "met" 140 + "radio_browser" 141 + "homekit" 142 + "homekit_controller" 143 + "isal" 144 + "mqtt" 145 + "tasmota" 146 + "wiz" 147 + "google_translate" 148 + "ecobee" 149 + "ibeacon" 150 + "go2rtc" 151 + "generic" 152 + ]; 153 + config = { 154 + homeassistant = { 155 + time_zone = "America/Toronto"; 156 + }; 157 + default_config = { }; 158 + zeroconf = { }; 159 + mqtt = { }; 160 + automation = [ 161 + { 162 + id = "1761448856909"; 163 + alias = "Lower heat at night"; 164 + trigger = [ 165 + { 166 + platform = "time"; 167 + at = "23:00:00"; 168 + } 169 + ]; 170 + condition = [ ]; 171 + action = [ 172 + { 173 + action = "climate.set_temperature"; 174 + target.device_id = "bfe22d32a4532f8ae991d6daffb48267"; 175 + data = { 176 + hvac_mode = "heat"; 177 + temperature = 18; 178 + }; 179 + } 180 + ]; 181 + mode = "single"; 182 + } 183 + { 184 + id = "1766200000001"; 185 + alias = "Raise heat in morning"; 186 + trigger = [ 187 + { 188 + platform = "time"; 189 + at = "06:00:00"; 190 + } 191 + ]; 192 + condition = [ ]; 193 + action = [ 194 + { 195 + action = "climate.set_temperature"; 196 + target.device_id = "bfe22d32a4532f8ae991d6daffb48267"; 197 + data = { 198 + hvac_mode = "heat"; 199 + temperature = 21; 200 + }; 201 + } 202 + ]; 203 + mode = "single"; 204 + } 205 + { 206 + id = "1766153071796"; 207 + alias = "Close Garage Door"; 208 + trigger = [ 209 + { 210 + platform = "device"; 211 + device_id = "d8dedd8cd0ce1488d9830c455bb0a761"; 212 + domain = "cover"; 213 + entity_id = "cf36763543169888aa106b1acb02ad72"; 214 + type = "opened"; 215 + for = { 216 + hours = 0; 217 + minutes = 10; 218 + seconds = 0; 219 + }; 220 + } 221 + ]; 222 + condition = [ ]; 223 + action = [ 224 + { 225 + device_id = "d8dedd8cd0ce1488d9830c455bb0a761"; 226 + domain = "cover"; 227 + entity_id = "cf36763543169888aa106b1acb02ad72"; 228 + type = "close"; 229 + } 230 + ]; 231 + mode = "single"; 232 + } 233 + ]; 234 + }; 235 + }; 236 + }; 237 + }
+50
modules/hosts/framework16.nix
··· 1 + { inputs, config, ... }: 2 + 3 + let 4 + nm = config.flake.modules.nixos; 5 + hm = config.flake.modules.homeManager; 6 + in 7 + { 8 + flake.nixosConfigurations.framework16 = inputs.nixpkgs.lib.nixosSystem { 9 + system = "x86_64-linux"; 10 + modules = [ 11 + # Hardware 12 + ../../hosts/framework16/hardware-configuration.nix 13 + inputs.nixos-hardware.nixosModules.framework-16-7040-amd 14 + 15 + # External NixOS modules 16 + inputs.catppuccin.nixosModules.catppuccin 17 + inputs.home-manager.nixosModules.home-manager 18 + { nixpkgs.overlays = [ inputs.niri.overlays.niri ]; } 19 + 20 + # Common aspects 21 + nm.nix-settings 22 + nm.base 23 + nm.security 24 + nm.sean 25 + nm.desktop 26 + nm.fonts 27 + 28 + # Framework-specific aspects 29 + nm.framework-laptop 30 + 31 + # Home Manager 32 + { 33 + home-manager.users.sean = { 34 + imports = [ 35 + inputs.catppuccin.homeModules.catppuccin 36 + inputs.niri.homeModules.niri 37 + inputs.zen-browser.homeModules.beta 38 + inputs.agenix.homeManagerModules.default 39 + hm.sean 40 + hm.desktop 41 + hm.fonts 42 + hm.shell 43 + hm.editor 44 + hm.apps 45 + ]; 46 + }; 47 + } 48 + ]; 49 + }; 50 + }
+140
modules/hosts/kodi-pi.nix
··· 1 + { inputs, config, ... }: 2 + 3 + let 4 + nm = config.flake.modules.nixos; 5 + in 6 + { 7 + flake.nixosConfigurations.kodi-pi = inputs.nixos-raspberrypi.lib.nixosSystemFull { 8 + specialArgs = { 9 + inherit inputs; 10 + inherit (inputs) nixos-raspberrypi; 11 + }; 12 + modules = [ 13 + ( 14 + { nixos-raspberrypi, ... }: 15 + { 16 + imports = with nixos-raspberrypi.nixosModules; [ 17 + raspberry-pi-5.base 18 + raspberry-pi-5.page-size-16k 19 + raspberry-pi-5.display-vc4 20 + ]; 21 + } 22 + ) 23 + # Disable SDL3 test suite (testprocess fails in Nix sandbox) 24 + ( 25 + { pkgs, ... }: 26 + { 27 + nixpkgs.overlays = [ 28 + (final: prev: { 29 + sdl3 = prev.sdl3.overrideAttrs (old: { 30 + doCheck = false; 31 + }); 32 + }) 33 + ]; 34 + } 35 + ) 36 + inputs.agenix.nixosModules.default 37 + 38 + # Aspect modules 39 + nm.pi-wifi 40 + 41 + # Kodi Pi 5 specific settings 42 + ( 43 + { pkgs, lib, ... }: 44 + { 45 + networking.hostName = "kodi-pi"; 46 + 47 + boot.loader.raspberry-pi.bootloader = "kernel"; 48 + boot.kernelParams = [ "video=HDMI-A-1:3840x2160@30D" ]; 49 + 50 + fileSystems."/" = { 51 + device = "/dev/disk/by-label/NIXOS_SD"; 52 + fsType = "ext4"; 53 + options = [ "noatime" ]; 54 + }; 55 + fileSystems."/boot/firmware" = { 56 + device = "/dev/disk/by-label/FIRMWARE"; 57 + fsType = "vfat"; 58 + options = [ 59 + "noatime" 60 + "noauto" 61 + "x-systemd.automount" 62 + "x-systemd.idle-timeout=1min" 63 + ]; 64 + }; 65 + 66 + hardware.graphics.enable = true; 67 + 68 + hardware.raspberry-pi.config.all.options = { 69 + gpu_mem = { 70 + enable = true; 71 + value = 256; 72 + }; 73 + hdmi_force_hotplug = { 74 + enable = true; 75 + value = true; 76 + }; 77 + }; 78 + 79 + services.pipewire = { 80 + enable = true; 81 + alsa.enable = true; 82 + pulse.enable = true; 83 + }; 84 + 85 + services.cage = { 86 + enable = true; 87 + user = "kiosk"; 88 + program = "${pkgs.jellyfin-media-player}/bin/jellyfinmediaplayer --tv"; 89 + }; 90 + 91 + users.users.kiosk = { 92 + isNormalUser = true; 93 + extraGroups = [ 94 + "video" 95 + "audio" 96 + "input" 97 + "render" 98 + ]; 99 + }; 100 + 101 + users.users.sean = { 102 + isNormalUser = true; 103 + extraGroups = [ "wheel" ]; 104 + openssh.authorizedKeys.keys = [ 105 + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDCIqgZ7kedxo+mOW7YG73Vp3zel3h180y3GKvHtRsXfGlpIIvRDy7pgCBQ4AGXYD4y78URQmFohYSAPqCPOPaWcU2un3XG9KvCzEsHmsbskPonitUmCiKvrKkb6oW4jCBtd7AEtBn+AiajAQFtPZ7NN2Df3AmTypvR6Irg7R+nxnfc9NTIHmGvxSDyWcbb4pguL20sctUSqGL6xGh8q/bqhdOThSimM+z9bEUNxK/5rPhwkNniMrp4pJcUrUiAh5/4DiRFG6KT+oeg+/myoz/Z1sPvAs7u/8JDQI4RshRD8Hu0oTkRBN6Hxj478q2SXbeBUZlD6IdjP3RhGpmSecoDdtWqKbpuV3eVRtQtba3KL86GBeV/bugaOdJ1Aud+1SOFJreAAuvxzMMKT+cdQZk6oOPP148DA/No+mDm/2S43lcdCXh79wA6YRAmKQ8jmZxTCtPutrvuZK1rguvvUlEoG/vhdNHh7eDa4Td07V6bjCRPUl8qk/e4M0E3pwsTlZc=" 106 + "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIOIgEteUEW06dnBHe2z8vNLwz2iMKe8bba6JgMmOUpcBAAAABHNzaDo= sean@framework16" 107 + ]; 108 + }; 109 + 110 + services.openssh = { 111 + enable = true; 112 + settings = { 113 + PasswordAuthentication = false; 114 + PermitRootLogin = "no"; 115 + }; 116 + }; 117 + 118 + nix.settings.require-sigs = false; 119 + nix.settings.substituters = [ 120 + "https://nixos-raspberrypi.cachix.org" 121 + "https://seanaye.cachix.org" 122 + ]; 123 + nix.settings.trusted-public-keys = [ 124 + "nixos-raspberrypi.cachix.org-1:4iMO9LXa8BqhU+Rpg6LQKiGa2lsNh/j2oiYLNOQ5sPI=" 125 + "seanaye.cachix.org-1:0Qf3cZ1SwnTwqaiNGltYySksjGHnemzRPiodThnvibA=" 126 + ]; 127 + 128 + networking.useDHCP = true; 129 + security.sudo.wheelNeedsPassword = false; 130 + 131 + networking.firewall.allowedTCPPorts = [ 22 ]; 132 + 133 + environment.systemPackages = [ pkgs.jellyfin-media-player ]; 134 + 135 + system.stateVersion = "24.11"; 136 + } 137 + ) 138 + ]; 139 + }; 140 + }
+56
modules/hosts/mira.nix
··· 1 + { inputs, config, ... }: 2 + 3 + let 4 + nm = config.flake.modules.nixos; 5 + hm = config.flake.modules.homeManager; 6 + in 7 + { 8 + flake.nixosConfigurations.mira = inputs.nixpkgs.lib.nixosSystem { 9 + system = "x86_64-linux"; 10 + modules = [ 11 + # Hardware 12 + ../../hosts/mira/hardware-configuration.nix 13 + 14 + # External NixOS modules 15 + inputs.catppuccin.nixosModules.catppuccin 16 + inputs.agenix.nixosModules.default 17 + inputs.copyparty.nixosModules.default 18 + inputs.nixarr.nixosModules.default 19 + inputs.home-manager.nixosModules.home-manager 20 + { nixpkgs.overlays = [ inputs.niri.overlays.niri inputs.copyparty.overlays.default ]; } 21 + 22 + # Common aspects 23 + nm.nix-settings 24 + nm.base 25 + nm.security 26 + nm.sean 27 + nm.desktop 28 + nm.fonts 29 + 30 + # Mira-specific aspects 31 + nm.nvidia 32 + nm.media-server 33 + nm.home-automation 34 + nm.gaming 35 + nm.mira-extras 36 + 37 + # Home Manager 38 + { 39 + home-manager.users.sean = { 40 + imports = [ 41 + inputs.catppuccin.homeModules.catppuccin 42 + inputs.niri.homeModules.niri 43 + inputs.zen-browser.homeModules.beta 44 + inputs.agenix.homeManagerModules.default 45 + hm.sean 46 + hm.desktop 47 + hm.fonts 48 + hm.shell 49 + hm.editor 50 + hm.apps 51 + ]; 52 + }; 53 + } 54 + ]; 55 + }; 56 + }
+47
modules/hosts/pi.nix
··· 1 + { inputs, config, ... }: 2 + 3 + let 4 + nm = config.flake.modules.nixos; 5 + in 6 + { 7 + flake.nixosConfigurations.pi = inputs.nixpkgs.lib.nixosSystem { 8 + system = "aarch64-linux"; 9 + modules = [ 10 + "${inputs.nixpkgs}/nixos/modules/installer/sd-card/sd-image-aarch64.nix" 11 + inputs.nixos-hardware.nixosModules.raspberry-pi-4 12 + inputs.agenix.nixosModules.default 13 + 14 + # Allow missing kernel modules 15 + ({ 16 + nixpkgs.overlays = [ 17 + (final: super: { 18 + makeModulesClosure = x: super.makeModulesClosure (x // { allowMissing = true; }); 19 + }) 20 + ]; 21 + }) 22 + 23 + # Aspect modules 24 + nm.pi-camera 25 + nm.pi-wifi 26 + 27 + # Pi 4 specific settings 28 + { 29 + networking.hostName = "pi"; 30 + 31 + pi = { 32 + streamName = "picam"; 33 + resolution = { 34 + width = 1920; 35 + height = 1080; 36 + }; 37 + framerate = 30; 38 + deviceTreeFilter = "bcm2711-rpi-4*.dtb"; 39 + deviceTreeCompatible = "brcm,bcm2711"; 40 + gpuMem = 256; 41 + }; 42 + 43 + system.stateVersion = "24.11"; 44 + } 45 + ]; 46 + }; 47 + }
+61
modules/hosts/pizero.nix
··· 1 + { inputs, config, ... }: 2 + 3 + let 4 + nm = config.flake.modules.nixos; 5 + in 6 + { 7 + flake.nixosConfigurations.pizero = inputs.nixpkgs.lib.nixosSystem { 8 + system = "aarch64-linux"; 9 + modules = [ 10 + "${inputs.nixpkgs}/nixos/modules/installer/sd-card/sd-image-aarch64.nix" 11 + inputs.agenix.nixosModules.default 12 + 13 + # Allow missing kernel modules 14 + ({ 15 + nixpkgs.overlays = [ 16 + (final: super: { 17 + makeModulesClosure = x: super.makeModulesClosure (x // { allowMissing = true; }); 18 + }) 19 + ]; 20 + }) 21 + 22 + # Aspect modules 23 + nm.pi-camera 24 + nm.pi-wifi 25 + 26 + # Pi Zero 2W specific settings 27 + ( 28 + { pkgs, lib, ... }: 29 + { 30 + networking.hostName = "pizero"; 31 + 32 + pi = { 33 + streamName = "pizerocam"; 34 + resolution = { 35 + width = 1280; 36 + height = 720; 37 + }; 38 + framerate = 15; 39 + deviceTreeFilter = "bcm2837-rpi-zero-2-w.dtb"; 40 + deviceTreeCompatible = "brcm,bcm2837"; 41 + gpuMem = 128; 42 + flipCamera = true; 43 + }; 44 + 45 + # Use RPi kernel which includes IMX708 camera driver 46 + boot.kernelPackages = pkgs.linuxPackages_rpi3; 47 + 48 + # Disable NixOS DTB so U-Boot uses the firmware's DTB 49 + hardware.deviceTree.enable = lib.mkForce false; 50 + sdImage.populateFirmwareCommands = lib.mkAfter '' 51 + chmod u+w ./firmware/config.txt 52 + echo "dtoverlay=imx708" >> ./firmware/config.txt 53 + echo "camera_auto_detect=1" >> ./firmware/config.txt 54 + ''; 55 + 56 + system.stateVersion = "24.11"; 57 + } 58 + ) 59 + ]; 60 + }; 61 + }
+114
modules/media-server.nix
··· 1 + { inputs, ... }: 2 + 3 + let 4 + jellyfinKodiSyncQueue = inputs.nixpkgs.legacyPackages.x86_64-linux.fetchzip { 5 + url = "https://repo.jellyfin.org/releases/plugin/kodi-sync-queue/kodi-sync-queue_15.0.0.0.zip"; 6 + stripRoot = false; 7 + hash = "sha256-xtlG3UQ/WClt/Hvxe+oId2CeJ+PWMDXBUJXh5+k+mZQ="; 8 + }; 9 + in 10 + { 11 + flake.modules.nixos.media-server = 12 + { pkgs, config, ... }: 13 + { 14 + services.flaresolverr.enable = true; 15 + 16 + age.secrets.wireguard.file = ../secrets/wireguard.age; 17 + 18 + nixarr = { 19 + enable = true; 20 + mediaDir = "/mnt/storage1/nixarr/media"; 21 + vpn = { 22 + enable = true; 23 + wgConf = config.age.secrets.wireguard.path; 24 + }; 25 + 26 + jellyfin = { 27 + enable = true; 28 + openFirewall = true; 29 + }; 30 + 31 + transmission = { 32 + enable = true; 33 + vpn.enable = true; 34 + }; 35 + sabnzbd = { 36 + enable = true; 37 + vpn.enable = true; 38 + openFirewall = true; 39 + }; 40 + 41 + prowlarr.enable = true; 42 + radarr.enable = true; 43 + sonarr.enable = true; 44 + jellyseerr = { 45 + enable = true; 46 + openFirewall = true; 47 + }; 48 + 49 + recyclarr = { 50 + enable = true; 51 + configuration = { 52 + sonarr = { 53 + series = { 54 + base_url = "http://localhost:8989"; 55 + api_key = "!env_var SONARR_API_KEY"; 56 + quality_definition = { 57 + type = "series"; 58 + }; 59 + delete_old_custom_formats = true; 60 + custom_formats = [ 61 + { 62 + trash_ids = [ 63 + "85c61753df5da1fb2aab6f2a47426b09" 64 + "9c11cd3f07101cdba90a2d81cf0e56b4" 65 + ]; 66 + assign_scores_to = [ 67 + { 68 + name = "WEB-DL (1080p)"; 69 + score = -10000; 70 + } 71 + ]; 72 + } 73 + ]; 74 + }; 75 + }; 76 + radarr = { 77 + movies = { 78 + base_url = "http://localhost:7878"; 79 + api_key = "!env_var RADARR_API_KEY"; 80 + quality_definition = { 81 + type = "movie"; 82 + }; 83 + delete_old_custom_formats = true; 84 + custom_formats = [ 85 + { 86 + trash_ids = [ 87 + "570bc9ebecd92723d2d21500f4be314c" 88 + "eca37840c13c6ef2dd0262b141a5482f" 89 + ]; 90 + assign_scores_to = [ 91 + { 92 + name = "HD Bluray + WEB"; 93 + score = 25; 94 + } 95 + ]; 96 + } 97 + ]; 98 + }; 99 + }; 100 + }; 101 + }; 102 + }; 103 + 104 + # Install Kodi Sync Queue plugin into Jellyfin 105 + systemd.services.jellyfin.serviceConfig.ExecStartPre = 106 + let 107 + pluginDir = "/data/.state/nixarr/jellyfin/data/plugins/Kodi Sync Queue/15.0.0.0"; 108 + in 109 + pkgs.writeShellScript "install-jellyfin-plugins" '' 110 + mkdir -p "${pluginDir}" 111 + cp -f ${jellyfinKodiSyncQueue}/*.dll ${jellyfinKodiSyncQueue}/meta.json "${pluginDir}/" 112 + ''; 113 + }; 114 + }
+120
modules/mira-extras.nix
··· 1 + { inputs, ... }: { 2 + flake.modules.nixos.mira-extras = 3 + { pkgs, config, lib, ... }: 4 + let 5 + bambu-studio = 6 + let 7 + pname = "bambu-studio"; 8 + version = "02.05.00.67"; 9 + ubuntu_version = "24.04_PR-9540"; 10 + 11 + src = pkgs.fetchurl { 12 + url = "https://github.com/bambulab/BambuStudio/releases/download/v${version}/Bambu_Studio_ubuntu-${ubuntu_version}.AppImage"; 13 + hash = "sha256-3ubZblrsOJzz1p34QiiwiagKaB7nI8xDeadFWHBkWfg="; 14 + }; 15 + 16 + appimage-contents = pkgs.appimageTools.extractType2 { 17 + inherit src pname version; 18 + }; 19 + 20 + wrapped = pkgs.appimageTools.wrapType2 { 21 + inherit src pname version; 22 + 23 + profile = '' 24 + export SSL_CERT_FILE="${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt" 25 + export GIO_MODULE_DIR="${pkgs.glib-networking}/lib/gio/modules/" 26 + export __GLX_VENDOR_LIBRARY_NAME=nvidia 27 + ''; 28 + 29 + extraPkgs = 30 + p: with p; [ 31 + cacert 32 + glib 33 + glib-networking 34 + gst_all_1.gst-plugins-bad 35 + gst_all_1.gst-plugins-base 36 + gst_all_1.gst-plugins-good 37 + webkitgtk_4_1 38 + ]; 39 + }; 40 + in 41 + pkgs.runCommand "bambu-studio-${version}" { } '' 42 + mkdir -p $out/bin 43 + ln -s ${wrapped}/bin/${pname} $out/bin/bambu-studio 44 + ln -s ${wrapped}/bin/${pname} $out/bin/BambuStudio 45 + 46 + mkdir -p $out/share/applications 47 + substitute ${appimage-contents}/BambuStudio.desktop $out/share/applications/BambuStudio.desktop \ 48 + --replace-fail "Exec=AppRun" "Exec=$out/bin/BambuStudio" 49 + 50 + if [ -d ${appimage-contents}/usr/share/icons ]; then 51 + cp -r ${appimage-contents}/usr/share/icons $out/share/ 52 + fi 53 + ''; 54 + in 55 + { 56 + networking.hostName = "mira"; 57 + 58 + # Prevent NetworkManager from managing USB Ethernet 59 + networking.networkmanager.unmanaged = [ "interface-name:enp0s20f0u4u3" ]; 60 + 61 + # Avahi (mDNS discovery) 62 + services.avahi = { 63 + enable = true; 64 + nssmdns4 = true; 65 + openFirewall = true; 66 + }; 67 + 68 + services.copyparty.enable = true; 69 + 70 + # SSH 71 + services.openssh = { 72 + enable = true; 73 + settings = { 74 + PasswordAuthentication = false; 75 + KbdInteractiveAuthentication = false; 76 + PermitRootLogin = "no"; 77 + AllowUsers = [ "sean" ]; 78 + }; 79 + }; 80 + 81 + # trmnl-rs server 82 + systemd.services.trmnl-rs = { 83 + description = "TRMNL Server"; 84 + wantedBy = [ "multi-user.target" ]; 85 + wants = [ "network-online.target" ]; 86 + after = [ 87 + "network-online.target" 88 + "nss-lookup.target" 89 + ]; 90 + serviceConfig = { 91 + ExecStart = "${inputs.trmnl-rs.packages.x86_64-linux.default}/bin/server"; 92 + Restart = "on-failure"; 93 + RestartSec = 5; 94 + DynamicUser = true; 95 + StateDirectory = "trmnl-rs"; 96 + WorkingDirectory = "/var/lib/trmnl-rs"; 97 + }; 98 + }; 99 + 100 + environment.systemPackages = [ 101 + pkgs.lm_sensors 102 + bambu-studio 103 + ]; 104 + 105 + # Firewall 106 + networking.firewall.allowedTCPPorts = [ 107 + 8096 # jellyfin 108 + 5055 # jellyseer 109 + 3000 # vite dev port 110 + 1883 # MQTT for Tasmota devices 111 + 2300 # trmnl 112 + 5000 # Frigate web UI 113 + 8971 # Frigate API 114 + config.services.home-assistant.config.http.server_port 115 + ]; 116 + networking.firewall.allowedUDPPorts = [ ]; 117 + 118 + system.stateVersion = "25.05"; 119 + }; 120 + }
+11
modules/nix-settings.nix
··· 1 + { ... }: { 2 + flake.modules.nixos.nix-settings = { 3 + nix.settings.experimental-features = [ 4 + "nix-command" 5 + "flakes" 6 + ]; 7 + nix.settings.download-buffer-size = 268435456; 8 + 9 + nixpkgs.config.allowUnfree = true; 10 + }; 11 + }
+41
modules/nvidia.nix
··· 1 + { ... }: { 2 + flake.modules.nixos.nvidia = 3 + { pkgs, lib, config, ... }: 4 + { 5 + # NVIDIA kernel modules in initrd 6 + boot.initrd.kernelModules = [ 7 + "nvidia" 8 + "nvidia_modeset" 9 + "nvidia_uvm" 10 + "nvidia_drm" 11 + ]; 12 + 13 + # System76 case support 14 + hardware.system76.kernel-modules.enable = true; 15 + hardware.system76.firmware-daemon.enable = true; 16 + hardware.system76.power-daemon.enable = true; 17 + 18 + # Graphics 19 + hardware.graphics = { 20 + enable = true; 21 + enable32Bit = true; 22 + }; 23 + 24 + # NVIDIA driver 25 + services.xserver.videoDrivers = [ "nvidia" ]; 26 + hardware.nvidia = { 27 + modesetting.enable = true; 28 + powerManagement.enable = true; 29 + open = true; 30 + nvidiaSettings = true; 31 + package = config.boot.kernelPackages.nvidiaPackages.stable; 32 + }; 33 + 34 + # ZSA keyboard 35 + hardware.keyboard.zsa.enable = true; 36 + 37 + # NVIDIA needs GBM/EGL env vars for cage (wlroots) to initialize GPU on greetd restart 38 + services.greetd.settings.default_session.command = lib.mkOverride 49 39 + "${pkgs.dbus}/bin/dbus-run-session ${lib.getExe pkgs.cage} -s -d -- env GBM_BACKEND=nvidia-drm __GLX_VENDOR_LIBRARY_NAME=nvidia GDK_SCALE=2 ${lib.getExe pkgs.greetd.regreet}"; 40 + }; 41 + }
+321
modules/pi-camera.nix
··· 1 + { ... }: { 2 + flake.modules.nixos.pi-camera = 3 + { pkgs, lib, config, ... }: 4 + let 5 + cfg = config.pi; 6 + 7 + deviceTree_overlay = _final: prev: { 8 + deviceTree = { 9 + applyOverlays = prev.callPackage ../hosts/pi-common/overlays/apply-overlays-dtmerge.nix { }; 10 + compileDTS = prev.deviceTree.compileDTS; 11 + }; 12 + }; 13 + 14 + libcamera-rpi = pkgs.libcamera.overrideAttrs (old: { 15 + mesonFlags = (old.mesonFlags or [ ]) ++ [ 16 + "-Dipas=rpi/vc4,rpi/pisp" 17 + "-Dpipelines=rpi/vc4,rpi/pisp" 18 + ]; 19 + }); 20 + 21 + rpicam-apps = pkgs.stdenv.mkDerivation rec { 22 + pname = "rpicam-apps"; 23 + version = "1.11.1"; 24 + 25 + src = pkgs.fetchFromGitHub { 26 + owner = "raspberrypi"; 27 + repo = "rpicam-apps"; 28 + rev = "v${version}"; 29 + hash = "sha256-hVoKbvWFeramPkHuibJwUgFOPS9v588+K8828a1fNnA="; 30 + }; 31 + 32 + nativeBuildInputs = with pkgs; [ 33 + meson 34 + ninja 35 + pkg-config 36 + ]; 37 + 38 + buildInputs = [ 39 + libcamera-rpi 40 + pkgs.libdrm 41 + pkgs.libexif 42 + pkgs.libjpeg 43 + pkgs.libpng 44 + pkgs.libtiff 45 + pkgs.boost 46 + pkgs.ffmpeg 47 + ]; 48 + 49 + mesonFlags = [ 50 + "-Denable_libav=enabled" 51 + "-Denable_drm=enabled" 52 + "-Denable_egl=disabled" 53 + "-Denable_qt=disabled" 54 + "-Denable_opencv=disabled" 55 + "-Denable_tflite=disabled" 56 + "-Denable_hailo=disabled" 57 + ]; 58 + 59 + meta = with lib; { 60 + description = "Raspberry Pi camera applications"; 61 + homepage = "https://github.com/raspberrypi/rpicam-apps"; 62 + license = licenses.bsd2; 63 + platforms = [ "aarch64-linux" ]; 64 + }; 65 + }; 66 + in 67 + { 68 + options.pi = { 69 + streamName = lib.mkOption { 70 + type = lib.types.str; 71 + description = "Name of the camera stream"; 72 + }; 73 + resolution = { 74 + width = lib.mkOption { 75 + type = lib.types.int; 76 + default = 1920; 77 + description = "Camera resolution width"; 78 + }; 79 + height = lib.mkOption { 80 + type = lib.types.int; 81 + default = 1080; 82 + description = "Camera resolution height"; 83 + }; 84 + }; 85 + framerate = lib.mkOption { 86 + type = lib.types.int; 87 + default = 30; 88 + description = "Camera framerate"; 89 + }; 90 + deviceTreeFilter = lib.mkOption { 91 + type = lib.types.str; 92 + description = "Device tree filter pattern"; 93 + }; 94 + deviceTreeCompatible = lib.mkOption { 95 + type = lib.types.str; 96 + description = "Device tree compatible string"; 97 + }; 98 + gpuMem = lib.mkOption { 99 + type = lib.types.int; 100 + default = 256; 101 + description = "GPU memory allocation in MB"; 102 + }; 103 + flipCamera = lib.mkOption { 104 + type = lib.types.bool; 105 + default = false; 106 + description = "Flip camera image 180 degrees"; 107 + }; 108 + }; 109 + 110 + config = { 111 + nix.settings.trusted-users = [ "sean" ]; 112 + 113 + networking.useDHCP = true; 114 + nixpkgs.overlays = [ deviceTree_overlay ]; 115 + 116 + boot.supportedFilesystems = lib.mkForce [ 117 + "vfat" 118 + "ext4" 119 + ]; 120 + boot.initrd.systemd.enable = false; 121 + 122 + services.openssh = { 123 + enable = true; 124 + settings = { 125 + PasswordAuthentication = false; 126 + PermitRootLogin = "no"; 127 + }; 128 + }; 129 + 130 + users.users.sean = { 131 + isNormalUser = true; 132 + extraGroups = [ 133 + "wheel" 134 + "video" 135 + ]; 136 + openssh.authorizedKeys.keys = [ 137 + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDCIqgZ7kedxo+mOW7YG73Vp3zel3h180y3GKvHtRsXfGlpIIvRDy7pgCBQ4AGXYD4y78URQmFohYSAPqCPOPaWcU2un3XG9KvCzEsHmsbskPonitUmCiKvrKkb6oW4jCBtd7AEtBn+AiajAQFtPZ7NN2Df3AmTypvR6Irg7R+nxnfc9NTIHmGvxSDyWcbb4pguL20sctUSqGL6xGh8q/bqhdOThSimM+z9bEUNxK/5rPhwkNniMrp4pJcUrUiAh5/4DiRFG6KT+oeg+/myoz/Z1sPvAs7u/8JDQI4RshRD8Hu0oTkRBN6Hxj478q2SXbeBUZlD6IdjP3RhGpmSecoDdtWqKbpuV3eVRtQtba3KL86GBeV/bugaOdJ1Aud+1SOFJreAAuvxzMMKT+cdQZk6oOPP148DA/No+mDm/2S43lcdCXh79wA6YRAmKQ8jmZxTCtPutrvuZK1rguvvUlEoG/vhdNHh7eDa4Td07V6bjCRPUl8qk/e4M0E3pwsTlZc=" 138 + "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIOIgEteUEW06dnBHe2z8vNLwz2iMKe8bba6JgMmOUpcBAAAABHNzaDo= sean@framework16" 139 + ]; 140 + }; 141 + 142 + security.sudo.wheelNeedsPassword = false; 143 + 144 + services.go2rtc = { 145 + enable = true; 146 + settings = { 147 + ffmpeg.bin = "${pkgs.ffmpeg}/bin/ffmpeg"; 148 + streams = { 149 + "${cfg.streamName}" = "exec:${rpicam-apps}/bin/rpicam-vid -t 0 --width ${toString cfg.resolution.width} --height ${toString cfg.resolution.height} --framerate ${toString cfg.framerate} --codec h264 --inline${lib.optionalString cfg.flipCamera " --vflip --hflip"} -o -"; 150 + }; 151 + }; 152 + }; 153 + 154 + services.udev.extraRules = '' 155 + SUBSYSTEM=="dma_heap", GROUP="video", MODE="0660" 156 + ''; 157 + 158 + systemd.services.go2rtc.serviceConfig = { 159 + User = lib.mkForce "root"; 160 + }; 161 + 162 + environment.systemPackages = [ 163 + pkgs.ffmpeg 164 + pkgs.libraspberrypi 165 + libcamera-rpi 166 + rpicam-apps 167 + pkgs.v4l-utils 168 + ]; 169 + 170 + hardware.deviceTree.filter = cfg.deviceTreeFilter; 171 + hardware.deviceTree.overlays = [ 172 + { 173 + name = "imx708-overlay"; 174 + dtsText = '' 175 + // SPDX-License-Identifier: GPL-2.0-only 176 + // Definitions for IMX708 camera module on VC I2C bus 177 + /dts-v1/; 178 + /plugin/; 179 + 180 + /{ 181 + compatible = "${cfg.deviceTreeCompatible}"; 182 + 183 + fragment@0 { 184 + target = <&i2c0if>; 185 + __overlay__ { 186 + status = "okay"; 187 + }; 188 + }; 189 + 190 + clk_frag: fragment@1 { 191 + target = <&cam1_clk>; 192 + __overlay__ { 193 + status = "okay"; 194 + clock-frequency = <24000000>; 195 + }; 196 + }; 197 + 198 + fragment@2 { 199 + target = <&i2c0mux>; 200 + __overlay__ { 201 + status = "okay"; 202 + }; 203 + }; 204 + 205 + reg_frag: fragment@3 { 206 + target = <&cam1_reg>; 207 + cam_reg: __overlay__ { 208 + startup-delay-us = <70000>; 209 + off-on-delay-us = <30000>; 210 + regulator-min-microvolt = <2700000>; 211 + regulator-max-microvolt = <2700000>; 212 + }; 213 + }; 214 + 215 + i2c_frag: fragment@100 { 216 + target = <&i2c_csi_dsi>; 217 + __overlay__ { 218 + #address-cells = <1>; 219 + #size-cells = <0>; 220 + status = "okay"; 221 + 222 + cam_node: imx708@1a { 223 + compatible = "sony,imx708"; 224 + reg = <0x1a>; 225 + status = "okay"; 226 + 227 + clocks = <&cam1_clk>; 228 + clock-names = "inclk"; 229 + 230 + vana1-supply = <&cam1_reg>; 231 + vana2-supply = <&cam_dummy_reg>; 232 + vdig-supply = <&cam_dummy_reg>; 233 + vddl-supply = <&cam_dummy_reg>; 234 + 235 + rotation = <180>; 236 + orientation = <2>; 237 + 238 + port { 239 + cam_endpoint: endpoint { 240 + clock-lanes = <0>; 241 + data-lanes = <1 2>; 242 + clock-noncontinuous; 243 + link-frequencies = 244 + /bits/ 64 <450000000>; 245 + }; 246 + }; 247 + }; 248 + 249 + vcm_node: dw9817@c { 250 + compatible = "dongwoon,dw9817-vcm"; 251 + reg = <0x0c>; 252 + status = "okay"; 253 + VDD-supply = <&cam1_reg>; 254 + }; 255 + }; 256 + }; 257 + 258 + csi_frag: fragment@101 { 259 + target = <&csi1>; 260 + csi: __overlay__ { 261 + status = "okay"; 262 + brcm,media-controller; 263 + 264 + port { 265 + csi_ep: endpoint { 266 + remote-endpoint = <&cam_endpoint>; 267 + clock-lanes = <0>; 268 + data-lanes = <1 2>; 269 + clock-noncontinuous; 270 + }; 271 + }; 272 + }; 273 + }; 274 + 275 + __overrides__ { 276 + rotation = <&cam_node>,"rotation:0"; 277 + orientation = <&cam_node>,"orientation:0"; 278 + media-controller = <&csi>,"brcm,media-controller?"; 279 + cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>, 280 + <&csi_frag>, "target:0=",<&csi0>, 281 + <&clk_frag>, "target:0=",<&cam0_clk>, 282 + <&reg_frag>, "target:0=",<&cam0_reg>, 283 + <&cam_node>, "clocks:0=",<&cam0_clk>, 284 + <&cam_node>, "vana1-supply:0=",<&cam0_reg>, 285 + <&vcm_node>, "VDD-supply:0=",<&cam0_reg>; 286 + vcm = <&vcm_node>, "status", 287 + <0>, "=4"; 288 + link-frequency = <&cam_endpoint>,"link-frequencies#0"; 289 + }; 290 + }; 291 + 292 + &cam_endpoint { 293 + remote-endpoint = <&csi_ep>; 294 + }; 295 + ''; 296 + } 297 + ]; 298 + 299 + hardware.enableRedistributableFirmware = true; 300 + 301 + sdImage.populateFirmwareCommands = lib.mkAfter '' 302 + chmod u+w ./firmware/config.txt 303 + cat >> ./firmware/config.txt << EOF 304 + 305 + # Camera support - Pi Camera v3 (IMX708) 306 + gpu_mem=${toString cfg.gpuMem} 307 + EOF 308 + 309 + if [ -d ${pkgs.raspberrypifw}/share/raspberrypi/boot/overlays ]; then 310 + cp -r ${pkgs.raspberrypifw}/share/raspberrypi/boot/overlays ./firmware/ 311 + fi 312 + ''; 313 + 314 + networking.firewall.allowedTCPPorts = [ 315 + 22 316 + 1984 317 + 8554 318 + ]; 319 + }; 320 + }; 321 + }
+40
modules/pi-wifi.nix
··· 1 + { ... }: { 2 + flake.modules.nixos.pi-wifi = 3 + { config, pkgs, ... }: 4 + { 5 + services.openssh.hostKeys = [ 6 + { 7 + path = "/etc/ssh/ssh_host_ed25519_key"; 8 + type = "ed25519"; 9 + } 10 + ]; 11 + 12 + age.identityPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; 13 + age.secrets.wifi = { 14 + file = ../secrets/wifi.age; 15 + mode = "0444"; 16 + }; 17 + 18 + networking.wireless = { 19 + enable = true; 20 + secretsFile = config.age.secrets.wifi.path; 21 + networks."GL-MT6000-6a6" = { 22 + pskRaw = "ext:WIFI_PSK"; 23 + extraConfig = '' 24 + freq_list=5180 5200 5220 5240 5260 5280 5300 5320 5500 5520 5540 5560 5580 5600 5620 5640 5660 5680 5700 5720 5745 5765 5785 5805 5825 25 + ''; 26 + }; 27 + }; 28 + 29 + systemd.services.wifi-powersave-off = { 30 + description = "Disable WiFi power save"; 31 + after = [ "network.target" ]; 32 + wantedBy = [ "multi-user.target" ]; 33 + serviceConfig = { 34 + Type = "oneshot"; 35 + ExecStart = "${pkgs.iw}/bin/iw dev wlan0 set power_save off"; 36 + RemainAfterExit = true; 37 + }; 38 + }; 39 + }; 40 + }
+120
modules/sean.nix
··· 1 + { inputs, ... }: 2 + 3 + let 4 + sshAuthorizedKeys = [ 5 + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDCIqgZ7kedxo+mOW7YG73Vp3zel3h180y3GKvHtRsXfGlpIIvRDy7pgCBQ4AGXYD4y78URQmFohYSAPqCPOPaWcU2un3XG9KvCzEsHmsbskPonitUmCiKvrKkb6oW4jCBtd7AEtBn+AiajAQFtPZ7NN2Df3AmTypvR6Irg7R+nxnfc9NTIHmGvxSDyWcbb4pguL20sctUSqGL6xGh8q/bqhdOThSimM+z9bEUNxK/5rPhwkNniMrp4pJcUrUiAh5/4DiRFG6KT+oeg+/myoz/Z1sPvAs7u/8JDQI4RshRD8Hu0oTkRBN6Hxj478q2SXbeBUZlD6IdjP3RhGpmSecoDdtWqKbpuV3eVRtQtba3KL86GBeV/bugaOdJ1Aud+1SOFJreAAuvxzMMKT+cdQZk6oOPP148DA/No+mDm/2S43lcdCXh79wA6YRAmKQ8jmZxTCtPutrvuZK1rguvvUlEoG/vhdNHh7eDa4Td07V6bjCRPUl8qk/e4M0E3pwsTlZc=" 6 + "no-touch-required sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAILdilHXHdAP/V8Zq28EzHKtLAMMaFPu4+1det2N50QfhAAAABHNzaDo= sean@framework16" 7 + ]; 8 + in 9 + { 10 + flake.modules.nixos.sean = 11 + { pkgs, ... }: 12 + { 13 + users.groups.storage = { }; 14 + users.groups.plugdev = { }; 15 + users.users.sean = { 16 + isNormalUser = true; 17 + description = "Sean Aye"; 18 + extraGroups = [ 19 + "docker" 20 + "networkmanager" 21 + "wheel" 22 + "video" 23 + "disk" 24 + "storage" 25 + "input" 26 + "plugdev" 27 + "dialout" 28 + ]; 29 + shell = pkgs.fish; 30 + linger = true; 31 + openssh.authorizedKeys.keys = sshAuthorizedKeys; 32 + }; 33 + 34 + programs.fish.enable = true; 35 + programs._1password.enable = true; 36 + programs._1password-gui = { 37 + enable = true; 38 + polkitPolicyOwners = [ "sean" ]; 39 + }; 40 + }; 41 + 42 + flake.modules.homeManager.sean = 43 + { pkgs, config, ... }: 44 + { 45 + home.username = "sean"; 46 + home.homeDirectory = "/home/sean"; 47 + home.stateVersion = "25.05"; 48 + 49 + nixpkgs.config.allowUnfree = true; 50 + nixpkgs.config.permittedInsecurePackages = [ 51 + "libsoup-2.74.3" 52 + ]; 53 + 54 + home.packages = with pkgs; [ 55 + inputs.agenix.packages.${pkgs.system}.default 56 + age-plugin-yubikey 57 + lxqt.lxqt-policykit 58 + ]; 59 + 60 + programs.ssh = { 61 + enable = true; 62 + enableDefaultConfig = false; 63 + matchBlocks = { 64 + "*" = { 65 + identityFile = [ 66 + "${config.home.homeDirectory}/.ssh/id_ed25519_sk_rk" 67 + "${config.home.homeDirectory}/.ssh/id_rsa.pub" 68 + ]; 69 + }; 70 + }; 71 + }; 72 + 73 + programs.awscli = { 74 + enable = true; 75 + settings = { 76 + "default" = { 77 + region = "us-east-1"; 78 + }; 79 + }; 80 + }; 81 + 82 + home.sessionVariables = { 83 + EDITOR = "hx"; 84 + VISUAL = "hx"; 85 + SUDO_EDITOR = "hx"; 86 + SSH_AUTH_SOCK = "${config.home.homeDirectory}/.1password/agent.sock"; 87 + SSH_ASKPASS = "${pkgs.openssh-askpass}/bin/gnome-ssh-askpass3"; 88 + SSH_ASKPASS_REQUIRE = "prefer"; 89 + }; 90 + 91 + # SSH allowed signers for commit signature verification 92 + home.file.".ssh/allowed_signers".text = '' 93 + hello@seanaye.ca ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDCIqgZ7kedxo+mOW7YG73Vp3zel3h180y3GKvHtRsXfGlpIIvRDy7pgCBQ4AGXYD4y78URQmFohYSAPqCPOPaWcU2un3XG9KvCzEsHmsbskPonitUmCiKvrKkb6oW4jCBtd7AEtBn+AiajAQFtPZ7NN2Df3AmTypvR6Irg7R+nxnfc9NTIHmGvxSDyWcbb4pguL20sctUSqGL6xGh8q/bqhdOThSimM+z9bEUNxK/5rPhwkNniMrp4pJcUrUiAh5/4DiRFG6KT+oeg+/myoz/Z1sPvAs7u/8JDQI4RshRD8Hu0oTkRBN6Hxj478q2SXbeBUZlD6IdjP3RhGpmSecoDdtWqKbpuV3eVRtQtba3KL86GBeV/bugaOdJ1Aud+1SOFJreAAuvxzMMKT+cdQZk6oOPP148DA/No+mDm/2S43lcdCXh79wA6YRAmKQ8jmZxTCtPutrvuZK1rguvvUlEoG/vhdNHh7eDa4Td07V6bjCRPUl8qk/e4M0E3pwsTlZc= 94 + hello@seanaye.ca sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAILdilHXHdAP/V8Zq28EzHKtLAMMaFPu4+1det2N50QfhAAAABHNzaDo= sean@framework16 95 + ''; 96 + 97 + # Yubikey identity for agenix 98 + home.file.".config/agenix/yubikey-identity.txt".text = '' 99 + # Serial: 26930059, Slot: 1 100 + # Name: agenix 101 + # Recipient: age1yubikey1qw64ag5lzvn9ekrflu5ruj4a6ucycscl6ctk39fjzf76jptsay39z442pxv 102 + AGE-PLUGIN-YUBIKEY-1304E5QVZZD74FKSP8FMCT 103 + ''; 104 + 105 + # Same identity for sops 106 + home.file.".config/sops/age/keys.txt".text = '' 107 + # Serial: 26930059, Slot: 1 108 + # Name: agenix 109 + # Recipient: age1yubikey1qw64ag5lzvn9ekrflu5ruj4a6ucycscl6ctk39fjzf76jptsay39z442pxv 110 + AGE-PLUGIN-YUBIKEY-1304E5QVZZD74FKSP8FMCT 111 + ''; 112 + 113 + # Yubikey sudo access 114 + home.file.".config/Yubico/u2f_keys".text = '' 115 + sean:2HY//CedY0ZSrKf57lT7abxG8+8bkPyxCfp/0HMlk/il/5W8pn4R5xLiZDcJtvL85U24h9IEIxa4CS22mpaDSA==,gcD/dpLdwvUFcGGPHS4qNsarH4lOEy1AJAT7zoC6BPlFRUYEa8DpVVKFTcvT6PotjnSHSrWWGb/f3U2k2jIOIw==,es256,+presence 116 + ''; 117 + 118 + programs.home-manager.enable = true; 119 + }; 120 + }
+23
modules/security.nix
··· 1 + { ... }: { 2 + flake.modules.nixos.security = { 3 + security.polkit.enable = true; 4 + 5 + services.gnome.gnome-keyring.enable = true; 6 + security.pam.services.greetd.enableGnomeKeyring = true; 7 + 8 + services.pcscd.enable = true; 9 + 10 + security.pam.u2f = { 11 + enable = true; 12 + control = "sufficient"; 13 + cue = true; 14 + settings = { 15 + origin = "pam://nixos"; 16 + appid = "pam://nixos"; 17 + }; 18 + }; 19 + security.pam.services.sudo.u2fAuth = true; 20 + 21 + programs.yubikey-touch-detector.enable = true; 22 + }; 23 + }
+140
modules/shell.nix
··· 1 + { inputs, ... }: 2 + 3 + let 4 + zjctl = pkgs: import ../packages/zjctl.nix { inherit pkgs; }; 5 + zrpc-wasm = pkgs: import ../packages/zrpc-wasm.nix { inherit pkgs; }; 6 + in 7 + { 8 + flake.modules.homeManager.shell = 9 + { pkgs, config, ... }: 10 + { 11 + home.packages = with pkgs; [ 12 + yazi 13 + fd 14 + ripgrep 15 + rsync 16 + zoxide 17 + (zjctl pkgs) 18 + ]; 19 + 20 + programs.direnv.enable = true; 21 + 22 + programs.atuin = { 23 + enable = true; 24 + enableFishIntegration = true; 25 + daemon.enable = true; 26 + settings = { 27 + filter_mode_shell_up_key_binding = "session"; 28 + }; 29 + }; 30 + 31 + programs.zellij = { 32 + enable = true; 33 + settings = { 34 + keybinds = { 35 + unbind = [ 36 + "Ctrl q" 37 + "Ctrl o" 38 + ]; 39 + normal = { 40 + "bind \"Ctrl m\"" = { 41 + SwitchToMode = "Session"; 42 + }; 43 + }; 44 + }; 45 + load_plugins = { 46 + "\"file:~/.config/zellij/plugins/zrpc.wasm\"" = { }; 47 + }; 48 + pane_frames = false; 49 + show_startup_tips = false; 50 + ui = { 51 + pane_frames.hide_session_name = true; 52 + }; 53 + }; 54 + }; 55 + 56 + xdg.configFile."zellij/plugins/zrpc.wasm".source = "${zrpc-wasm pkgs}/zrpc.wasm"; 57 + 58 + xdg.configFile."zellij/layouts/split.kdl".text = '' 59 + layout { 60 + tab { 61 + pane size="50%" 62 + pane split_direction="vertical" size="50%" { 63 + pane 64 + pane 65 + } 66 + } 67 + } 68 + ''; 69 + 70 + programs.zoxide = { 71 + enable = true; 72 + enableFishIntegration = true; 73 + }; 74 + 75 + programs.fish = { 76 + enable = true; 77 + shellAliases = { 78 + agenix = "agenix -i ~/.config/agenix/yubikey-identity.txt"; 79 + }; 80 + interactiveShellInit = '' 81 + set fish_greeting 82 + # Set 1Password SSH agent socket 83 + set -gx SSH_AUTH_SOCK ${config.home.homeDirectory}/.1password/agent.sock 84 + # Load 1Password CLI plugins 85 + if test -f ~/.config/op/plugins.sh 86 + source ~/.config/op/plugins.sh 87 + end 88 + # Auto-launch zellij if not already inside a session 89 + # Use systemd-run to escape niri's session scope so the server 90 + # survives logout 91 + if not set -q ZELLIJ 92 + systemd-run --user --scope zellij 93 + else 94 + fastfetch --logo small 95 + end 96 + 97 + function y 98 + set tmp (mktemp -t "yazi-cwd.XXXXXX") 99 + yazi $argv --cwd-file="$tmp" 100 + if read -z cwd < "$tmp"; and [ -n "$cwd" ]; and [ "$cwd" != "$PWD" ] 101 + builtin cd -- "$cwd" 102 + end 103 + rm -f -- "$tmp" 104 + end 105 + ''; 106 + functions = { 107 + s3edit = '' 108 + set file (basename $argv[1]) 109 + set tmpfile /tmp/$file 110 + aws s3 cp $argv[1] $tmpfile 111 + and $EDITOR $tmpfile 112 + and aws s3 cp $tmpfile $argv[1] 113 + ''; 114 + }; 115 + }; 116 + 117 + programs.starship = { 118 + enable = true; 119 + enableFishIntegration = true; 120 + }; 121 + 122 + programs.alacritty = { 123 + enable = true; 124 + settings = { 125 + terminal.shell.program = "fish"; 126 + window = { 127 + decorations = "none"; 128 + opacity = 0.9; 129 + }; 130 + font = { 131 + normal = { 132 + family = "BerkeleyMono Nerd Font"; 133 + style = "Regular"; 134 + }; 135 + size = 12.0; 136 + }; 137 + }; 138 + }; 139 + }; 140 + }
+20
modules/systems.nix
··· 1 + { lib, ... }: { 2 + config.systems = [ 3 + "x86_64-linux" 4 + "aarch64-linux" 5 + ]; 6 + 7 + # Declare flake.modules option so aspect files can merge their contributions 8 + options.flake.modules = { 9 + nixos = lib.mkOption { 10 + type = lib.types.attrsOf lib.types.raw; 11 + default = { }; 12 + description = "NixOS modules keyed by aspect name"; 13 + }; 14 + homeManager = lib.mkOption { 15 + type = lib.types.attrsOf lib.types.raw; 16 + default = { }; 17 + description = "Home Manager modules keyed by aspect name"; 18 + }; 19 + }; 20 + }