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 (import ../../packages/cclip.nix { inherit pkgs; }) # 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 # --- FONTS ARE IMPORTANT ---
72 # Berkeley Mono is the main system font, keeping JetBrains and Font Awesome for icons
73 font-awesome
74 noto-fonts
75 noto-fonts-cjk-sans
76 noto-fonts-color-emoji
77 nerd-fonts.jetbrains-mono
78 nerd-fonts.symbols-only
79 # --- POLKIT AGENT (for 1Password GUI, etc.) ---
80 lxqt.lxqt-policykit # Lightweight polkit agent
81 ];
82
83 programs.niri = {
84 enable = true;
85 settings = {
86 window-rules = [
87 {
88 geometry-corner-radius = {
89 top-left = 5.0;
90 top-right = 5.0;
91 bottom-left = 5.0;
92 bottom-right = 5.0;
93 };
94 clip-to-geometry = true;
95 draw-border-with-background = false;
96 }
97 ];
98 debug = {
99 honor-xdg-activation-with-invalid-serial = { };
100 };
101 layout = {
102 focus-ring = {
103 width = 2;
104 active.color = "#8caaee";
105 inactive.color = "#414559";
106 };
107 struts = {
108 top = -6;
109 bottom = -6;
110 left = 0;
111 right = 0;
112 };
113 gaps = 8;
114 };
115 gestures = {
116 hot-corners = {
117 enable = false;
118 };
119 };
120 binds = {
121 "Mod+d".action.spawn = "fuzzel";
122 "Mod+e".action.spawn = "bemoji";
123 "Mod+n".action.spawn = "networkmanager_dmenu";
124 "Mod+a".action.spawn = "alacritty";
125 "Mod+h".action = {
126 focus-column-left = { };
127 };
128 "Mod+j".action = {
129 focus-workspace-down = { };
130 };
131 "Mod+k".action = {
132 focus-workspace-up = { };
133 };
134 "Mod+l".action = {
135 focus-column-right = { };
136 };
137 "Mod+Shift+h".action = {
138 move-column-left = { };
139 };
140 "Mod+Shift+j".action = {
141 move-window-down-or-to-workspace-down = { };
142 };
143 "Mod+Shift+k".action = {
144 move-window-up-or-to-workspace-up = { };
145 };
146 "Mod+Shift+l".action = {
147 move-column-right = { };
148 };
149 "Mod+Down".action = {
150 move-workspace-down = { };
151 };
152 "Mod+Up".action = {
153 move-workspace-up = { };
154 };
155 "Mod+p".action = {
156 show-hotkey-overlay = { };
157 };
158 "Mod+o".action = {
159 toggle-overview = { };
160 };
161 "Mod+q".action = {
162 close-window = { };
163 };
164 "Mod+f".action = {
165 toggle-window-floating = { };
166 };
167 "Mod+Shift+f".action = {
168 switch-focus-between-floating-and-tiling = { };
169 };
170 "Mod+m".action = {
171 fullscreen-window = { };
172 };
173 "Mod+s".action = {
174 screenshot = {
175 show-pointer = true;
176 };
177 };
178 "Mod+1".action = {
179 set-column-width = "100%";
180 };
181 "Mod+2".action = {
182 set-column-width = "50%";
183 };
184 "Mod+Minus".action = {
185 set-column-width = "-10%";
186 };
187 "Mod+Equal".action = {
188 set-column-width = "+10%";
189 };
190 "Mod+Shift+q".action = {
191 quit = { };
192 };
193 "Mod+Shift+r".action.spawn = [
194 "systemctl"
195 "--user"
196 "restart"
197 "quickshell.service"
198 ];
199 "XF86AudioPlay".action.spawn = [
200 "playerctl"
201 "play-pause"
202 ];
203 "XF86AudioStop".action.spawn = [
204 "playerctl"
205 "stop"
206 ];
207 "XF86AudioNext".action.spawn = [
208 "playerctl"
209 "next"
210 ];
211 "XF86AudioPrev".action.spawn = [
212 "playerctl"
213 "previous"
214 ];
215 "XF86MonBrightnessDown".action.spawn = [
216 "brightnessctl"
217 "set"
218 "5%-"
219 ];
220 "XF86MonBrightnessUp".action.spawn = [
221 "brightnessctl"
222 "set"
223 "+5%"
224 ];
225 };
226 outputs = {
227 # External monitor - primary display at position (0, 0)
228 "DP-5" = {
229 scale = 2.0;
230 mode = {
231 width = 5120;
232 height = 2160;
233 refresh = 120.0;
234 };
235 position = {
236 x = 0;
237 y = 0;
238 };
239 };
240 "DP-1" = {
241 scale = 2.0;
242 mode = {
243 width = 5120;
244 height = 2160;
245 refresh = 120.0;
246 };
247 position = {
248 x = 0;
249 y = 0;
250 };
251 };
252 "DP-2" = {
253 scale = 1.0;
254 mode = {
255 width = 5120;
256 height = 2160;
257 refresh = 120.0;
258 };
259 position = {
260 x = 0;
261 y = 0;
262 };
263 };
264 "DP-6" = {
265 scale = 2.0;
266 mode = {
267 width = 5120;
268 height = 2160;
269 refresh = 120.0;
270 };
271 position = {
272 x = 0;
273 y = 0;
274 };
275 };
276 "DP-7" = {
277 scale = 2.0;
278 mode = {
279 width = 5120;
280 height = 2160;
281 refresh = 120.0;
282 };
283 position = {
284 x = 0;
285 y = 0;
286 };
287 };
288 # Laptop display - secondary display positioned underneath
289 "eDP-1" = {
290 scale = 1.5;
291 mode = {
292 width = 2560;
293 height = 1600;
294 refresh = 165.0;
295 };
296 position = {
297 x = 0;
298 y = 1080; # Position underneath the external monitor (2160 / 2 scale = 1080 logical height)
299 };
300 };
301 };
302 spawn-at-startup = [
303 { command = [ "xwayland-satellite" ]; }
304 { command = [ "swww-daemon" ]; }
305 { command = [ "cclipd" ]; }
306 ];
307 environment = {
308 DISPLAY = ":0";
309 };
310 };
311 };
312
313 # Allow unfree packages
314 nixpkgs.config.allowUnfree = true;
315
316 nixpkgs.config.permittedInsecurePackages = [
317 "libsoup-2.74.3"
318 ];
319
320 # Quickshell status bar
321 xdg.configFile."quickshell/shell.qml".source = ./quickshell/shell.qml;
322
323 systemd.user.services.quickshell = {
324 Unit = {
325 Description = "QuickShell status bar";
326 After = [ "graphical-session.target" ];
327 PartOf = [ "graphical-session.target" ];
328 };
329 Service = {
330 ExecStart = "${pkgs.quickshell}/bin/quickshell";
331 Restart = "on-failure";
332 RestartSec = 2;
333 };
334 Install = {
335 WantedBy = [ "graphical-session.target" ];
336 };
337 };
338
339 systemd.user.services.quickshell-reload = {
340 Unit = {
341 Description = "Reload QuickShell on wake or display change";
342 After = [
343 "quickshell.service"
344 "graphical-session.target"
345 ];
346 PartOf = [ "graphical-session.target" ];
347 };
348 Service = {
349 Type = "simple";
350 ExecStart = "${pkgs.writeShellScript "quickshell-reload" ''
351 LOCKFILE="/tmp/quickshell-reload.lock"
352
353 do_restart() {
354 (
355 ${pkgs.util-linux}/bin/flock -xn 200 || exit 0
356 sleep 2
357 ${pkgs.systemd}/bin/systemctl --user restart quickshell.service
358 sleep 3
359 ) 200>"$LOCKFILE"
360 }
361
362 # Sleep/wake monitor
363 ${pkgs.dbus}/bin/dbus-monitor --system \
364 "type='signal',interface='org.freedesktop.login1.Manager',member='PrepareForSleep'" 2>/dev/null | \
365 while IFS= read -r line; do
366 if [[ "$line" == *"boolean false"* ]]; then
367 do_restart
368 fi
369 done &
370
371 # Display hotplug monitor
372 ${pkgs.systemd}/bin/udevadm monitor --property --subsystem-match=drm 2>/dev/null | \
373 while IFS= read -r line; do
374 if [[ "$line" == *"HOTPLUG=1"* ]]; then
375 do_restart
376 fi
377 done &
378
379 wait
380 ''}";
381 Restart = "on-failure";
382 RestartSec = 5;
383 };
384 Install = {
385 WantedBy = [ "graphical-session.target" ];
386 };
387 };
388
389 programs.ssh = {
390 enable = true;
391 enableDefaultConfig = false;
392 matchBlocks = {
393 "*" = {
394 identityFile = [
395 "${config.home.homeDirectory}/.ssh/id_ed25519_sk_rk"
396 "${config.home.homeDirectory}/.ssh/id_rsa.pub"
397 ];
398 };
399 };
400 };
401
402 programs.awscli = {
403 enable = true;
404 settings = {
405 "default" = {
406 region = "us-east-1";
407 };
408 };
409 };
410
411 services.udiskie = {
412 enable = true;
413 tray = "auto";
414 automount = true;
415 };
416
417 services.mako = {
418 enable = true;
419 settings = {
420 border-radius = 8;
421 border-size = 2;
422 padding = "12";
423 margin = "12";
424 font = "BerkeleyMono Nerd Font 11";
425 on-button-left = "invoke-default-action";
426 on-button-right = "dismiss";
427 };
428 };
429
430 catppuccin = {
431 enable = true;
432 flavor = "frappe";
433 };
434
435 programs.fuzzel.enable = true;
436
437 programs.direnv.enable = true;
438
439 programs.atuin = {
440 enable = true;
441 enableFishIntegration = true;
442 daemon.enable = true;
443 settings = {
444 filter_mode_shell_up_key_binding = "session";
445 };
446 };
447
448 programs.zellij = {
449 enable = true;
450 settings = {
451 keybinds = {
452 unbind = [
453 "Ctrl q"
454 "Ctrl o"
455 ];
456 normal = {
457 "bind \"Ctrl m\"" = {
458 SwitchToMode = "Session";
459 };
460 };
461 };
462 pane_frames = false;
463 show_startup_tips = false;
464 ui = {
465 pane_frames.hide_session_name = true;
466 };
467 };
468 };
469
470 programs.zen-browser.enable = true;
471 # programs.swww.enable = true;
472 programs.zoxide = {
473 enable = true;
474 enableFishIntegration = true;
475 };
476
477 programs.obs-studio = {
478 enable = true;
479 plugins = with pkgs.obs-studio-plugins; [
480 obs-backgroundremoval
481 ];
482 };
483
484 # Program configurations
485 programs.git = {
486 enable = true;
487 settings = {
488 user = {
489 name = "seanaye";
490 email = "hello@seanaye.ca";
491 };
492 init.defaultBranch = "main";
493 commit.gpgSign = true;
494 gpg.format = "ssh";
495 user.signingKey = "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIOIgEteUEW06dnBHe2z8vNLwz2iMKe8bba6JgMmOUpcBAAAABHNzaDo= sean@framework16";
496 gpg.ssh.allowedSignersFile = "${config.home.homeDirectory}/.ssh/allowed_signers";
497 };
498 };
499 programs.jujutsu = {
500 enable = true;
501 settings = {
502 user = {
503 email = "hello@seanaye.ca";
504 name = "Sean Aye";
505 };
506 signing = {
507 sign-all = true;
508 behavior = "own";
509 backend = "ssh";
510 key = "${config.home.homeDirectory}/.ssh/id_ed25519_sk_rk";
511 backends.ssh.allowed-signers = "${config.home.homeDirectory}/.ssh/allowed_signers";
512 };
513 };
514 };
515
516 programs.home-manager.enable = true;
517
518 programs.fish = {
519 enable = true;
520 shellAliases = {
521 agenix = "agenix -i ~/.config/agenix/yubikey-identity.txt";
522 };
523 interactiveShellInit = ''
524 set fish_greeting
525 # Set 1Password SSH agent socket
526 set -gx SSH_AUTH_SOCK ${config.home.homeDirectory}/.1password/agent.sock
527 # Load 1Password CLI plugins
528 if test -f ~/.config/op/plugins.sh
529 source ~/.config/op/plugins.sh
530 end
531 # Auto-launch zellij if not already inside a session
532 if not set -q ZELLIJ
533 zellij
534 else
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.program = "fish";
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}