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