me like nix
1{
2 pkgs,
3 inputs,
4 config,
5 ...
6}:
7
8let
9 zjctl = import ../../packages/zjctl.nix { inherit pkgs; };
10 zrpc-wasm = import ../../packages/zrpc-wasm.nix { inherit pkgs; };
11in
12{
13 # Import the home-manager modules you want to use
14 imports = [
15 inputs.catppuccin.homeModules.catppuccin
16 inputs.niri.homeModules.niri
17 inputs.zen-browser.homeModules.beta
18 inputs.agenix.homeManagerModules.default
19 ];
20
21 # All your user-specific packages
22 home.packages = with pkgs; [
23 helix
24 git
25 jujutsu # jj-cli
26 htop
27 iotop
28 ncdu
29 youtube-tui
30 yt-dlp # youtube-tui and mpv need this to resolve YouTube URLs
31 zellij # terminal multiplexer
32 alacritty
33 inputs.fsel.packages.${pkgs.system}.default # App launcher / fuzzy finder
34 inputs.mako-tui.packages.${pkgs.system}.default # Notification TUI
35 bemoji # emoji picker
36 networkmanager_dmenu # network picker for fuzzel
37 quickshell # Status bar (QML-based)
38 inputs.kaleidux.packages.${pkgs.system}.default # Dynamic wallpaper daemon
39 (import ../../packages/cclip.nix { inherit pkgs; }) # Clipboard history manager
40 pavucontrol # GUI for PulseAudio/PipeWire volume control
41 playerctl # MPRIS media player control
42 (element-desktop.override {
43 commandLineArgs = "--password-store=gnome-libsecret";
44 })
45 fd
46 ripgrep
47 opencode
48 yazi # tui file browser
49 gh # github cli
50 forgejo-cli
51 gh-dash # github dashboard TUI
52 diffnav # git diff viewer
53 signal-desktop
54 xwayland-satellite # for running x11 apps
55 nixfmt # nix formatter
56 nil # nix language server
57 atac # postman-like TUI
58 trippy # network analyzer
59 rsync # file sync utility
60 udiskie # for mounting external drives
61 darktable # photo editing
62 zoxide
63 chromium
64 claude-code
65 libnotify # for notify-send (claude-notify)
66 zjctl # Programmatic Zellij control
67 (pkgs.writeShellScriptBin "claude-notify" ''
68 PANE_ID="$ZELLIJ_PANE_ID"
69 SESSION="$ZELLIJ_SESSION_NAME"
70 ID_FILE="/tmp/claude-notify-''${PANE_ID}"
71 (
72 {
73 read -r NOTIFY_ID
74 echo "$NOTIFY_ID" > "$ID_FILE"
75 while read -r line; do
76 if [ "$line" = "default" ]; then
77 WIN_ID=$(niri msg windows | ${pkgs.gnugrep}/bin/grep -B1 "$SESSION" | ${pkgs.gnugrep}/bin/grep -oP '(?<=Window ID )\d+')
78 if [ -n "$WIN_ID" ]; then
79 niri msg action focus-window --id "$WIN_ID"
80 fi
81 zjctl pane focus --pane "id:terminal:$PANE_ID"
82 fi
83 done
84 } < <(${pkgs.libnotify}/bin/notify-send "Claude Code" "Waiting for your approval" \
85 --app-name=claude-code \
86 --action=default=Open \
87 --print-id \
88 --wait)
89 rm -f "$ID_FILE"
90 ) &
91 '')
92 (pkgs.writeShellScriptBin "claude-notify-clear" ''
93 PANE_ID="$ZELLIJ_PANE_ID"
94 ID_FILE="/tmp/claude-notify-''${PANE_ID}"
95 if [ -f "$ID_FILE" ]; then
96 NOTIFY_ID=$(cat "$ID_FILE")
97 ${pkgs.dbus}/bin/dbus-send --session \
98 --print-reply \
99 --dest=org.freedesktop.Notifications \
100 --type=method_call \
101 /org/freedesktop/Notifications \
102 org.freedesktop.Notifications.CloseNotification \
103 "uint32:$NOTIFY_ID" >/dev/null 2>&1
104 rm -f "$ID_FILE"
105 fi
106 '')
107 nautilus # file browser
108 sqlitebrowser
109 gnome-characters # symbol picker
110 sendme # file transfer
111 desktop-file-utils # for managing .desktop files
112 flyctl # fly.io cli
113 vscode-json-languageserver
114 gnome-network-displays
115 rainfrog # db tui
116 loupe # image viewer
117 glycin-loaders # various format loaders for loupe
118 docker-compose
119 discord
120 mangohud
121 prismlauncher # minecraft launcher
122 fastfetch
123 inputs.agenix.packages.${pkgs.system}.default # agenix CLI
124 age-plugin-yubikey # Yubikey support for agenix
125 # --- FONTS ARE IMPORTANT ---
126 # Berkeley Mono is the main system font, keeping JetBrains and Font Awesome for icons
127 font-awesome
128 noto-fonts
129 noto-fonts-cjk-sans
130 noto-fonts-color-emoji
131 nerd-fonts.jetbrains-mono
132 nerd-fonts.symbols-only
133 # --- POLKIT AGENT (for 1Password GUI, etc.) ---
134 lxqt.lxqt-policykit # Lightweight polkit agent
135 ];
136
137 programs.niri = {
138 enable = true;
139 settings = {
140 window-rules = [
141 {
142 geometry-corner-radius = {
143 top-left = 5.0;
144 top-right = 5.0;
145 bottom-left = 5.0;
146 bottom-right = 5.0;
147 };
148 clip-to-geometry = true;
149 draw-border-with-background = false;
150 }
151 {
152 matches = [ { app-id = "^fsel$"; } ];
153 open-floating = true;
154 default-column-width.fixed = 800;
155 default-window-height.fixed = 500;
156 }
157 {
158 matches = [ { app-id = "^mako-tui$"; } ];
159 open-floating = true;
160 default-column-width.fixed = 800;
161 default-window-height.fixed = 500;
162 }
163 ];
164 debug = {
165 honor-xdg-activation-with-invalid-serial = { };
166 };
167 layout = {
168 focus-ring = {
169 width = 2;
170 active.color = "#8caaee";
171 inactive.color = "#414559";
172 };
173 struts = {
174 top = -6;
175 bottom = -6;
176 left = 0;
177 right = 0;
178 };
179 gaps = 8;
180 };
181 gestures = {
182 hot-corners = {
183 enable = false;
184 };
185 };
186 binds = {
187 "Mod+d".action.spawn = [
188 "alacritty"
189 "--class"
190 "fsel"
191 "-e"
192 "fsel"
193 "--detach"
194 ];
195 "Mod+c".action.spawn = [
196 "alacritty"
197 "--class"
198 "fsel"
199 "-e"
200 "fsel"
201 "--cclip"
202 ];
203 "Mod+e".action.spawn = "bemoji";
204 "Mod+n".action.spawn = [
205 "alacritty"
206 "--class"
207 "mako-tui"
208 "-e"
209 "mako-tui"
210 ];
211 "Mod+a".action.spawn = "alacritty";
212 "Mod+h".action = {
213 focus-column-left = { };
214 };
215 "Mod+j".action = {
216 focus-workspace-down = { };
217 };
218 "Mod+k".action = {
219 focus-workspace-up = { };
220 };
221 "Mod+l".action = {
222 focus-column-right = { };
223 };
224 "Mod+Shift+h".action = {
225 move-column-left = { };
226 };
227 "Mod+Shift+j".action = {
228 move-window-down-or-to-workspace-down = { };
229 };
230 "Mod+Shift+k".action = {
231 move-window-up-or-to-workspace-up = { };
232 };
233 "Mod+Shift+l".action = {
234 move-column-right = { };
235 };
236 "Mod+Down".action = {
237 move-workspace-down = { };
238 };
239 "Mod+Up".action = {
240 move-workspace-up = { };
241 };
242 "Mod+p".action = {
243 show-hotkey-overlay = { };
244 };
245 "Mod+o".action = {
246 toggle-overview = { };
247 };
248 "Mod+q".action = {
249 close-window = { };
250 };
251 "Mod+f".action = {
252 toggle-window-floating = { };
253 };
254 "Mod+Shift+f".action = {
255 switch-focus-between-floating-and-tiling = { };
256 };
257 "Mod+m".action = {
258 fullscreen-window = { };
259 };
260 "Mod+s".action = {
261 screenshot = {
262 show-pointer = true;
263 };
264 };
265 "Mod+1".action = {
266 set-column-width = "100%";
267 };
268 "Mod+2".action = {
269 set-column-width = "50%";
270 };
271 "Mod+Minus".action = {
272 set-column-width = "-10%";
273 };
274 "Mod+Equal".action = {
275 set-column-width = "+10%";
276 };
277 "Mod+Shift+q".action = {
278 quit = { };
279 };
280 "Mod+Shift+r".action.spawn = [
281 "systemctl"
282 "--user"
283 "restart"
284 "quickshell.service"
285 ];
286 "XF86AudioPlay".action.spawn = [
287 "playerctl"
288 "play-pause"
289 ];
290 "XF86AudioStop".action.spawn = [
291 "playerctl"
292 "stop"
293 ];
294 "XF86AudioNext".action.spawn = [
295 "playerctl"
296 "next"
297 ];
298 "XF86AudioPrev".action.spawn = [
299 "playerctl"
300 "previous"
301 ];
302 "XF86MonBrightnessDown".action.spawn = [
303 "brightnessctl"
304 "set"
305 "5%-"
306 ];
307 "XF86MonBrightnessUp".action.spawn = [
308 "brightnessctl"
309 "set"
310 "+5%"
311 ];
312 };
313 outputs = {
314 # External monitor - primary display at position (0, 0)
315 "DP-5" = {
316 scale = 2.0;
317 mode = {
318 width = 5120;
319 height = 2160;
320 refresh = 120.0;
321 };
322 position = {
323 x = 0;
324 y = 0;
325 };
326 };
327 "DP-1" = {
328 scale = 2.0;
329 mode = {
330 width = 5120;
331 height = 2160;
332 refresh = 120.0;
333 };
334 position = {
335 x = 0;
336 y = 0;
337 };
338 };
339 "DP-2" = {
340 scale = 1.0;
341 mode = {
342 width = 5120;
343 height = 2160;
344 refresh = 120.0;
345 };
346 position = {
347 x = 0;
348 y = 0;
349 };
350 };
351 "DP-6" = {
352 scale = 2.0;
353 mode = {
354 width = 5120;
355 height = 2160;
356 refresh = 120.0;
357 };
358 position = {
359 x = 0;
360 y = 0;
361 };
362 };
363 "DP-7" = {
364 scale = 2.0;
365 mode = {
366 width = 5120;
367 height = 2160;
368 refresh = 120.0;
369 };
370 position = {
371 x = 0;
372 y = 0;
373 };
374 };
375 # Laptop display - secondary display positioned underneath
376 "eDP-1" = {
377 scale = 1.5;
378 mode = {
379 width = 2560;
380 height = 1600;
381 refresh = 165.0;
382 };
383 position = {
384 x = 0;
385 y = 1080; # Position underneath the external monitor (2160 / 2 scale = 1080 logical height)
386 };
387 };
388 };
389 spawn-at-startup = [
390 { command = [ "xwayland-satellite" ]; }
391 { command = [ "cclipd" ]; }
392 ];
393 environment = {
394 DISPLAY = ":0";
395 };
396 };
397 };
398
399 # Allow unfree packages
400 nixpkgs.config.allowUnfree = true;
401
402 nixpkgs.config.permittedInsecurePackages = [
403 "libsoup-2.74.3"
404 ];
405
406 # Download wallpapers at activation time (skips dead URLs gracefully)
407 home.activation.downloadWallpapers =
408 let
409 wallpapers = import ./wallpapers.nix;
410 downloads = builtins.concatStringsSep "\n" (
411 map (wp: ''
412 if [ ! -f "$DIR/${wp.filename}" ]; then
413 echo "Downloading ${wp.filename}..."
414 ${pkgs.curl}/bin/curl -fsSL -o "$DIR/${wp.filename}" ${
415 builtins.replaceStrings [ "\"" ] [ "\\\"" ] wp.url
416 } || echo "WARNING: Failed to download ${wp.filename}, skipping"
417 fi
418 '') wallpapers
419 );
420 in
421 config.lib.dag.entryAfter [ "writeBoundary" ] ''
422 DIR="${config.home.homeDirectory}/Pictures/Wallpapers"
423 mkdir -p "$DIR"
424 ${downloads}
425 '';
426
427 # Kaleidux wallpaper daemon config
428 xdg.configFile."kaleidux/config.toml".text = ''
429 [global]
430 monitor-behavior = "independent"
431 video-ratio = 50
432 sorting = "loveit"
433 transition-time = 1000
434
435 [any]
436 path = "${config.home.homeDirectory}/Pictures/Wallpapers"
437 duration = "15m"
438 transition = { type = "fade" }
439 '';
440
441 # Quickshell status bar
442 xdg.configFile."quickshell" = {
443 source = ./quickshell;
444 recursive = true;
445 };
446
447 systemd.user.services.kaleidux = {
448 Unit = {
449 Description = "Kaleidux dynamic wallpaper daemon";
450 After = [ "graphical-session.target" ];
451 PartOf = [ "graphical-session.target" ];
452 };
453 Service = {
454 ExecStart = "${inputs.kaleidux.packages.${pkgs.system}.default}/bin/kaleidux-daemon";
455 Restart = "on-failure";
456 RestartSec = 2;
457 };
458 Install = {
459 WantedBy = [ "graphical-session.target" ];
460 };
461 };
462
463 systemd.user.services.quickshell = {
464 Unit = {
465 Description = "QuickShell status bar";
466 After = [ "graphical-session.target" ];
467 PartOf = [ "graphical-session.target" ];
468 };
469 Service = {
470 ExecStart = "${pkgs.quickshell}/bin/quickshell";
471 Restart = "on-failure";
472 RestartSec = 2;
473 };
474 Install = {
475 WantedBy = [ "graphical-session.target" ];
476 };
477 };
478
479 systemd.user.services.quickshell-reload = {
480 Unit = {
481 Description = "Reload QuickShell on wake or display change";
482 After = [
483 "quickshell.service"
484 "graphical-session.target"
485 ];
486 PartOf = [ "graphical-session.target" ];
487 };
488 Service = {
489 Type = "simple";
490 ExecStart = "${pkgs.writeShellScript "quickshell-reload" ''
491 LOCKFILE="/tmp/quickshell-reload.lock"
492
493 do_restart() {
494 (
495 ${pkgs.util-linux}/bin/flock -xn 200 || exit 0
496 sleep 2
497 ${pkgs.systemd}/bin/systemctl --user restart quickshell.service
498 sleep 3
499 ) 200>"$LOCKFILE"
500 }
501
502 # Sleep/wake monitor
503 ${pkgs.dbus}/bin/dbus-monitor --system \
504 "type='signal',interface='org.freedesktop.login1.Manager',member='PrepareForSleep'" 2>/dev/null | \
505 while IFS= read -r line; do
506 if [[ "$line" == *"boolean false"* ]]; then
507 do_restart
508 fi
509 done &
510
511 # Display hotplug monitor
512 ${pkgs.systemd}/bin/udevadm monitor --property --subsystem-match=drm 2>/dev/null | \
513 while IFS= read -r line; do
514 if [[ "$line" == *"HOTPLUG=1"* ]]; then
515 do_restart
516 fi
517 done &
518
519 wait
520 ''}";
521 Restart = "on-failure";
522 RestartSec = 5;
523 };
524 Install = {
525 WantedBy = [ "graphical-session.target" ];
526 };
527 };
528
529 programs.ssh = {
530 enable = true;
531 enableDefaultConfig = false;
532 matchBlocks = {
533 "*" = {
534 identityFile = [
535 "${config.home.homeDirectory}/.ssh/id_ed25519_sk_rk"
536 "${config.home.homeDirectory}/.ssh/id_rsa.pub"
537 ];
538 };
539 };
540 };
541
542 programs.awscli = {
543 enable = true;
544 settings = {
545 "default" = {
546 region = "us-east-1";
547 };
548 };
549 };
550
551 services.udiskie = {
552 enable = true;
553 tray = "never";
554 automount = true;
555 };
556
557 services.mako = {
558 enable = true;
559 settings = {
560 border-radius = 8;
561 border-size = 2;
562 padding = "12";
563 margin = "12";
564 font = "BerkeleyMono Nerd Font 11";
565 on-button-left = "invoke-default-action";
566 on-button-right = "dismiss";
567 };
568 };
569
570 catppuccin = {
571 enable = true;
572 flavor = "frappe";
573 };
574
575 programs.direnv.enable = true;
576
577 programs.atuin = {
578 enable = true;
579 enableFishIntegration = true;
580 daemon.enable = true;
581 settings = {
582 filter_mode_shell_up_key_binding = "session";
583 };
584 };
585
586 programs.zellij = {
587 enable = true;
588 settings = {
589 keybinds = {
590 unbind = [
591 "Ctrl q"
592 "Ctrl o"
593 ];
594 normal = {
595 "bind \"Ctrl m\"" = {
596 SwitchToMode = "Session";
597 };
598 };
599 };
600 load_plugins = {
601 "\"file:~/.config/zellij/plugins/zrpc.wasm\"" = { };
602 };
603 pane_frames = false;
604 show_startup_tips = false;
605 ui = {
606 pane_frames.hide_session_name = true;
607 };
608 };
609 };
610
611 xdg.configFile."zellij/plugins/zrpc.wasm".source = "${zrpc-wasm}/zrpc.wasm";
612
613 home.file.".claude/settings.json".text = builtins.toJSON {
614 enabledPlugins = {
615 "rust-analyzer-lsp@claude-plugins-official" = true;
616 "typescript-lsp@claude-plugins-official" = true;
617 };
618 hooks = {
619 Notification = [
620 {
621 matcher = "permission_prompt";
622 hooks = [
623 {
624 type = "command";
625 command = "claude-notify";
626 }
627 ];
628 }
629 ];
630 Stop = [
631 {
632 hooks = [
633 {
634 type = "command";
635 command = "claude-notify-clear";
636 }
637 ];
638 }
639 ];
640 };
641 };
642
643 xdg.configFile."zellij/layouts/split.kdl".text = ''
644 layout {
645 tab {
646 pane size="50%"
647 pane split_direction="vertical" size="50%" {
648 pane
649 pane
650 }
651 }
652 }
653 '';
654
655 xdg.configFile."gh-dash/config.yml".text = ''
656 prSections:
657 - title: My Pull Requests
658 filters: is:open author:@me
659 - title: Review Requested
660 filters: is:open review-requested:@me
661 issuesSections:
662 - title: My Issues
663 filters: is:open author:@me
664 pager:
665 diff: diffnav
666 keybindings:
667 prs:
668 - key: T
669 name: enhance
670 command: >-
671 zellij run -- gh enhance -R {{.RepoName}} {{.PrNumber}}
672 '';
673
674 programs.zen-browser.enable = true;
675 # programs.swww.enable = true;
676 programs.zoxide = {
677 enable = true;
678 enableFishIntegration = true;
679 };
680
681 programs.obs-studio = {
682 enable = true;
683 plugins = with pkgs.obs-studio-plugins; [
684 obs-backgroundremoval
685 ];
686 };
687
688 # Program configurations
689 programs.mpv = {
690 enable = true;
691 scripts = [ pkgs.mpvScripts.mpris ];
692 config.af = "loudnorm=I=-16:TP=-1.5:LRA=11";
693 };
694 xdg.configFile."youtube-tui/commands.yml".source = ./youtube-tui/commands.yml;
695 programs.git = {
696 enable = true;
697 settings = {
698 user = {
699 name = "seanaye";
700 email = "hello@seanaye.ca";
701 };
702 init.defaultBranch = "main";
703 commit.gpgSign = true;
704 gpg.format = "ssh";
705 user.signingKey = "sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIOIgEteUEW06dnBHe2z8vNLwz2iMKe8bba6JgMmOUpcBAAAABHNzaDo= sean@framework16";
706 gpg.ssh.allowedSignersFile = "${config.home.homeDirectory}/.ssh/allowed_signers";
707 diff.tool = "diffnav";
708 difftool.prompt = false;
709 "difftool \"diffnav\"".cmd = "diffnav \"$LOCAL\" \"$REMOTE\"";
710 };
711 };
712 programs.jujutsu = {
713 enable = true;
714 settings = {
715 user = {
716 email = "hello@seanaye.ca";
717 name = "Sean Aye";
718 };
719 ui."diff-formatter" = ":git";
720 signing = {
721 sign-all = true;
722 behavior = "own";
723 backend = "ssh";
724 key = "${config.home.homeDirectory}/.ssh/id_ed25519_sk_rk";
725 backends.ssh.allowed-signers = "${config.home.homeDirectory}/.ssh/allowed_signers";
726 };
727 };
728 };
729 xdg.configFile."jj/conf.d/diffnav.toml".text = ''
730 [[--scope]]
731 --when.commands = ["diff", "show"]
732 [--scope.ui]
733 pager = "diffnav"
734 '';
735
736 programs.home-manager.enable = true;
737
738 programs.fish = {
739 enable = true;
740 shellAliases = {
741 agenix = "agenix -i ~/.config/agenix/yubikey-identity.txt";
742 };
743 interactiveShellInit = ''
744 set fish_greeting
745 # Set 1Password SSH agent socket
746 set -gx SSH_AUTH_SOCK ${config.home.homeDirectory}/.1password/agent.sock
747 # Load 1Password CLI plugins
748 if test -f ~/.config/op/plugins.sh
749 source ~/.config/op/plugins.sh
750 end
751 # Auto-launch zellij if not already inside a session
752 if not set -q ZELLIJ
753 zellij
754 else
755 fastfetch --logo small
756 end
757
758 function y
759 set tmp (mktemp -t "yazi-cwd.XXXXXX")
760 yazi $argv --cwd-file="$tmp"
761 if read -z cwd < "$tmp"; and [ -n "$cwd" ]; and [ "$cwd" != "$PWD" ]
762 builtin cd -- "$cwd"
763 end
764 rm -f -- "$tmp"
765 end
766 '';
767 functions = {
768 s3edit = ''
769 set file (basename $argv[1])
770 set tmpfile /tmp/$file
771 aws s3 cp $argv[1] $tmpfile
772 and $EDITOR $tmpfile
773 and aws s3 cp $tmpfile $argv[1]
774 '';
775 };
776 };
777
778 programs.starship = {
779 enable = true;
780 enableFishIntegration = true;
781 };
782
783 programs.alacritty = {
784 enable = true;
785 settings = {
786 terminal.shell.program = "fish";
787 window = {
788 decorations = "none";
789 opacity = 0.9;
790 };
791 font = {
792 normal = {
793 family = "BerkeleyMono Nerd Font";
794 style = "Regular";
795 };
796 size = 12.0;
797 };
798 };
799
800 };
801
802 programs.helix = {
803 enable = true;
804 settings = {
805 editor = {
806 bufferline = "multiple";
807 file-picker = {
808 hidden = false;
809 git-ignore = true;
810 };
811 cursor-shape = {
812 insert = "bar";
813 normal = "block";
814 select = "underline";
815 };
816 line-number = "relative";
817 cursorline = true;
818 auto-format = true;
819 end-of-line-diagnostics = "hint";
820 soft-wrap = {
821 enable = true;
822 };
823 lsp = {
824 display-inlay-hints = true;
825 display-messages = true;
826 display-progress-messages = true;
827 };
828 inline-diagnostics = {
829 cursor-line = "hint";
830 };
831 };
832 keys = {
833 normal = {
834 esc = [
835 "keep_primary_selection"
836 "collapse_selection"
837 ];
838 };
839
840 };
841 };
842 languages = {
843
844 language-server.rust-analyzer = {
845 config = {
846 check = {
847 command = "clippy";
848 };
849 checkOnSave = true;
850 cargo = {
851 allFeatures = true;
852 };
853 };
854 };
855 language-server.deno-lsp = {
856 command = "deno";
857 args = [ "lsp" ];
858 config.deno.enable = true;
859 };
860
861 language = [
862 {
863 name = "html";
864 formatter = {
865 command = "prettier";
866 args = [
867 "--parser"
868 "html"
869 ];
870 };
871 }
872 {
873 name = "nix";
874 auto-format = true;
875 formatter = {
876 command = "${pkgs.nixfmt}/bin/nixfmt";
877 };
878 }
879 {
880 name = "kotlin";
881 auto-format = true;
882 }
883 {
884 name = "rust";
885 auto-format = true;
886 formatter = {
887 command = "rustfmt";
888 args = [
889 "--edition"
890 "2024"
891 ];
892 };
893 indent = {
894 tab-width = 4;
895 unit = "t";
896 };
897 }
898 {
899 name = "astro";
900 auto-format = true;
901 formatter = {
902 command = "npx";
903 args = [
904 "prettier"
905 "--plugin"
906 "prettier-plugin-astro"
907 "--parser"
908 "astro"
909 ];
910 };
911 }
912 {
913 name = "json";
914 auto-format = true;
915 }
916 {
917 name = "just";
918 auto-format = true;
919 formatter = {
920 command = "just";
921 args = [
922 "--justfile"
923 "/dev/stdin"
924 "--dump"
925 ];
926 };
927 }
928 {
929 name = "toml";
930 auto-format = true;
931 formatter = {
932 command = "taplo";
933 args = [
934 "format"
935 "-"
936 ];
937 };
938 }
939 # {
940 # name = "typescript";
941 # roots = [
942 # "deno.json"
943 # "deno.jsonc"
944 # ];
945 # file-types = [
946 # "ts"
947 # "tsx"
948 # ];
949 # auto-format = true;
950 # language-servers = [ "deno-lsp" ];
951 # }
952 ];
953 };
954 };
955
956 dconf.settings = {
957 "org/gnome/desktop/interface" = {
958 color-scheme = "prefer-dark";
959 enable-hot-corners = false;
960 };
961 };
962
963 # Font rendering configuration
964 fonts.fontconfig = {
965 enable = true;
966 defaultFonts = {
967 monospace = [ "BerkeleyMono Nerd Font" ];
968 sansSerif = [ "Noto Sans" ];
969 serif = [ "Noto Serif" ];
970 };
971 };
972
973 # Cursor configuration
974 home.pointerCursor = {
975 name = "Adwaita";
976 package = pkgs.adwaita-icon-theme;
977 size = 16;
978 x11.enable = true;
979 gtk.enable = true;
980 };
981
982 # Session variables
983 home.sessionVariables = {
984 EDITOR = "hx";
985 VISUAL = "hx";
986 SUDO_EDITOR = "hx";
987 SSH_AUTH_SOCK = "${config.home.homeDirectory}/.1password/agent.sock";
988 SSH_ASKPASS = "${pkgs.openssh-askpass}/bin/gnome-ssh-askpass3";
989 SSH_ASKPASS_REQUIRE = "prefer";
990 };
991
992 # SSH allowed signers for commit signature verification
993 home.file.".ssh/allowed_signers".text = ''
994 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=
995 hello@seanaye.ca sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAILdilHXHdAP/V8Zq28EzHKtLAMMaFPu4+1det2N50QfhAAAABHNzaDo= sean@framework16
996 '';
997
998 # Yubikey identity for agenix (not secret - just a reference to the hardware key)
999 home.file.".config/agenix/yubikey-identity.txt".text = ''
1000 # Serial: 26930059, Slot: 1
1001 # Name: agenix
1002 # Recipient: age1yubikey1qw64ag5lzvn9ekrflu5ruj4a6ucycscl6ctk39fjzf76jptsay39z442pxv
1003 AGE-PLUGIN-YUBIKEY-1304E5QVZZD74FKSP8FMCT
1004 '';
1005
1006 # Same identity for sops (expects keys at this path by default)
1007 home.file.".config/sops/age/keys.txt".text = ''
1008 # Serial: 26930059, Slot: 1
1009 # Name: agenix
1010 # Recipient: age1yubikey1qw64ag5lzvn9ekrflu5ruj4a6ucycscl6ctk39fjzf76jptsay39z442pxv
1011 AGE-PLUGIN-YUBIKEY-1304E5QVZZD74FKSP8FMCT
1012 '';
1013
1014 # yubikey sudo access
1015 home.file.".config/Yubico/u2f_keys".text = ''
1016 sean:2HY//CedY0ZSrKf57lT7abxG8+8bkPyxCfp/0HMlk/il/5W8pn4R5xLiZDcJtvL85U24h9IEIxa4CS22mpaDSA==,gcD/dpLdwvUFcGGPHS4qNsarH4lOEy1AJAT7zoC6BPlFRUYEa8DpVVKFTcvT6PotjnSHSrWWGb/f3U2k2jIOIw==,es256,+presence
1017 '';
1018
1019 # Set the state version for Home Manager
1020 home.stateVersion = "25.05";
1021}