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