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