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+q".action = {
192 quit = { };
193 };
194 "Mod+Shift+r".action.spawn = [
195 "systemctl"
196 "--user"
197 "restart"
198 "quickshell.service"
199 ];
200 "XF86AudioPlay".action.spawn = [
201 "playerctl"
202 "play-pause"
203 ];
204 "XF86AudioStop".action.spawn = [
205 "playerctl"
206 "stop"
207 ];
208 "XF86AudioNext".action.spawn = [
209 "playerctl"
210 "next"
211 ];
212 "XF86AudioPrev".action.spawn = [
213 "playerctl"
214 "previous"
215 ];
216 "XF86MonBrightnessDown".action.spawn = [
217 "brightnessctl"
218 "set"
219 "5%-"
220 ];
221 "XF86MonBrightnessUp".action.spawn = [
222 "brightnessctl"
223 "set"
224 "+5%"
225 ];
226 };
227 outputs = {
228 # External monitor - primary display at position (0, 0)
229 "DP-5" = {
230 scale = 2.0;
231 mode = {
232 width = 5120;
233 height = 2160;
234 refresh = 120.0;
235 };
236 position = {
237 x = 0;
238 y = 0;
239 };
240 };
241 "DP-1" = {
242 scale = 2.0;
243 mode = {
244 width = 5120;
245 height = 2160;
246 refresh = 120.0;
247 };
248 position = {
249 x = 0;
250 y = 0;
251 };
252 };
253 "DP-2" = {
254 scale = 1.0;
255 mode = {
256 width = 5120;
257 height = 2160;
258 refresh = 120.0;
259 };
260 position = {
261 x = 0;
262 y = 0;
263 };
264 };
265 "DP-6" = {
266 scale = 2.0;
267 mode = {
268 width = 5120;
269 height = 2160;
270 refresh = 120.0;
271 };
272 position = {
273 x = 0;
274 y = 0;
275 };
276 };
277 "DP-7" = {
278 scale = 2.0;
279 mode = {
280 width = 5120;
281 height = 2160;
282 refresh = 120.0;
283 };
284 position = {
285 x = 0;
286 y = 0;
287 };
288 };
289 # Laptop display - secondary display positioned underneath
290 "eDP-1" = {
291 scale = 1.5;
292 mode = {
293 width = 2560;
294 height = 1600;
295 refresh = 165.0;
296 };
297 position = {
298 x = 0;
299 y = 1080; # Position underneath the external monitor (2160 / 2 scale = 1080 logical height)
300 };
301 };
302 };
303 spawn-at-startup = [
304 { command = [ "xwayland-satellite" ]; }
305 { command = [ "swww-daemon" ]; }
306 { command = [ "wl-paste --watch cliphist store" ]; }
307 ];
308 environment = {
309 DISPLAY = ":0";
310 };
311 };
312 };
313
314 # Allow unfree packages
315 nixpkgs.config.allowUnfree = true;
316
317 nixpkgs.config.permittedInsecurePackages = [
318 "libsoup-2.74.3"
319 ];
320
321 # Quickshell status bar
322 xdg.configFile."quickshell/shell.qml".source = ./quickshell/shell.qml;
323
324 systemd.user.services.quickshell = {
325 Unit = {
326 Description = "QuickShell status bar";
327 After = [ "graphical-session.target" ];
328 PartOf = [ "graphical-session.target" ];
329 };
330 Service = {
331 ExecStart = "${pkgs.quickshell}/bin/quickshell";
332 Restart = "on-failure";
333 RestartSec = 2;
334 };
335 Install = {
336 WantedBy = [ "graphical-session.target" ];
337 };
338 };
339
340 systemd.user.services.quickshell-reload = {
341 Unit = {
342 Description = "Reload QuickShell on wake or display change";
343 After = [
344 "quickshell.service"
345 "graphical-session.target"
346 ];
347 PartOf = [ "graphical-session.target" ];
348 };
349 Service = {
350 Type = "simple";
351 ExecStart = "${pkgs.writeShellScript "quickshell-reload" ''
352 LOCKFILE="/tmp/quickshell-reload.lock"
353
354 do_restart() {
355 (
356 ${pkgs.util-linux}/bin/flock -xn 200 || exit 0
357 sleep 2
358 ${pkgs.systemd}/bin/systemctl --user restart quickshell.service
359 sleep 3
360 ) 200>"$LOCKFILE"
361 }
362
363 # Sleep/wake monitor
364 ${pkgs.dbus}/bin/dbus-monitor --system \
365 "type='signal',interface='org.freedesktop.login1.Manager',member='PrepareForSleep'" 2>/dev/null | \
366 while IFS= read -r line; do
367 if [[ "$line" == *"boolean false"* ]]; then
368 do_restart
369 fi
370 done &
371
372 # Display hotplug monitor
373 ${pkgs.systemd}/bin/udevadm monitor --property --subsystem-match=drm 2>/dev/null | \
374 while IFS= read -r line; do
375 if [[ "$line" == *"HOTPLUG=1"* ]]; then
376 do_restart
377 fi
378 done &
379
380 wait
381 ''}";
382 Restart = "on-failure";
383 RestartSec = 5;
384 };
385 Install = {
386 WantedBy = [ "graphical-session.target" ];
387 };
388 };
389
390 programs.ssh = {
391 enable = true;
392 enableDefaultConfig = false;
393 matchBlocks = {
394 "*" = {
395 identityFile = [
396 "${config.home.homeDirectory}/.ssh/id_ed25519_sk_rk"
397 "${config.home.homeDirectory}/.ssh/id_rsa.pub"
398 ];
399 };
400 };
401 };
402
403 programs.awscli = {
404 enable = true;
405 settings = {
406 "default" = {
407 region = "us-east-1";
408 };
409 };
410 };
411
412 services.udiskie = {
413 enable = true;
414 tray = "auto";
415 automount = true;
416 };
417
418 services.mako = {
419 enable = true;
420 settings = {
421 border-radius = 8;
422 border-size = 2;
423 padding = "12";
424 margin = "12";
425 font = "BerkeleyMono Nerd Font 11";
426 on-button-left = "invoke-default-action";
427 on-button-right = "dismiss";
428 };
429 };
430
431 catppuccin = {
432 enable = true;
433 flavor = "frappe";
434 };
435
436 programs.fuzzel.enable = true;
437
438 programs.direnv.enable = true;
439
440 programs.atuin = {
441 enable = true;
442 enableFishIntegration = true;
443 daemon.enable = true;
444 settings = {
445 filter_mode_shell_up_key_binding = "session";
446 };
447 };
448
449 programs.zellij = {
450 enable = true;
451 settings = {
452 keybinds = {
453 unbind = [
454 "Ctrl q"
455 "Ctrl o"
456 ];
457 normal = {
458 "bind \"Ctrl m\"" = {
459 SwitchToMode = "Session";
460 };
461 };
462 };
463 pane_frames = false;
464 show_startup_tips = false;
465 ui = {
466 pane_frames.hide_session_name = true;
467 };
468 };
469 };
470
471 programs.zen-browser.enable = true;
472 # programs.swww.enable = true;
473 programs.zoxide = {
474 enable = true;
475 enableFishIntegration = true;
476 };
477
478 programs.obs-studio = {
479 enable = true;
480 plugins = with pkgs.obs-studio-plugins; [
481 obs-backgroundremoval
482 ];
483 };
484
485 # Program configurations
486 programs.git = {
487 enable = true;
488 settings = {
489 user = {
490 name = "seanaye";
491 email = "hello@seanaye.ca";
492 };
493 init.defaultBranch = "main";
494 commit.gpgSign = true;
495 gpg.format = "ssh";
496 user.signingKey = "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIOIgEteUEW06dnBHe2z8vNLwz2iMKe8bba6JgMmOUpcBAAAABHNzaDo= sean@framework16";
497 gpg.ssh.allowedSignersFile = "${config.home.homeDirectory}/.ssh/allowed_signers";
498 };
499 };
500 programs.jujutsu = {
501 enable = true;
502 settings = {
503 user = {
504 email = "hello@seanaye.ca";
505 name = "Sean Aye";
506 };
507 signing = {
508 sign-all = true;
509 behavior = "own";
510 backend = "ssh";
511 key = "${config.home.homeDirectory}/.ssh/id_ed25519_sk_rk";
512 backends.ssh.allowed-signers = "${config.home.homeDirectory}/.ssh/allowed_signers";
513 };
514 };
515 };
516
517 programs.home-manager.enable = true;
518
519 programs.fish = {
520 enable = true;
521 shellAliases = {
522 agenix = "agenix -i ~/.config/agenix/yubikey-identity.txt";
523 };
524 interactiveShellInit = ''
525 set fish_greeting
526 # Set 1Password SSH agent socket
527 set -gx SSH_AUTH_SOCK ${config.home.homeDirectory}/.1password/agent.sock
528 # Load 1Password CLI plugins
529 if test -f ~/.config/op/plugins.sh
530 source ~/.config/op/plugins.sh
531 end
532 # Show fastfetch when inside zellij
533 if set -q ZELLIJ
534 fastfetch --logo small
535 end
536
537 function y
538 set tmp (mktemp -t "yazi-cwd.XXXXXX")
539 yazi $argv --cwd-file="$tmp"
540 if read -z cwd < "$tmp"; and [ -n "$cwd" ]; and [ "$cwd" != "$PWD" ]
541 builtin cd -- "$cwd"
542 end
543 rm -f -- "$tmp"
544 end
545 '';
546 functions = {
547 s3edit = ''
548 set file (basename $argv[1])
549 set tmpfile /tmp/$file
550 aws s3 cp $argv[1] $tmpfile
551 and $EDITOR $tmpfile
552 and aws s3 cp $tmpfile $argv[1]
553 '';
554 };
555 };
556
557 programs.starship = {
558 enable = true;
559 enableFishIntegration = true;
560 };
561
562 programs.alacritty = {
563 enable = true;
564 settings = {
565 terminal.shell = {
566 program = "zellij";
567 args = [
568 "options"
569 "--on-force-close"
570 "detach"
571 ];
572 };
573 window = {
574 decorations = "none";
575 opacity = 0.9;
576 };
577 font = {
578 normal = {
579 family = "BerkeleyMono Nerd Font";
580 style = "Regular";
581 };
582 size = 12.0;
583 };
584 };
585
586 };
587
588 programs.helix = {
589 enable = true;
590 settings = {
591 editor = {
592 bufferline = "multiple";
593 file-picker = {
594 hidden = false;
595 git-ignore = true;
596 };
597 cursor-shape = {
598 insert = "bar";
599 normal = "block";
600 select = "underline";
601 };
602 line-number = "relative";
603 cursorline = true;
604 auto-format = true;
605 end-of-line-diagnostics = "hint";
606 soft-wrap = {
607 enable = true;
608 };
609 lsp = {
610 display-inlay-hints = true;
611 display-messages = true;
612 display-progress-messages = true;
613 };
614 inline-diagnostics = {
615 cursor-line = "hint";
616 };
617 };
618 keys = {
619 normal = {
620 esc = [
621 "keep_primary_selection"
622 "collapse_selection"
623 ];
624 };
625
626 };
627 };
628 languages = {
629
630 language-server.rust-analyzer = {
631 config = {
632 check = {
633 command = "clippy";
634 };
635 checkOnSave = true;
636 cargo = {
637 allFeatures = true;
638 };
639 };
640 };
641 language-server.deno-lsp = {
642 command = "deno";
643 args = [ "lsp" ];
644 config.deno.enable = true;
645 };
646
647 language = [
648 {
649 name = "html";
650 formatter = {
651 command = "prettier";
652 args = [
653 "--parser"
654 "html"
655 ];
656 };
657 }
658 {
659 name = "nix";
660 auto-format = true;
661 formatter = {
662 command = "${pkgs.nixfmt}/bin/nixfmt";
663 };
664 }
665 {
666 name = "kotlin";
667 auto-format = true;
668 }
669 {
670 name = "rust";
671 auto-format = true;
672 formatter = {
673 command = "rustfmt";
674 args = [
675 "--edition"
676 "2024"
677 ];
678 };
679 indent = {
680 tab-width = 4;
681 unit = "t";
682 };
683 }
684 {
685 name = "astro";
686 auto-format = true;
687 formatter = {
688 command = "npx";
689 args = [
690 "prettier"
691 "--plugin"
692 "prettier-plugin-astro"
693 "--parser"
694 "astro"
695 ];
696 };
697 }
698 {
699 name = "json";
700 auto-format = true;
701 }
702 {
703 name = "just";
704 auto-format = true;
705 formatter = {
706 command = "just";
707 args = [
708 "--justfile"
709 "/dev/stdin"
710 "--dump"
711 ];
712 };
713 }
714 {
715 name = "toml";
716 auto-format = true;
717 formatter = {
718 command = "taplo";
719 args = [
720 "format"
721 "-"
722 ];
723 };
724 }
725 # {
726 # name = "typescript";
727 # roots = [
728 # "deno.json"
729 # "deno.jsonc"
730 # ];
731 # file-types = [
732 # "ts"
733 # "tsx"
734 # ];
735 # auto-format = true;
736 # language-servers = [ "deno-lsp" ];
737 # }
738 ];
739 };
740 };
741
742 dconf.settings = {
743 "org/gnome/desktop/interface" = {
744 color-scheme = "prefer-dark";
745 enable-hot-corners = false;
746 };
747 };
748
749 # Font rendering configuration
750 fonts.fontconfig = {
751 enable = true;
752 defaultFonts = {
753 monospace = [ "BerkeleyMono Nerd Font" ];
754 sansSerif = [ "Noto Sans" ];
755 serif = [ "Noto Serif" ];
756 };
757 };
758
759 # Cursor configuration
760 home.pointerCursor = {
761 name = "Adwaita";
762 package = pkgs.adwaita-icon-theme;
763 size = 16;
764 x11.enable = true;
765 gtk.enable = true;
766 };
767
768 # Session variables
769 home.sessionVariables = {
770 EDITOR = "hx";
771 VISUAL = "hx";
772 SUDO_EDITOR = "hx";
773 SSH_AUTH_SOCK = "${config.home.homeDirectory}/.1password/agent.sock";
774 SSH_ASKPASS = "${pkgs.openssh-askpass}/bin/gnome-ssh-askpass3";
775 SSH_ASKPASS_REQUIRE = "prefer";
776 };
777
778 # SSH allowed signers for commit signature verification
779 home.file.".ssh/allowed_signers".text = ''
780 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=
781 hello@seanaye.ca sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAILdilHXHdAP/V8Zq28EzHKtLAMMaFPu4+1det2N50QfhAAAABHNzaDo= sean@framework16
782 '';
783
784 # Yubikey identity for agenix (not secret - just a reference to the hardware key)
785 home.file.".config/agenix/yubikey-identity.txt".text = ''
786 # Serial: 26930059, Slot: 1
787 # Name: agenix
788 # Recipient: age1yubikey1qw64ag5lzvn9ekrflu5ruj4a6ucycscl6ctk39fjzf76jptsay39z442pxv
789 AGE-PLUGIN-YUBIKEY-1304E5QVZZD74FKSP8FMCT
790 '';
791
792 # Same identity for sops (expects keys at this path by default)
793 home.file.".config/sops/age/keys.txt".text = ''
794 # Serial: 26930059, Slot: 1
795 # Name: agenix
796 # Recipient: age1yubikey1qw64ag5lzvn9ekrflu5ruj4a6ucycscl6ctk39fjzf76jptsay39z442pxv
797 AGE-PLUGIN-YUBIKEY-1304E5QVZZD74FKSP8FMCT
798 '';
799
800 home.file.".config/Yubico/u2f_keys".text = ''
801 sean:6sa1fnimjshdqKgadDlgQXqSXD6qQ7eSOZneMQZNAzO2OVCViQlxAZXHVf8kDOLKQ4uzcrHMj/t3889Sqi3Dyw==,1jm0HwRmNFFRMGu/DsVrwIBZc6HyNDSlvDhwQd73f0f3KWVxHo6PdSu4OUr+7GwOAfASKGakwyyetv73463CQw==,es256,+presence
802 '';
803
804 # Set the state version for Home Manager
805 home.stateVersion = "25.05";
806}