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