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