me like nix
1{
2 pkgs,
3 inputs,
4 config,
5 ...
6}:
7
8{
9 # Import the home-manager modules you want to use
10 imports = [
11 inputs.catppuccin.homeModules.catppuccin
12 inputs.niri.homeModules.niri
13 inputs.zen-browser.homeModules.beta
14 inputs.agenix.homeManagerModules.default
15 ];
16
17 # All your user-specific packages
18 home.packages = with pkgs; [
19 helix
20 git
21 jujutsu # jj-cli
22 atop
23 zellij # terminal multiplexer
24 alacritty
25 fuzzel # Application launcher
26 bemoji # emoji picker
27 networkmanager_dmenu # network picker for fuzzel
28 quickshell # Status bar (QML-based)
29 swww # For setting wallpapers
30 cliphist # Clipboard history manager
31 pavucontrol # GUI for PulseAudio/PipeWire volume control
32 playerctl # MPRIS media player control
33 (element-desktop.override {
34 commandLineArgs = "--password-store=gnome-libsecret";
35 })
36 fd
37 ripgrep
38 yazi # tui file browser
39 gh # github cli
40 signal-desktop
41 xwayland-satellite # for running x11 apps
42 nixfmt # nix formatter
43 nil # nix language server
44 atac # postman-like TUI
45 trippy # network analyzer
46 rsync # file sync utility
47 udiskie # for mounting external drives
48 darktable # photo editing
49 zoxide
50 chromium
51 claude-code
52 nautilus # file browser
53 sqlitebrowser
54 gnome-characters # symbol picker
55 sendme # file transfer
56 desktop-file-utils # for managing .desktop files
57 flyctl # fly.io cli
58 vscode-json-languageserver
59 gnome-network-displays
60 doppler # secret management
61 rainfrog # db tui
62 loupe # image viewer
63 glycin-loaders # various format loaders for loupe
64 docker-compose
65 discord
66 mangohud
67 prismlauncher # minecraft launcher
68 fastfetch
69 inputs.agenix.packages.${pkgs.system}.default # agenix CLI
70 age-plugin-yubikey # Yubikey support for agenix
71
72 # --- FONTS ARE IMPORTANT ---
73 # Berkeley Mono is the main system font, keeping JetBrains and Font Awesome for icons
74 font-awesome
75 noto-fonts
76 noto-fonts-cjk-sans
77 noto-fonts-color-emoji
78 nerd-fonts.jetbrains-mono
79 nerd-fonts.symbols-only
80 # --- POLKIT AGENT (for 1Password GUI, etc.) ---
81 lxqt.lxqt-policykit # Lightweight polkit agent
82 ];
83
84 programs.niri = {
85 enable = true;
86 settings = {
87 window-rules = [
88 {
89 geometry-corner-radius = {
90 top-left = 5.0;
91 top-right = 5.0;
92 bottom-left = 5.0;
93 bottom-right = 5.0;
94 };
95 clip-to-geometry = true;
96 draw-border-with-background = false;
97 }
98 ];
99 debug = {
100 honor-xdg-activation-with-invalid-serial = { };
101 };
102 layout = {
103 focus-ring = {
104 width = 2;
105 active.color = "#8caaee";
106 inactive.color = "#414559";
107 };
108 struts = {
109 top = -6;
110 bottom = -6;
111 left = 0;
112 right = 0;
113 };
114 gaps = 8;
115 };
116 gestures = {
117 hot-corners = {
118 enable = false;
119 };
120 };
121 binds = {
122 "Mod+d".action.spawn = "fuzzel";
123 "Mod+e".action.spawn = "bemoji";
124 "Mod+n".action.spawn = "networkmanager_dmenu";
125 "Mod+a".action.spawn = "alacritty";
126 "Mod+h".action = {
127 focus-column-left = { };
128 };
129 "Mod+j".action = {
130 focus-workspace-down = { };
131 };
132 "Mod+k".action = {
133 focus-workspace-up = { };
134 };
135 "Mod+l".action = {
136 focus-column-right = { };
137 };
138 "Mod+Shift+h".action = {
139 move-column-left = { };
140 };
141 "Mod+Shift+j".action = {
142 move-window-down-or-to-workspace-down = { };
143 };
144 "Mod+Shift+k".action = {
145 move-window-up-or-to-workspace-up = { };
146 };
147 "Mod+Shift+l".action = {
148 move-column-right = { };
149 };
150 "Mod+Down".action = {
151 move-workspace-down = { };
152 };
153 "Mod+Up".action = {
154 move-workspace-up = { };
155 };
156 "Mod+p".action = {
157 show-hotkey-overlay = { };
158 };
159 "Mod+o".action = {
160 toggle-overview = { };
161 };
162 "Mod+q".action = {
163 close-window = { };
164 };
165 "Mod+f".action = {
166 toggle-window-floating = { };
167 };
168 "Mod+Shift+f".action = {
169 switch-focus-between-floating-and-tiling = { };
170 };
171 "Mod+m".action = {
172 fullscreen-window = { };
173 };
174 "Mod+s".action = {
175 screenshot = {
176 show-pointer = true;
177 };
178 };
179 "Mod+1".action = {
180 set-column-width = "100%";
181 };
182 "Mod+2".action = {
183 set-column-width = "50%";
184 };
185 "Mod+Minus".action = {
186 set-column-width = "-10%";
187 };
188 "Mod+Equal".action = {
189 set-column-width = "+10%";
190 };
191 "Mod+Shift+r".action.spawn = [
192 "systemctl"
193 "--user"
194 "restart"
195 "quickshell.service"
196 ];
197 "XF86AudioPlay".action.spawn = [
198 "playerctl"
199 "play-pause"
200 ];
201 "XF86AudioStop".action.spawn = [
202 "playerctl"
203 "stop"
204 ];
205 "XF86AudioNext".action.spawn = [
206 "playerctl"
207 "next"
208 ];
209 "XF86AudioPrev".action.spawn = [
210 "playerctl"
211 "previous"
212 ];
213 "XF86MonBrightnessDown".action.spawn = [
214 "brightnessctl"
215 "set"
216 "5%-"
217 ];
218 "XF86MonBrightnessUp".action.spawn = [
219 "brightnessctl"
220 "set"
221 "+5%"
222 ];
223 };
224 outputs = {
225 # External monitor - primary display at position (0, 0)
226 "DP-5" = {
227 scale = 2.0;
228 mode = {
229 width = 5120;
230 height = 2160;
231 refresh = 120.0;
232 };
233 position = {
234 x = 0;
235 y = 0;
236 };
237 };
238 "DP-1" = {
239 scale = 2.0;
240 mode = {
241 width = 5120;
242 height = 2160;
243 refresh = 120.0;
244 };
245 position = {
246 x = 0;
247 y = 0;
248 };
249 };
250 "DP-2" = {
251 scale = 1.0;
252 mode = {
253 width = 5120;
254 height = 2160;
255 refresh = 120.0;
256 };
257 position = {
258 x = 0;
259 y = 0;
260 };
261 };
262 "DP-6" = {
263 scale = 2.0;
264 mode = {
265 width = 5120;
266 height = 2160;
267 refresh = 120.0;
268 };
269 position = {
270 x = 0;
271 y = 0;
272 };
273 };
274 "DP-7" = {
275 scale = 2.0;
276 mode = {
277 width = 5120;
278 height = 2160;
279 refresh = 120.0;
280 };
281 position = {
282 x = 0;
283 y = 0;
284 };
285 };
286 # Laptop display - secondary display positioned underneath
287 "eDP-1" = {
288 scale = 1.5;
289 mode = {
290 width = 2560;
291 height = 1600;
292 refresh = 165.0;
293 };
294 position = {
295 x = 0;
296 y = 1080; # Position underneath the external monitor (2160 / 2 scale = 1080 logical height)
297 };
298 };
299 };
300 spawn-at-startup = [
301 { command = [ "xwayland-satellite" ]; }
302 { command = [ "swww-daemon" ]; }
303 { command = [ "wl-paste --watch cliphist store" ]; }
304 ];
305 environment = {
306 DISPLAY = ":0";
307 };
308 };
309 };
310
311 # Allow unfree packages
312 nixpkgs.config.allowUnfree = true;
313
314 nixpkgs.config.permittedInsecurePackages = [
315 "libsoup-2.74.3"
316 ];
317
318 # Quickshell status bar
319 xdg.configFile."quickshell/shell.qml".source = ./quickshell/shell.qml;
320
321 systemd.user.services.quickshell = {
322 Unit = {
323 Description = "QuickShell status bar";
324 After = [ "graphical-session.target" ];
325 PartOf = [ "graphical-session.target" ];
326 };
327 Service = {
328 ExecStart = "${pkgs.quickshell}/bin/quickshell";
329 Restart = "on-failure";
330 RestartSec = 2;
331 };
332 Install = {
333 WantedBy = [ "graphical-session.target" ];
334 };
335 };
336
337 systemd.user.services.quickshell-reload = {
338 Unit = {
339 Description = "Reload QuickShell on wake or display change";
340 After = [
341 "quickshell.service"
342 "graphical-session.target"
343 ];
344 PartOf = [ "graphical-session.target" ];
345 };
346 Service = {
347 Type = "simple";
348 ExecStart = "${pkgs.writeShellScript "quickshell-reload" ''
349 LOCKFILE="/tmp/quickshell-reload.lock"
350
351 do_restart() {
352 (
353 ${pkgs.util-linux}/bin/flock -xn 200 || exit 0
354 sleep 2
355 ${pkgs.systemd}/bin/systemctl --user restart quickshell.service
356 sleep 3
357 ) 200>"$LOCKFILE"
358 }
359
360 # Sleep/wake monitor
361 ${pkgs.dbus}/bin/dbus-monitor --system \
362 "type='signal',interface='org.freedesktop.login1.Manager',member='PrepareForSleep'" 2>/dev/null | \
363 while IFS= read -r line; do
364 if [[ "$line" == *"boolean false"* ]]; then
365 do_restart
366 fi
367 done &
368
369 # Display hotplug monitor
370 ${pkgs.systemd}/bin/udevadm monitor --property --subsystem-match=drm 2>/dev/null | \
371 while IFS= read -r line; do
372 if [[ "$line" == *"HOTPLUG=1"* ]]; then
373 do_restart
374 fi
375 done &
376
377 wait
378 ''}";
379 Restart = "on-failure";
380 RestartSec = 5;
381 };
382 Install = {
383 WantedBy = [ "graphical-session.target" ];
384 };
385 };
386
387 programs.ssh = {
388 enable = true;
389 enableDefaultConfig = false;
390 matchBlocks = {
391 "*" = {
392 identityFile = [
393 "${config.home.homeDirectory}/.ssh/id_ed25519_sk_rk"
394 "${config.home.homeDirectory}/.ssh/id_rsa.pub"
395 ];
396 };
397 };
398 };
399
400 programs.awscli = {
401 enable = true;
402 settings = {
403 "default" = {
404 region = "us-east-1";
405 };
406 };
407 };
408
409 services.udiskie = {
410 enable = true;
411 tray = "auto";
412 automount = true;
413 };
414
415 services.mako = {
416 enable = true;
417 settings = {
418 border-radius = 8;
419 border-size = 2;
420 padding = "12";
421 margin = "12";
422 font = "BerkeleyMono Nerd Font 11";
423 on-button-left = "invoke-default-action";
424 on-button-right = "dismiss";
425 };
426 };
427
428 catppuccin = {
429 enable = true;
430 flavor = "frappe";
431 };
432
433 programs.fuzzel.enable = true;
434
435 programs.direnv.enable = true;
436
437 programs.atuin = {
438 enable = true;
439 enableFishIntegration = true;
440 daemon.enable = true;
441 settings = {
442 filter_mode_shell_up_key_binding = "session";
443 };
444 };
445
446 programs.zellij = {
447 enable = true;
448 settings = {
449 keybinds = {
450 unbind = [
451 "Ctrl q"
452 "Ctrl o"
453 ];
454 normal = {
455 "bind \"Ctrl m\"" = {
456 SwitchToMode = "Session";
457 };
458 };
459 };
460 pane_frames = false;
461 show_startup_tips = false;
462 ui = {
463 pane_frames.hide_session_name = true;
464 };
465 };
466 };
467
468 programs.zen-browser.enable = true;
469 # programs.swww.enable = true;
470 programs.zoxide = {
471 enable = true;
472 enableFishIntegration = true;
473 };
474
475 programs.obs-studio = {
476 enable = true;
477 plugins = with pkgs.obs-studio-plugins; [
478 obs-backgroundremoval
479 ];
480 };
481
482 # Program configurations
483 programs.git = {
484 enable = true;
485 settings = {
486 user = {
487 name = "seanaye";
488 email = "hello@seanaye.ca";
489 };
490 init.defaultBranch = "main";
491 commit.gpgSign = true;
492 gpg.format = "ssh";
493 user.signingKey = "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIOIgEteUEW06dnBHe2z8vNLwz2iMKe8bba6JgMmOUpcBAAAABHNzaDo= sean@framework16";
494 gpg.ssh.allowedSignersFile = "${config.home.homeDirectory}/.ssh/allowed_signers";
495 };
496 };
497 programs.jujutsu = {
498 enable = true;
499 settings = {
500 user = {
501 email = "hello@seanaye.ca";
502 name = "Sean Aye";
503 };
504 signing = {
505 sign-all = true;
506 behavior = "own";
507 backend = "ssh";
508 key = "${config.home.homeDirectory}/.ssh/id_ed25519_sk_rk";
509 backends.ssh.allowed-signers = "${config.home.homeDirectory}/.ssh/allowed_signers";
510 };
511 };
512 };
513
514 programs.home-manager.enable = true;
515
516 programs.fish = {
517 enable = true;
518 shellAliases = {
519 agenix = "agenix -i ~/.config/agenix/yubikey-identity.txt";
520 };
521 interactiveShellInit = ''
522 set fish_greeting
523 # Set 1Password SSH agent socket
524 set -gx SSH_AUTH_SOCK ${config.home.homeDirectory}/.1password/agent.sock
525 # Load 1Password CLI plugins
526 if test -f ~/.config/op/plugins.sh
527 source ~/.config/op/plugins.sh
528 end
529 # Show fastfetch when inside zellij
530 if set -q ZELLIJ
531 fastfetch --logo small
532 end
533
534 function y
535 set tmp (mktemp -t "yazi-cwd.XXXXXX")
536 yazi $argv --cwd-file="$tmp"
537 if read -z cwd < "$tmp"; and [ -n "$cwd" ]; and [ "$cwd" != "$PWD" ]
538 builtin cd -- "$cwd"
539 end
540 rm -f -- "$tmp"
541 end
542 '';
543 functions = {
544 s3edit = ''
545 set file (basename $argv[1])
546 set tmpfile /tmp/$file
547 aws s3 cp $argv[1] $tmpfile
548 and $EDITOR $tmpfile
549 and aws s3 cp $tmpfile $argv[1]
550 '';
551 };
552 };
553
554 programs.starship = {
555 enable = true;
556 enableFishIntegration = true;
557 };
558
559 programs.alacritty = {
560 enable = true;
561 settings = {
562 terminal.shell = {
563 program = "zellij";
564 args = [
565 "options"
566 "--on-force-close"
567 "detach"
568 ];
569 };
570 window = {
571 decorations = "none";
572 opacity = 0.9;
573 };
574 font = {
575 normal = {
576 family = "BerkeleyMono Nerd Font";
577 style = "Regular";
578 };
579 size = 12.0;
580 };
581 };
582
583 };
584
585 programs.helix = {
586 enable = true;
587 settings = {
588 editor = {
589 bufferline = "multiple";
590 file-picker = {
591 hidden = false;
592 git-ignore = true;
593 };
594 cursor-shape = {
595 insert = "bar";
596 normal = "block";
597 select = "underline";
598 };
599 line-number = "relative";
600 cursorline = true;
601 auto-format = true;
602 end-of-line-diagnostics = "hint";
603 soft-wrap = {
604 enable = true;
605 };
606 lsp = {
607 display-inlay-hints = true;
608 display-messages = true;
609 display-progress-messages = true;
610 };
611 inline-diagnostics = {
612 cursor-line = "hint";
613 };
614 };
615 keys = {
616 normal = {
617 esc = [
618 "keep_primary_selection"
619 "collapse_selection"
620 ];
621 };
622
623 };
624 };
625 languages = {
626
627 language-server.rust-analyzer = {
628 config = {
629 check = {
630 command = "clippy";
631 };
632 checkOnSave = true;
633 cargo = {
634 allFeatures = true;
635 };
636 };
637 };
638 language-server.deno-lsp = {
639 command = "deno";
640 args = [ "lsp" ];
641 config.deno.enable = true;
642 };
643
644 language = [
645 {
646 name = "html";
647 formatter = {
648 command = "prettier";
649 args = [
650 "--parser"
651 "html"
652 ];
653 };
654 }
655 {
656 name = "nix";
657 auto-format = true;
658 formatter = {
659 command = "${pkgs.nixfmt}/bin/nixfmt";
660 };
661 }
662 {
663 name = "kotlin";
664 auto-format = true;
665 }
666 {
667 name = "rust";
668 auto-format = true;
669 formatter = {
670 command = "rustfmt";
671 args = [
672 "--edition"
673 "2024"
674 ];
675 };
676 indent = {
677 tab-width = 4;
678 unit = "t";
679 };
680 }
681 {
682 name = "astro";
683 auto-format = true;
684 formatter = {
685 command = "npx";
686 args = [
687 "prettier"
688 "--plugin"
689 "prettier-plugin-astro"
690 "--parser"
691 "astro"
692 ];
693 };
694 }
695 {
696 name = "json";
697 auto-format = true;
698 }
699 {
700 name = "just";
701 auto-format = true;
702 formatter = {
703 command = "just";
704 args = [
705 "--justfile"
706 "/dev/stdin"
707 "--dump"
708 ];
709 };
710 }
711 {
712 name = "toml";
713 auto-format = true;
714 formatter = {
715 command = "taplo";
716 args = [
717 "format"
718 "-"
719 ];
720 };
721 }
722 # {
723 # name = "typescript";
724 # roots = [
725 # "deno.json"
726 # "deno.jsonc"
727 # ];
728 # file-types = [
729 # "ts"
730 # "tsx"
731 # ];
732 # auto-format = true;
733 # language-servers = [ "deno-lsp" ];
734 # }
735 ];
736 };
737 };
738
739 dconf.settings = {
740 "org/gnome/desktop/interface" = {
741 color-scheme = "prefer-dark";
742 enable-hot-corners = false;
743 };
744 };
745
746 # Font rendering configuration
747 fonts.fontconfig = {
748 enable = true;
749 defaultFonts = {
750 monospace = [ "BerkeleyMono Nerd Font" ];
751 sansSerif = [ "Noto Sans" ];
752 serif = [ "Noto Serif" ];
753 };
754 };
755
756 # Cursor configuration
757 home.pointerCursor = {
758 name = "Adwaita";
759 package = pkgs.adwaita-icon-theme;
760 size = 16;
761 x11.enable = true;
762 gtk.enable = true;
763 };
764
765 # Session variables
766 home.sessionVariables = {
767 EDITOR = "hx";
768 VISUAL = "hx";
769 SUDO_EDITOR = "hx";
770 SSH_AUTH_SOCK = "${config.home.homeDirectory}/.1password/agent.sock";
771 SSH_ASKPASS = "${pkgs.openssh-askpass}/bin/gnome-ssh-askpass3";
772 SSH_ASKPASS_REQUIRE = "prefer";
773 };
774
775 # SSH allowed signers for commit signature verification
776 home.file.".ssh/allowed_signers".text = ''
777 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=
778 hello@seanaye.ca sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAILdilHXHdAP/V8Zq28EzHKtLAMMaFPu4+1det2N50QfhAAAABHNzaDo= sean@framework16
779 '';
780
781 # Yubikey identity for agenix (not secret - just a reference to the hardware key)
782 home.file.".config/agenix/yubikey-identity.txt".text = ''
783 # Serial: 26930059, Slot: 1
784 # Name: agenix
785 # Recipient: age1yubikey1qw64ag5lzvn9ekrflu5ruj4a6ucycscl6ctk39fjzf76jptsay39z442pxv
786 AGE-PLUGIN-YUBIKEY-1304E5QVZZD74FKSP8FMCT
787 '';
788
789 # Same identity for sops (expects keys at this path by default)
790 home.file.".config/sops/age/keys.txt".text = ''
791 # Serial: 26930059, Slot: 1
792 # Name: agenix
793 # Recipient: age1yubikey1qw64ag5lzvn9ekrflu5ruj4a6ucycscl6ctk39fjzf76jptsay39z442pxv
794 AGE-PLUGIN-YUBIKEY-1304E5QVZZD74FKSP8FMCT
795 '';
796
797 home.file.".config/Yubico/u2f_keys".text = ''
798 sean:6sa1fnimjshdqKgadDlgQXqSXD6qQ7eSOZneMQZNAzO2OVCViQlxAZXHVf8kDOLKQ4uzcrHMj/t3889Sqi3Dyw==,1jm0HwRmNFFRMGu/DsVrwIBZc6HyNDSlvDhwQd73f0f3KWVxHo6PdSu4OUr+7GwOAfASKGakwyyetv73463CQw==,es256,+presence
799 '';
800
801 # Set the state version for Home Manager
802 home.stateVersion = "25.05";
803}