summaryrefslogtreecommitdiff
path: root/accounts/gkleen@sif
diff options
context:
space:
mode:
Diffstat (limited to 'accounts/gkleen@sif')
-rw-r--r--accounts/gkleen@sif/default.nix387
-rw-r--r--accounts/gkleen@sif/emacs.el5
-rw-r--r--accounts/gkleen@sif/firefox-chrome.css30
-rw-r--r--accounts/gkleen@sif/niri/default.nix1318
-rw-r--r--accounts/gkleen@sif/niri/mako.nix62
-rw-r--r--accounts/gkleen@sif/niri/swayosd.nix66
-rw-r--r--accounts/gkleen@sif/niri/waybar.nix74
-rw-r--r--accounts/gkleen@sif/ssh-hosts.nix85
-rw-r--r--accounts/gkleen@sif/synadm/default.nix9
-rw-r--r--accounts/gkleen@sif/synadm/synadm_yaml15
-rw-r--r--accounts/gkleen@sif/systemd.nix121
-rw-r--r--accounts/gkleen@sif/utils/async-yt-dlp.nix57
-rw-r--r--accounts/gkleen@sif/utils/sieve-edit.nix24
-rw-r--r--accounts/gkleen@sif/zshrc145
14 files changed, 1594 insertions, 804 deletions
diff --git a/accounts/gkleen@sif/default.nix b/accounts/gkleen@sif/default.nix
index 0c0872cc..706eb241 100644
--- a/accounts/gkleen@sif/default.nix
+++ b/accounts/gkleen@sif/default.nix
@@ -4,33 +4,6 @@ with lib;
4 4
5let 5let
6 cfg = config.home-manager.users.${userName}; 6 cfg = config.home-manager.users.${userName};
7 emacsScratch = pkgs.stdenv.mkDerivation (sources.emacs-scratch_el // rec {
8 phases = [ "installPhase" ];
9
10 installPhase = ''
11 mkdir -p $out/share/emacs/site-lisp
12 cp $src/scratch.el $out/share/emacs/site-lisp/default.el
13 '';
14 });
15 muteScript = pkgs.stdenv.mkDerivation {
16 name = "mute";
17 src = ./scripts/mute.zsh;
18
19 buildInputs = with pkgs; [ makeWrapper ];
20
21 phases = [ "installPhase" ];
22
23 installPhase = ''
24 mkdir -p $out/bin
25 install -m 0755 $src $out/bin/mute
26 wrapProgram $out/bin/mute \
27 --prefix PATH : ${pkgs.zsh}/bin \
28 --prefix PATH : ${pkgs.findutils}/bin \
29 --prefix PATH : ${pkgs.util-linux}/bin \
30 --prefix PATH : ${pkgs.coreutils}/bin \
31 --prefix PATH : ${pkgs.pulseaudio}/bin
32 '';
33 };
34 wrapElectron = { package, bin ? package.meta.mainProgram or package.pname or (pkgs.lib.strings.nameFromURL package.name "-"), outBin ? bin, sandbox ? true }: pkgs.symlinkJoin { 7 wrapElectron = { package, bin ? package.meta.mainProgram or package.pname or (pkgs.lib.strings.nameFromURL package.name "-"), outBin ? bin, sandbox ? true }: pkgs.symlinkJoin {
35 name = "${package.name}-wrapped"; 8 name = "${package.name}-wrapped";
36 buildInputs = with pkgs; [ makeWrapper ]; 9 buildInputs = with pkgs; [ makeWrapper ];
@@ -47,10 +20,6 @@ let
47 ''; 20 '';
48 }; 21 };
49 22
50 wrappedChrome = wrapElectron { package = pkgs.google-chrome; outBin = "google-chrome"; };
51 wrappedZulip = wrapElectron { package = pkgs.zulip; bin = "zulip"; outBin = "zulip"; };
52 wrappedElementDesktop = wrapElectron { package = pkgs.element-desktop; bin = "element-desktop"; };
53 wrappedRocketChatDesktop = wrapElectron { package = pkgs.rocketchat-desktop; bin = "rocketchat-desktop"; outBin = "rocketchat"; };
54 wrappedYTMDesktop = wrapElectron { package = pkgs.ytmdesktop; sandbox = false; }; 23 wrappedYTMDesktop = wrapElectron { package = pkgs.ytmdesktop; sandbox = false; };
55 24
56 wrappedKeepassxc = pkgs.symlinkJoin { 25 wrappedKeepassxc = pkgs.symlinkJoin {
@@ -63,7 +32,7 @@ let
63 text = '' 32 text = ''
64 [D-BUS Service] 33 [D-BUS Service]
65 Name=org.keepassxc.KeePassXC.MainWindow 34 Name=org.keepassxc.KeePassXC.MainWindow
66 Exec=${pkgs.coreutils}/bin/false 35 Exec=${lib.getExe' pkgs.coreutils "false"}
67 SystemdService=keepassxc.service 36 SystemdService=keepassxc.service
68 ''; 37 '';
69 }) 38 })
@@ -73,36 +42,46 @@ let
73 text = '' 42 text = ''
74 [D-BUS Service] 43 [D-BUS Service]
75 Name=org.freedesktop.secrets 44 Name=org.freedesktop.secrets
76 Exec=${pkgs.coreutils}/bin/false 45 Exec=${lib.getExe' pkgs.coreutils "false"}
77 SystemdService=keepassxc.service 46 SystemdService=keepassxc.service
78 ''; 47 '';
79 }) 48 })
80 ]; 49 ];
81 }; 50 };
82 51
83 lockCommand = "${config.systemd.package}/bin/systemctl --user start gtklock.service"; 52 lockCommand = "${lib.getExe' config.systemd.package "systemctl"} --user start gtklock.service";
53
54 editor = pkgs.symlinkJoin {
55 inherit (cfg.services.emacs.package) name;
56 buildInputs = with pkgs; [ makeWrapper ];
57 paths = [ cfg.services.emacs.package ];
58 postBuild = ''
59 wrapProgram $out/bin/emacsclient \
60 --inherit-argv0 \
61 --add-flags ${lib.escapeShellArg (lib.escapeShellArgs cfg.services.emacs.client.arguments)}
62 '';
63 };
84in { 64in {
85 imports = with flake.nixosModules.userProfiles.${userName}; [ 65 imports = with flake.nixosModules.userProfiles.${userName}; [
86 mpv yt-dlp (args: import ./xcompose.nix (inputs // args)) 66 zsh tmux mpv yt-dlp (args: import ./xcompose.nix (inputs // args))
87 ]; 67 ];
88 68
89 config = { 69 config = {
90 services.displayManager.defaultSession = "Hyprland"; # "none+xmonad";
91
92 home-manager.users.${userName} = { 70 home-manager.users.${userName} = {
93 imports = [ 71 imports = [
94 ./libvirt 72 ./libvirt
95 ./niri 73 ./niri
74 ./synadm
96 flakeInputs.nix-index-database.hmModules.nix-index 75 flakeInputs.nix-index-database.hmModules.nix-index
97 flakeInputs.impermanence.nixosModules.home-manager.impermanence 76 flakeInputs.impermanence.nixosModules.home-manager.impermanence
98 ]; 77 ];
99 78
100 home.stateVersion = "20.09"; 79 home.stateVersion = "20.09";
101 80
102 nixpkgs.config = { 81 # nixpkgs.config = {
103 allowUnfree = true; 82 # allowUnfree = true;
104 zathura.useMupdf = false; 83 # zathura.useMupdf = false;
105 }; 84 # };
106 85
107 nix.registry = { 86 nix.registry = {
108 "flk" = { 87 "flk" = {
@@ -112,14 +91,14 @@ in {
112 }; 91 };
113 to = { 92 to = {
114 type = "git"; 93 type = "git";
115 url = "file:///home/gkleen/config/nixos-flakes"; 94 url = "file:///home/gkleen/projects/machines";
116 }; 95 };
117 }; 96 };
118 }; 97 };
119 98
120 programs = { 99 programs = {
121 ssh = { 100 ssh = {
122 matchBlocks = import ./ssh-hosts.nix { inherit pkgs; }; # customUtils.nixImport { dir = ./ssh-hosts; }; 101 matchBlocks = import ./ssh-hosts.nix inputs; # customUtils.nixImport { dir = ./ssh-hosts; };
123 extraConfig = '' 102 extraConfig = ''
124 Match host uniworx3.ifi.lmu.de,uniworx4.ifi.lmu.de,uniworx5.ifi.lmu.de,uni2workgw.ifi.lmu.de,blackbeard.tcs.ifi.lmu.de,gitlab2.rz.ifi.lmu.de,oregon.tcs.ifi.lmu.de !exec "nc -z -w 1 %h %p &>/dev/null" 103 Match host uniworx3.ifi.lmu.de,uniworx4.ifi.lmu.de,uniworx5.ifi.lmu.de,uni2workgw.ifi.lmu.de,blackbeard.tcs.ifi.lmu.de,gitlab2.rz.ifi.lmu.de,oregon.tcs.ifi.lmu.de !exec "nc -z -w 1 %h %p &>/dev/null"
125 ProxyJump remote.cip.ifi.lmu.de 104 ProxyJump remote.cip.ifi.lmu.de
@@ -137,8 +116,8 @@ in {
137 ''} 116 ''}
138 117
139 Match host *.mathinst.loc,*.math.lmu.de !host ssh.math.lmu.de !exec "nc -z -w 1 %h %p &>/dev/null" 118 Match host *.mathinst.loc,*.math.lmu.de !host ssh.math.lmu.de !exec "nc -z -w 1 %h %p &>/dev/null"
140 # ProxyCommand ${pkgs.socat}/bin/socat - SOCKS4A:127.0.0.1:%h:%p,socksport=8118 119 ProxyCommand ${lib.getExe pkgs.socat} - SOCKS4A:127.0.0.1:%h:%p,socksport=8118
141 ProxyJump ssh.math.lmu.de 120 # ProxyJump ssh.math.lmu.de
142 121
143 Match host *.cipmath.loc !host cip04.cipmath.loc,mgmt-cls01.cipmath.loc !exec "nc -z -w 1 %h %p &>/dev/null" 122 Match host *.cipmath.loc !host cip04.cipmath.loc,mgmt-cls01.cipmath.loc !exec "nc -z -w 1 %h %p &>/dev/null"
144 ProxyJump cip04 123 ProxyJump cip04
@@ -155,22 +134,31 @@ in {
155 134
156 emacs = { 135 emacs = {
157 enable = true; 136 enable = true;
158 package = pkgs.emacs29-pgtk; 137 package = pkgs.emacs-pgtk;
159 extraPackages = epkgs: with epkgs; [ 138 extraPackages = epkgs: with epkgs; [
160 evil evil-dvorak undo-tree magit haskell-tng-mode nix-mode 139 evil evil-dvorak undo-tree magit haskell-tng-mode nix-mode
161 yaml-mode json-mode shakespeare-mode smart-mode-line 140 yaml-mode json-mode shakespeare-mode smart-mode-line
162 highlight-parentheses highlight-symbol ag sass-mode lua-mode 141 highlight-parentheses highlight-symbol ag sass-mode
163 fira-code-mode use-package wanderlust # notmuch 142 lua-mode fira-code-mode use-package wanderlust # notmuch
164 git-gutter emacsScratch 143 git-gutter scratch edit-server mediawiki editorconfig
165 edit-server mediawiki editorconfig typescript-mode 144 typescript-mode markdown-mode nftables-mode rustic
166 markdown-mode nftables-mode rustic lsp-mode lsp-ui 145 lsp-mode lsp-ui direnv company projectile
167 direnv company projectile tomorrow-night-paradise-theme 146 tomorrow-night-paradise-theme
168 treesit-grammars.with-all-grammars magit-delta scad-mode 147 treesit-grammars.with-all-grammars magit-delta scad-mode
169 ]; 148 ];
170 overrides = self: super: { 149 overrides = self: super: {
171 tomorrow-night-paradise-theme = super.trivialBuild { 150 tomorrow-night-paradise-theme = super.trivialBuild {
172 inherit (sources.tomorrow-night-paradise-theme) pname version src; 151 inherit (sources.tomorrow-night-paradise-theme) pname version src;
173 }; 152 };
153 scratch = pkgs.stdenv.mkDerivation {
154 inherit (sources.emacs-scratch_el) pname version src;
155
156 phases = [ "unpackPhase" "installPhase" ];
157
158 installPhase = ''
159 install -Dt $out/share/emacs/site-lisp scratch.el
160 '';
161 };
174 }; 162 };
175 }; 163 };
176 firefox = { 164 firefox = {
@@ -185,7 +173,12 @@ in {
185 }; 173 };
186 }; 174 };
187 175
188 zathura.enable = true; 176 zathura = {
177 enable = true;
178 options = {
179 scroll-page-aware = true;
180 };
181 };
189 imv.enable = true; 182 imv.enable = true;
190 183
191 mpv.config = { 184 mpv.config = {
@@ -194,13 +187,91 @@ in {
194 gpu-api = "vulkan"; 187 gpu-api = "vulkan";
195 }; 188 };
196 189
197 zsh.initExtra = '' 190 zsh.initContent = let
198 source ${./zshrc} 191 zshrc = pkgs.resholve.mkDerivation {
192 pname = "zshrc";
193 version = "0.0.0";
194
195 src = ./zshrc;
196
197 dontUnpack = true;
198 dontConfigure = true;
199 dontBuild = true;
200
201 installPhase = ''
202 mkdir -p $out/share
203 install "$src" $out/share/zshrc
204 '';
205
206 solutions = {
207 default = {
208 scripts = [ "share/zshrc" ];
209 interpreter = "none";
210 inputs = with pkgs; [
211 coreutils
212 rpm
213 binutils
214 squashfsTools
215 unzip
216 cfg.programs.git.package
217 magickWrapped
218 curl
219 file
220 gnutar
221 cpio
222 magic-wormhole
223 cfg.programs.zsh.package
224 fuse
225 util-linux
226 findutils
227 qrencode
228 tty-clock
229 cfg.programs.jq.package
230 eza
231 less
232 config.systemd.package
233 config.programs.ssh.package
234 gnused
235 miniserve
236 ];
237 execer = with pkgs; [
238 "cannot:${lib.getExe' rpm "rpm2cpio"}"
239 "cannot:${lib.getExe' squashfsTools "unsquashfs"}"
240 "cannot:${lib.getExe' unzip "unzip"}"
241 "cannot:${lib.getExe cfg.programs.git.package}"
242 "cannot:${lib.getExe cpio}"
243 "cannot:${lib.getExe' magic-wormhole "wormhole"}"
244 "cannot:${lib.getExe' fuse "fusermount"}"
245 "cannot:${lib.getExe less}"
246 "cannot:${lib.getExe' config.systemd.package "systemctl"}"
247 "cannot:${lib.getExe config.programs.ssh.package}"
248 ];
249 wrapper = with pkgs; [
250 "${lib.getExe' magickWrapped "magick"}:${lib.getExe' imagemagick "magick"}"
251 ];
252 fake = {
253 builtin = ["print"];
254 external = ["sudo" "umount"];
255 };
256 };
257 };
258 };
259 magickWrapped = pkgs.symlinkJoin {
260 inherit (pkgs.imagemagick) name;
261 paths = [ pkgs.imagemagick ];
262
263 buildInputs = with pkgs; [ makeWrapper ];
264 postBuild = ''
265 wrapProgram $out/bin/magick \
266 --prefix PATH : ${lib.makeBinPath (with pkgs; [ ghostscript ])}
267 '';
268 };
269 in ''
270 source ${zshrc}/share/zshrc
199 ''; 271 '';
200 zsh.dirHashes = let 272 zsh.dirHashes = let
201 flakeHashes = mapAttrs' (n: v: nameValuePair (inputNames.${n} or n) (toString v)) flakeInputs; 273 flakeHashes = mapAttrs' (n: v: nameValuePair (inputNames.${n} or n) (toString v)) flakeInputs;
202 inputNames = { 274 inputNames = {
203 "nixpkgs" = "nixos";
204 }; 275 };
205 in flakeHashes // { 276 in flakeHashes // {
206 u2w = "$HOME/projects/uni2work"; 277 u2w = "$HOME/projects/uni2work";
@@ -212,6 +283,16 @@ in {
212 pro = "$HOME/projects/pro"; 283 pro = "$HOME/projects/pro";
213 media = "$HOME/media"; 284 media = "$HOME/media";
214 }; 285 };
286 jq.colors = {
287 arrays = "1;37";
288 "false" = "0;37";
289 "null" = "2;37";
290 numbers = "0;37";
291 objectKeys = "1;34";
292 objects = "1;37";
293 strings = "0;32";
294 "true" = "0;37";
295 };
215 296
216 obs-studio = { 297 obs-studio = {
217 enable = true; 298 enable = true;
@@ -221,7 +302,7 @@ in {
221 gh = { 302 gh = {
222 enable = true; 303 enable = true;
223 settings = { 304 settings = {
224 editor = "${config.home-manager.users.${userName}.programs.emacs.package}/bin/emacsclient"; 305 editor = lib.getExe' editor "emacsclient";
225 gitProtocol = "ssh"; 306 gitProtocol = "ssh";
226 }; 307 };
227 }; 308 };
@@ -247,16 +328,10 @@ in {
247 # notify_on_cmd_finish = "invisible 120"; 328 # notify_on_cmd_finish = "invisible 120";
248 }; 329 };
249 keybindings = { 330 keybindings = {
250 "kitty_mod+n" = "detach_window"; 331 "kitty_mod+n" = "new_os_window_with_cwd";
251 "kitty_mod+m" = "detach_window ask"; 332 "kitty_mod+m" = "detach_window ask";
252 }; 333 "kitty_mod+enter" = "new_window_with_cwd";
253 }; 334 "kitty_mod+t" = "new_tab_with_cwd";
254 wpaperd = {
255 enable = true;
256 settings.default = {
257 path = "~/.wallpapers";
258 duration = "8h";
259 mode = "center";
260 }; 335 };
261 }; 336 };
262 fuzzel = { 337 fuzzel = {
@@ -269,7 +344,7 @@ in {
269 font = "Fira Sans"; 344 font = "Fira Sans";
270 }; 345 };
271 colors = { 346 colors = {
272 background = "000000aa"; 347 background = "000000cc";
273 text = "cdd6f4ff"; 348 text = "cdd6f4ff";
274 match = "94e2d5ff"; 349 match = "94e2d5ff";
275 selection = "585b70ff"; 350 selection = "585b70ff";
@@ -282,26 +357,46 @@ in {
282 }; 357 };
283 }; 358 };
284 }; 359 };
360 pandoc = {
361 enable = true;
362 extraAbbreviations = ["i.A." "d.h." "D.h." "gdw."];
363 };
364 nushell = {
365 enable = true;
366 settings.show_banner = false;
367 };
368 fd.enable = true;
285 }; 369 };
286 370
287 services = { 371 services = {
372 wpaperd = {
373 enable = true;
374 settings.default = {
375 path = "~/.wallpapers";
376 duration = "15m";
377 mode = "center";
378 };
379 };
288 emacs = { 380 emacs = {
289 enable = true; 381 enable = true;
290 socketActivation.enable = true; 382 socketActivation.enable = true;
291 client = { 383 client = {
292 enable = true; 384 enable = true;
293 arguments = mkForce ["--reuse-frame" "--alternate-editor" "\"\""]; 385 arguments = mkForce ["--create-frame" "--alternate-editor" (lib.getExe cfg.services.emacs.package)];
294 }; 386 };
295 }; 387 };
296 gpg-agent = { 388 gpg-agent = {
297 enable = true; 389 enable = true;
298 enableSshSupport = true; 390 enableSshSupport = true;
299 extraConfig = '' 391 extraConfig = ''
300 pinentry-program ${pkgs.pinentry-gtk2}/bin/pinentry 392 pinentry-program ${lib.getExe' pkgs.pinentry-gtk2 "pinentry"}
301 grab 393 grab
302 ''; 394 '';
303 }; 395 };
304 xembed-sni-proxy.enable = true; 396 xembed-sni-proxy = {
397 enable = true;
398 package = pkgs.kdePackages.plasma-workspace;
399 };
305 udiskie = { 400 udiskie = {
306 enable = true; 401 enable = true;
307 automount = false; 402 automount = false;
@@ -314,6 +409,7 @@ in {
314 }; 409 };
315 device_config = [ 410 device_config = [
316 { loop_file = "/nix/store/*-etc-metadata.erofs"; is_mounted = false; ignore = true; } 411 { loop_file = "/nix/store/*-etc-metadata.erofs"; is_mounted = false; ignore = true; }
412 { mount_path = "/run/nixos-etc-metadata"; ignore = true; }
317 { mount_path = "/run/nixos-etc-metadata.*"; ignore = true; } 413 { mount_path = "/run/nixos-etc-metadata.*"; ignore = true; }
318 ]; 414 ];
319 icon_names.media = ["drive-removable-media-symbolic"]; 415 icon_names.media = ["drive-removable-media-symbolic"];
@@ -333,7 +429,7 @@ in {
333 batch = "true"; 429 batch = "true";
334 log = "false"; 430 log = "false";
335 repeat = "watch"; 431 repeat = "watch";
336 sshcmd = "${pkgs.openssh}/bin/ssh"; 432 sshcmd = lib.getExe' pkgs.openssh "ssh";
337 ui = "text"; 433 ui = "text";
338 }; 434 };
339 }; 435 };
@@ -353,31 +449,17 @@ in {
353 enable = true; 449 enable = true;
354 events = [ 450 events = [
355 { event = "before-sleep"; command = lockCommand; } 451 { event = "before-sleep"; command = lockCommand; }
356 # { event = "after-resume"; command = "${cfg.wayland.windowManager.hyprland.package}/bin/hyprctl dispatch dpms on"; }
357 { event = "lock"; command = lockCommand; } 452 { event = "lock"; command = lockCommand; }
358 ]; 453 ];
359 timeouts = [ 454 timeouts = [
360 # { timeout = 300; 455 { timeout = 600; command = lockCommand; }
361 # command = "${cfg.wayland.windowManager.hyprland.package}/bin/hyprctl dispatch dpms off";
362 # }
363 { timeout = 330; command = lockCommand; }
364 ]; 456 ];
365 extraArgs = [ 457 extraArgs = [
458 "-w"
366 "idlehint" "30" 459 "idlehint" "30"
367 ]; 460 ];
368 }; 461 };
369 poweralertd.enable = true; 462 poweralertd.enable = true;
370 avizo = {
371 enable = true;
372 settings.default = {
373 time = "1.0";
374 background = "rgba(0, 0, 0, 0.8)";
375 border-color = "rgba(0, 0, 0, 1)";
376 bar-fg-color = "rgba(160, 160, 160, 1)";
377 bar-bg-color = "rgba(32, 32, 32, 0.96)";
378 # y-offset = "0.25";
379 };
380 };
381 }; 463 };
382 464
383 home.pointerCursor = { 465 home.pointerCursor = {
@@ -409,6 +491,13 @@ in {
409 }; 491 };
410 }; 492 };
411 493
494 qt.kde.settings = {
495 kwalletrc = {
496 KSecretD.Enabled = false;
497 Wallet."Default Wallet" = "store";
498 };
499 };
500
412 xsession.preferStatusNotifierItems = true; 501 xsession.preferStatusNotifierItems = true;
413 502
414 xresources.properties = import ./xresources.nix; 503 xresources.properties = import ./xresources.nix;
@@ -417,20 +506,19 @@ in {
417 packages = with pkgs; [ 506 packages = with pkgs; [
418 fira fira-code pwvucontrol wrappedKeepassxc wl-clipboard-rs 507 fira fira-code pwvucontrol wrappedKeepassxc wl-clipboard-rs
419 mumble pulseaudio-ctl pamixer libnotify screen-message 508 mumble pulseaudio-ctl pamixer libnotify screen-message
420 wrappedYTMDesktop libsForQt5.qt5ct playerctl evince 509 wrappedYTMDesktop libsForQt5.qt5ct playerctl evince papers
421 thunderbird zoom-us xdg-desktop-portal steam steam-run 510 thunderbird zoom-us xdg-desktop-portal steam steam-run
422 wireshark virt-manager rclone cached-nix-shell worktime 511 wireshark virt-manager rclone cached-nix-shell worktime
423 fira-code-symbols libreoffice xournalpp google-chrome 512 fira-code-symbols libreoffice xournalpp google-chrome
424 nixos-shell virt-viewer freerdp gnome-icon-theme 513 nixos-shell virt-viewer freerdp gnome-icon-theme
425 paper-icon-theme sshpassSecret weechat element-desktop 514 paper-icon-theme sshpassSecret weechat element-desktop
426 matrix-synapse-tools.synadm 515 sieve-connect gimp3 inkscape udiskie glab nitrokey-app
427 flakeInputs.deploy-rs.packages.${config.nixpkgs.system}.deploy-rs
428 sieve-connect gimp inkscape udiskie glab nitrokey-app
429 pynitrokey gtklock wlrctl remmina openscad spice-record 516 pynitrokey gtklock wlrctl remmina openscad spice-record
430 libguestfs-with-appliance nerd-fonts.fira-mono 517 libguestfs-with-appliance nerd-fonts.fira-mono
431 nerd-fonts.symbols-only nerd-fonts.fira-code powerline-fonts 518 nerd-fonts.symbols-only nerd-fonts.fira-code powerline-fonts
432 swtpm 519 swtpm (hunspellWithDicts (with hunspellDicts; [en_GB-large de_DE]))
433 ]; 520 libation libqalculate
521 ] ++ mapAttrsToList (_name: pkg: pkgs.callPackage pkg {}) (customUtils.nixImport { dir = ./utils; });
434 522
435 file = { 523 file = {
436 ".backup-munin".source = ./backup-patterns; 524 ".backup-munin".source = ./backup-patterns;
@@ -450,12 +538,9 @@ in {
450 QT_QPA_PLATFORMTHEME = "qt5ct"; 538 QT_QPA_PLATFORMTHEME = "qt5ct";
451 LIBVIRT_DEFAULT_URI = "qemu:///system"; 539 LIBVIRT_DEFAULT_URI = "qemu:///system";
452 STACK_XDG = 1; 540 STACK_XDG = 1;
453 EDITOR = pkgs.writeShellScript "editor" '' 541 EDITOR = lib.getExe' editor "emacsclient";
454 args=("--reuse-frame" "--alternate-editor" "") 542 RCLONE_PASSWORD_COMMAND = "${lib.getExe' pkgs.libsecret "secret-tool"} lookup service rclone";
455 args+=("$@") 543 SYSTEMD_TINT_BACKGROUND = "false";
456 exec -a emacsclient ${cfg.services.emacs.package}/bin/emacsclient "''${args[@]}"
457 '';
458 RCLONE_PASSWORD_COMMAND = "${pkgs.libsecret}/bin/secret-tool lookup service rclone";
459 }; 544 };
460 545
461 extraProfileCommands = '' 546 extraProfileCommands = ''
@@ -468,7 +553,7 @@ in {
468 source = ./wireplumber; 553 source = ./wireplumber;
469 recursive = true; 554 recursive = true;
470 onChange = '' 555 onChange = ''
471 ${pkgs.systemd}/bin/systemctl --user try-restart wireplumber 556 ${lib.getExe' config.systemd.package "systemctl"} --user try-restart wireplumber
472 ''; 557 '';
473 }; 558 };
474 "stack/config.yaml" = { 559 "stack/config.yaml" = {
@@ -492,41 +577,36 @@ in {
492 General = { 577 General = {
493 dot_as_separator = 0; 578 dot_as_separator = 0;
494 }; 579 };
580 Mode = {
581 calculate_as_you_type = 1;
582 };
495 }; 583 };
496 }; 584 };
497 "emacs/init.el".source = ./emacs.el; 585 "emacs/init.el".source = pkgs.substitute {
586 src = ./emacs.el;
587 substitutions = [
588 "--subst-var-by" "ksshaskpass" (lib.getExe pkgs.kdePackages.ksshaskpass)
589 ];
590 };
498 "systemd/user/xdg-desktop-portal.service.d/after-graphical-session.conf".text = '' 591 "systemd/user/xdg-desktop-portal.service.d/after-graphical-session.conf".text = ''
499 [Unit] 592 [Unit]
500 After=graphical-session.target 593 After=graphical-session.target
501 ''; 594 '';
595 "systemd/user/home-manager.service.d/before-graphical-session.conf".text = ''
596 [Unit]
597 Before=graphical-session-pre.target
598 '';
599 "pdfpc/pdfpcrc".text = ''
600 mouse 8 prev
601 mouse 9 next
602 '';
502 }; 603 };
503 604
504 xdg.dataFile = { 605 xdg.dataFile = {
505 "pandoc/abbreviations" = {
506 source = pkgs.runCommand "pandoc-abbreviations" {
507 buildInputs = [ pkgs.pandoc pkgs.coreutils ];
508 } (let
509 germanAbbrevs = pkgs.fetchFromGitHub {
510 owner = "jfilter";
511 repo = "german-abbreviations";
512 rev = "8eb9dae93b6f05d7c53374cd217ab2dc89558e0c";
513 sha256 = "SaD3tSqzen6Y3SPICe6/9vhe4iMHlArZ3kFQaEk7Hps=";
514 };
515 in ''
516 cat \
517 <(pandoc --print-default-data-file=abbreviations) \
518 <(grep -E '^[^ ]+\.$' ${germanAbbrevs}/german_abbreviations.txt) \
519 ${pkgs.writeText "abbrevs.txt" ''
520 i.A.
521 d.h.
522 D.h.
523 gdw.
524 ''} \
525 | sort | uniq >$out
526 '');
527 };
528 "dbus-1/services/org.keepassxc.KeePassXC.service".source = "${wrappedKeepassxc}/share/dbus-1/services/org.keepassxc.KeePassXC.service"; 606 "dbus-1/services/org.keepassxc.KeePassXC.service".source = "${wrappedKeepassxc}/share/dbus-1/services/org.keepassxc.KeePassXC.service";
529 "dbus-1/services/org.freedesktop.secrets.service.service".source = "${wrappedKeepassxc}/share/dbus-1/services/org.freedesktop.secrets.service.service"; 607 "dbus-1/services/org.freedesktop.secrets.service.service".source = "${wrappedKeepassxc}/share/dbus-1/services/org.freedesktop.secrets.service.service";
608 "dbus-1/services/org.kde.kwalletd6.service".source = "${pkgs.kdePackages.kwallet}/share/dbus-1/services/org.kde.kwalletd6.service";
609 "dbus-1/services/org.kde.kwalletd5.service".source = "${pkgs.kdePackages.kwallet}/share/dbus-1/services/org.kde.kwalletd5.service";
530 "emoji-data/list.txt".source = pkgs.stdenv.mkDerivation { 610 "emoji-data/list.txt".source = pkgs.stdenv.mkDerivation {
531 inherit (sources.emoji-data) pname src; 611 inherit (sources.emoji-data) pname src;
532 version = lib.removePrefix "v" sources.emoji-data.version; 612 version = lib.removePrefix "v" sources.emoji-data.version;
@@ -612,19 +692,68 @@ in {
612 name = "Rainbow"; 692 name = "Rainbow";
613 exec = toString (pkgs.writeShellScript "rainbow" '' 693 exec = toString (pkgs.writeShellScript "rainbow" ''
614 exec -- \ 694 exec -- \
615 ${config.systemd.package}/bin/systemd-run --wait --user --slice-inherit \ 695 ${lib.getExe' config.systemd.package "systemd-run"} --wait --user --slice-inherit \
616 --property 'CPUAccounting=yes' --property 'CPUQuotaPeriodSec=50ms' \ 696 --property 'CPUAccounting=yes' --property 'CPUQuotaPeriodSec=50ms' \
617 --property 'Environment=DSCP=46' \ 697 -E DSCP=46 -E NIXOS_OZONE_WL \
618 -- ${pkgs.dscp}/bin/dscp ${pkgs.google-chrome}/bin/google-chrome-stable \ 698 -- ${lib.getExe pkgs.dscp} ${lib.getExe' pkgs.google-chrome "google-chrome-stable"} \
619 --force-device-scale-factor=1.5 \
620 --class=Rainbow \ 699 --class=Rainbow \
621 --kiosk "https://web.openrainbow.com" \ 700 --app="https://web.openrainbow.com" \
622 --user-data-dir=''${HOME}/.config/google-chrome-rainbow 701 --user-data-dir=''${HOME}/.config/google-chrome-rainbow
623 ''); 702 '');
624 icon = pkgs.fetchurl { 703 icon = pkgs.fetchurl {
625 url = "https://web.openrainbow.com/rb/2.139.17/assets/skins/rainbow/images/homepage/logo__rainbow.svg"; 704 url = "https://web.openrainbow.com/rb/2.139.17/assets/skins/rainbow/images/homepage/logo__rainbow.svg";
626 hash = "sha256-5fmo8rDqVDpzkGaPjk4Y+SsSZpAsY7VUQSFW6WdHwuU="; 705 hash = "sha256-5fmo8rDqVDpzkGaPjk4Y+SsSZpAsY7VUQSFW6WdHwuU=";
627 }; 706 };
707 settings = {
708 StartupWMClass = "Rainbow";
709 };
710 };
711 kimai = {
712 name = "Kimai";
713 exec = toString (pkgs.writeShellScript "kimai" ''
714 exec -- \
715 ${lib.getExe' pkgs.google-chrome "google-chrome-stable"} \
716 --class=Kimai \
717 --app="https://kimai.yggdrasil.li" \
718 --user-data-dir=''${HOME}/.config/google-chrome-kimai
719 '');
720 icon = pkgs.fetchurl {
721 url = "https://www.kimai.org/images/kimai_logo.png";
722 hash = "sha256-lnlOttzR2SwXA70R+egJUkeKr4U5V0avqTk8uX4bqfs=";
723 };
724 settings = {
725 StartupWMClass = "Kimai";
726 StartupNotify = "true";
727 };
728 };
729 audiobookshelf = {
730 name = "Audiobookshelf";
731 exec = toString (pkgs.writeShellScript "audiobookshelf" ''
732 exec -- \
733 ${lib.getExe' pkgs.google-chrome "google-chrome-stable"} \
734 --class=Audiobookshelf \
735 --app="https://audiobookshelf.yggdrasil.li" \
736 --user-data-dir=''${HOME}/.config/google-chrome-audiobookshelf
737 '');
738 icon = pkgs.fetchurl {
739 url = "https://www.audiobookshelf.org/Logo.png";
740 hash = "sha256-JGPk+WNT1C4DC4lSMb0K0YmAMT5LvmSOeO0QRzkc7Lk=";
741 };
742 settings = {
743 StartupWMClass = "Audiobookshelf";
744 StartupNotify = "true";
745 };
746 };
747 thunderbird-lmu = {
748 name = "Thunderbird (LMU)";
749 exec = "thunderbird --name thunderbird -P lmu %U";
750 icon = "thunderbird";
751 genericName = "Email Client";
752 categories = [ "Network" "Chat" "Email" "Feed" "GTK" "News" ];
753 settings = {
754 StartupWMClass = "thunderbird";
755 StartupNotify = "true";
756 };
628 }; 757 };
629 }; 758 };
630 759
diff --git a/accounts/gkleen@sif/emacs.el b/accounts/gkleen@sif/emacs.el
index 183cb322..3beefba6 100644
--- a/accounts/gkleen@sif/emacs.el
+++ b/accounts/gkleen@sif/emacs.el
@@ -51,7 +51,7 @@
51 51
52;; (require 'scratch) 52;; (require 'scratch)
53(global-set-key (kbd "C-x B") 'scratch-create) 53(global-set-key (kbd "C-x B") 'scratch-create)
54(setq initial-major-mode 'scratch-mode) 54;; (setq initial-major-mode 'scratch-mode)
55(setq initial-scratch-message "") 55(setq initial-scratch-message "")
56 56
57(global-set-key (kbd "C-x K") 'kill-current-buffer) 57(global-set-key (kbd "C-x K") 'kill-current-buffer)
@@ -228,6 +228,7 @@ necessarily running."
228 (global-set-key (kbd "C-x k") 'kill-buffer-with-special-emacsclient-handling)) 228 (global-set-key (kbd "C-x k") 'kill-buffer-with-special-emacsclient-handling))
229 229
230(add-hook 'server-switch-hook 'install-emacsclient-wrapped-kill-buffer) 230(add-hook 'server-switch-hook 'install-emacsclient-wrapped-kill-buffer)
231(add-hook 'server-switch-hook #'raise-frame)
231 232
232(defun move-file (new-location) 233(defun move-file (new-location)
233 "Write this file to NEW-LOCATION, and delete the old one." 234 "Write this file to NEW-LOCATION, and delete the old one."
@@ -253,3 +254,5 @@ necessarily running."
253(bind-key "C-x C-m" #'move-file) 254(bind-key "C-x C-m" #'move-file)
254 255
255(let ((ssh_auth_sock (string-chop-newline (shell-command-to-string "gpgconf --list-dirs agent-ssh-socket")))) (setenv "SSH_AUTH_SOCK" ssh_auth_sock)) 256(let ((ssh_auth_sock (string-chop-newline (shell-command-to-string "gpgconf --list-dirs agent-ssh-socket")))) (setenv "SSH_AUTH_SOCK" ssh_auth_sock))
257(setenv "SSH_ASKPASS_REQUIRE" "prefer")
258(setenv "SSH_ASKPASS" "@ksshaskpass@")
diff --git a/accounts/gkleen@sif/firefox-chrome.css b/accounts/gkleen@sif/firefox-chrome.css
index 8900e2b9..726f1e4b 100644
--- a/accounts/gkleen@sif/firefox-chrome.css
+++ b/accounts/gkleen@sif/firefox-chrome.css
@@ -4,6 +4,21 @@
4 font-size:12px; 4 font-size:12px;
5} 5}
6 6
7#sidebar-main:has([expanded]) {
8 min-width:20em !important;
9 max-width:20em !important;
10}
11
12#sidebar, #sidebar-box {
13 min-width:35em !important;
14 max-width:35em !important;
15}
16
17#sidebar-box {
18 margin-right: var(--space-small);
19}
20
21/*
7#sidebar { 22#sidebar {
8 min-width:20em !important; 23 min-width:20em !important;
9 max-width:20em !important; 24 max-width:20em !important;
@@ -19,8 +34,21 @@
19} 34}
20 35
21#toolbar-menubar[inactive="true"] + #TabsToolbar { 36#toolbar-menubar[inactive="true"] + #TabsToolbar {
22 visibility: collapse !important; 37 visibility: collapse !important;
23} 38}
24 39
25#sidebar-box[sidebarcommand="tabcenter-reborn_ariasuni-sidebar-action"] #sidebar-header { visibility: collapse !important; } 40#sidebar-box[sidebarcommand="tabcenter-reborn_ariasuni-sidebar-action"] #sidebar-header { visibility: collapse !important; }
26#sidebar-box[sidebarcommand="_3c078156-979c-498b-8990-85f7987dd929_-sidebar-action"] #sidebar-header { visibility: collapse !important; } 41#sidebar-box[sidebarcommand="_3c078156-979c-498b-8990-85f7987dd929_-sidebar-action"] #sidebar-header { visibility: collapse !important; }
42*/
43
44.titlebar-buttonbox-container{ display: none; }
45#vertical-spacer { display: none; }
46#tabbrowser-tabs[orient="vertical"] .tab-background {
47 border-radius: var(--border-radius-small) !important;
48}
49hbox:has(> #tabs-newtab-button) {
50 display: none;
51}
52#sidebar-main .tools-and-extensions {
53 justify-content: space-around !important;
54}
diff --git a/accounts/gkleen@sif/niri/default.nix b/accounts/gkleen@sif/niri/default.nix
index 2e9b6909..8752f3e3 100644
--- a/accounts/gkleen@sif/niri/default.nix
+++ b/accounts/gkleen@sif/niri/default.nix
@@ -1,12 +1,15 @@
1{ config, hostConfig, pkgs, lib, ... }: 1{ config, hostConfig, pkgs, lib, flakeInputs, ... }:
2let 2let
3 niri = config.programs.niri.package; 3 cfg = config.programs.niri;
4
5 kdl = flakeInputs.niri-flake.lib.kdl;
6
7 niri = cfg.package;
4 terminal = lib.getExe config.programs.kitty.package; 8 terminal = lib.getExe config.programs.kitty.package;
5 lightctl = lib.getExe' config.services.avizo.package "lightctl";
6 volumectl = lib.getExe' config.services.avizo.package "volumectl";
7 makoctl = lib.getExe' config.services.mako.package "makoctl"; 9 makoctl = lib.getExe' config.services.mako.package "makoctl";
8 loginctl = lib.getExe' hostConfig.systemd.package "loginctl"; 10 loginctl = lib.getExe' hostConfig.systemd.package "loginctl";
9 systemctl = lib.getExe' hostConfig.systemd.package "systemctl"; 11 systemctl = lib.getExe' hostConfig.systemd.package "systemctl";
12 swayosd-client = lib.getExe' config.services.swayosd.package "swayosd-client";
10 13
11 focus_or_spawn = pkgs.writeShellApplication { 14 focus_or_spawn = pkgs.writeShellApplication {
12 name = "focus-or-spawn"; 15 name = "focus-or-spawn";
@@ -19,17 +22,25 @@ let
19 22
20 workspaces_json="$(niri msg -j workspaces)" 23 workspaces_json="$(niri msg -j workspaces)"
21 workspace_output="$(jq -r --arg workspace_name "$workspace_name" '.[] | select(.name == $workspace_name) | .output' <<<"$workspaces_json")" 24 workspace_output="$(jq -r --arg workspace_name "$workspace_name" '.[] | select(.name == $workspace_name) | .output' <<<"$workspaces_json")"
22 active_workspace="$(jq -r --arg workspace_output "$workspace_output" '.[] | select(.output == $workspace_output and .is_active) | .id' <<<"$workspaces_json")" 25 # active_workspace="$(jq -r --arg workspace_output "$workspace_output" '.[] | select(.output == $workspace_output and .is_active) | .id' <<<"$workspaces_json")"
23 active_output="$(jq -r '.[] | select(.is_focused) | .output' <<<"$workspaces_json")" 26 active_output="$(jq -r '.[] | select(.is_focused) | .output' <<<"$workspaces_json")"
24 if [[ $workspace_output != "$active_output" ]]; then 27 if [[ $workspace_output != "$active_output" ]]; then
25 niri msg action move-workspace-to-monitor --output "$active_output" "$workspace_name" 28 niri msg action move-workspace-to-monitor --reference "$workspace_name" "$active_output"
26 socat STDIO "$NIRI_SOCKET" <<<'{"Action":{"FocusWorkspace":{"reference":{"Id":'"''${active_workspace}"'}}}}' 29 # socat STDIO "$NIRI_SOCKET" <<<'{"Action":{"FocusWorkspace":{"reference":{"Id":'"''${active_workspace}"'}}}}'
27 niri msg action move-workspace-to-index --index 1 "$workspace_name" 30 # niri msg action move-workspace-to-index --reference "$workspace_name" 1
28 fi 31 fi
29 32
30 while IFS=$'\n' read -r window_json; do 33 while IFS=$'\n' read -r window_json; do
31 if [[ -n $(jq -c "$window_select" <<<"$window_json") ]]; then 34 if [[ -n $(jq -c "$window_select" <<<"$window_json") ]]; then
32 niri msg action focus-window --id "$(jq -r '.id' <<<"$window_json")" 35 if jq -e '.is_focused' <<<"$window_json" >/dev/null; then
36 niri msg action focus-workspace-previous
37 else
38 if [[ $(jq -r --arg workspace_name "$workspace_name" 'map(select(.name == $workspace_name)) | .[0].is_focused' <<<"$workspaces_json") != "true" ]] && [[ $(jq -r --arg workspace_name "$workspace_name" 'map(select(.name == $workspace_name)) | .[0].id' <<<"$workspaces_json") = $(jq -r '.workspace_id' <<<"$window_json") ]]; then
39 niri msg action focus-workspace "$workspace_name"
40 else
41 niri msg action focus-window --id "$(jq -r '.id' <<<"$window_json")"
42 fi
43 fi
33 exit 0 44 exit 0
34 fi 45 fi
35 done < <(niri msg -j windows | jq -c '.[]') 46 done < <(niri msg -j windows | jq -c '.[]')
@@ -38,7 +49,6 @@ let
38 ''; 49 '';
39 }; 50 };
40 focus-or-spawn-action = config.lib.niri.actions.spawn (lib.getExe focus_or_spawn); 51 focus-or-spawn-action = config.lib.niri.actions.spawn (lib.getExe focus_or_spawn);
41 focus-or-spawn-action-app_id = app_id: focus-or-spawn-action ''select(.app_id == "${app_id}")'';
42 52
43 with_adjacent_workspace = pkgs.writeShellApplication { 53 with_adjacent_workspace = pkgs.writeShellApplication {
44 name = "with-adjacent-workspace"; 54 name = "with-adjacent-workspace";
@@ -75,9 +85,9 @@ let
75 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET" 85 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
76 ''; 86 '';
77 }; 87 };
78 with-adjacent-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_adjacent_workspace) "^pwctl|kpxc|bmgr|edit|term$"; 88 with-adjacent-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_adjacent_workspace) "^${lib.concatMapStringsSep "|" ({ name, ...}: name) cfg.scratchspaces}$";
79 focus-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}''; 89 focus-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
80 move-column-to-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}''; 90 move-column-to-adjacent-workspace = direction: with-adjacent-workspace-action direction ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}, "focus": true}}}'';
81 91
82 with_unnamed_workspace = pkgs.writeShellApplication { 92 with_unnamed_workspace = pkgs.writeShellApplication {
83 name = "with-unnamed-workspace"; 93 name = "with-unnamed-workspace";
@@ -90,13 +100,29 @@ let
90 active_output="$(jq -r '.[] | select(.is_focused) | .output' <<<"$workspaces_json")" 100 active_output="$(jq -r '.[] | select(.is_focused) | .output' <<<"$workspaces_json")"
91 active_workspace="$(jq -r '.[] | select(.is_focused) | .id' <<<"$workspaces_json")" 101 active_workspace="$(jq -r '.[] | select(.is_focused) | .id' <<<"$workspaces_json")"
92 102
93 workspace_json="$(jq -c --arg active_output "$active_output" 'map(select(.output == $active_output and .name == null)) | sort_by(.idx) | .[0]' <<<"$workspaces_json")" 103 history_json="$(socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/niri-workspace-history.sock)"
104 workspace_json="$(jq -c --arg active_output "$active_output" --argjson history "$history_json" 'map(select(.output == $active_output and .name == null)) | map({"value": ., "history_idx": ((. as $workspace | ($history[$active_output] | index($workspace | .id))) as $active_idx | if $active_idx then $active_idx else ($history[$active_output] | length) + 1 end)}) | sort_by(.history_idx, .value.idx) | map(.value) | .[0]' <<<"$workspaces_json")"
94 [[ -n $workspace_json && $workspace_json != null ]] || exit 0 105 [[ -n $workspace_json && $workspace_json != null ]] || exit 0
95 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET" 106 jq --arg active_workspace "$active_workspace" -c "$action" <<<"$workspace_json" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
96 ''; 107 '';
97 }; 108 };
98 with-unnamed-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_unnamed_workspace); 109 with-unnamed-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_unnamed_workspace);
99 110
111 with_empty_unnamed_workspace = pkgs.writeShellApplication {
112 name = "with-empty-unnamed-workspace";
113 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
114 text = ''
115 action="$1"
116 shift
117
118 workspaces_json="$(niri msg -j workspaces)"
119 active_output="$(jq '.[] | select(.is_focused) | .output' <<<"$workspaces_json")"
120 target_workspace_id="$(jq --argjson active_output "$active_output" 'map(select(.active_window_id == null and .name == null and .output == $active_output)) | sort_by(.idx) | .[0].id' <<<"$workspaces_json")"
121 jq --argjson workspace_id "$target_workspace_id" -nc "$action" | tee /dev/stderr | socat STDIO "$NIRI_SOCKET"
122 '';
123 };
124 with-empty-unnamed-workspace-action = config.lib.niri.actions.spawn (lib.getExe with_empty_unnamed_workspace);
125
100 with_select_window = pkgs.writeShellApplication { 126 with_select_window = pkgs.writeShellApplication {
101 name = "with-select-window"; 127 name = "with-select-window";
102 runtimeInputs = [ niri pkgs.gojq pkgs.socat config.programs.fuzzel.package pkgs.gawk ]; 128 runtimeInputs = [ niri pkgs.gojq pkgs.socat config.programs.fuzzel.package pkgs.gawk ];
@@ -108,7 +134,7 @@ let
108 134
109 windows_json="$(niri msg -j windows)" 135 windows_json="$(niri msg -j windows)"
110 active_workspace="$(jq -r '.[] | select(.is_focused) | .workspace_id' <<<"$windows_json")" 136 active_workspace="$(jq -r '.[] | select(.is_focused) | .workspace_id' <<<"$windows_json")"
111 window_ix="$(gojq -r --arg active_workspace "$active_workspace" '.[] | select('"$window_select"') | "\(.title)\u0000icon\u001f\(.app_id)"' <<<"$windows_json" | fuzzel --log-level=warning --dmenu --index)" 137 window_ix="$(gojq -r --arg active_workspace "$active_workspace" '.[] | select('"$window_select"') | "\(.title)\u0000icon\u001f\(.app_id)"' <<<"$windows_json" | fuzzel --width=60 --log-level=warning --dmenu --index)"
112 # shellcheck disable=SC2016 138 # shellcheck disable=SC2016
113 window_json="$(gojq -rc --arg active_workspace "$active_workspace" --arg window_ix "$window_ix" 'map(select('"$window_select"')) | .[($window_ix | tonumber)]' <<<"$windows_json")" 139 window_json="$(gojq -rc --arg active_workspace "$active_workspace" --arg window_ix "$window_ix" 'map(select('"$window_select"')) | .[($window_ix | tonumber)]' <<<"$windows_json")"
114 140
@@ -118,12 +144,91 @@ let
118 ''; 144 '';
119 }; 145 };
120 with-select-window-action = config.lib.niri.actions.spawn (lib.getExe with_select_window); 146 with-select-window-action = config.lib.niri.actions.spawn (lib.getExe with_select_window);
147
148 with_predicate_window = pred: pkgs.writeShellApplication {
149 name = "with-predicate-window";
150 runtimeInputs = [ niri pkgs.gojq pkgs.socat ];
151 text = ''
152 action="$1"
153 shift
154
155 windows_json="$(niri msg -j windows)"
156 window_json="$(gojq -rc 'map(select(${pred})) | .[0]' <<<"$windows_json")"
157
158 [[ -z "$window_json" || $window_json = "null" ]] && exit 1
159
160 jq -c "$action" <<<"$window_json" | socat STDIO "$NIRI_SOCKET"
161 '';
162 };
163
164 with-urgent-window-action = config.lib.niri.actions.spawn (lib.getExe (with_predicate_window ".is_urgent"));
165 with-focused-window-action = config.lib.niri.actions.spawn (lib.getExe (with_predicate_window ".is_focused"));
121in { 166in {
122 imports = [ 167 imports = [
123 ./waybar.nix 168 ./waybar.nix
124 ./mako.nix 169 ./mako.nix
170 ./swayosd.nix
125 ]; 171 ];
126 172
173 options = {
174 programs.niri.scratchspaces = lib.mkOption {
175 type = lib.types.listOf (lib.types.submodule ({ config, ... }: {
176 options = {
177 name = lib.mkOption {
178 type = lib.types.str;
179 };
180 match = lib.mkOption {
181 type = lib.types.listOf (lib.types.attrsOf kdl.types.kdl-args);
182 default = [];
183 };
184 exclude = lib.mkOption {
185 type = lib.types.listOf (lib.types.attrsOf kdl.types.kdl-args);
186 default = [];
187 };
188 windowRuleExtra = lib.mkOption {
189 type = kdl.types.kdl-nodes;
190 default = [];
191 };
192 key = lib.mkOption {
193 type = lib.types.nullOr lib.types.str;
194 default = null;
195 };
196 moveKey = lib.mkOption {
197 type = lib.types.nullOr lib.types.str;
198 default = let
199 keys = lib.splitString "+" config.key;
200 defMoveKey = lib.concatStringsSep "+" (lib.flatten [
201 (lib.take (lib.length keys - 1) keys)
202 ["Shift"]
203 (lib.takeEnd 1 keys)
204 ]);
205 in if config.key == null then null else defMoveKey;
206 };
207 spawn = lib.mkOption {
208 type = lib.types.nullOr (lib.types.listOf lib.types.str);
209 default = null;
210 };
211 app-id = lib.mkOption {
212 type = lib.types.nullOr lib.types.str;
213 default = null;
214 };
215 selector = lib.mkOption {
216 type = lib.types.nullOr lib.types.str;
217 default = null;
218 };
219 };
220
221 config = lib.mkMerge [
222 (lib.mkIf (config.app-id != null) {
223 match = lib.mkDefault [ { app-id = "^${lib.escapeRegex config.app-id}$"; } ];
224 selector = lib.mkDefault "select(.app_id == \"${config.app-id}\")";
225 })
226 ];
227 }));
228 default = [];
229 };
230 };
231
127 config = { 232 config = {
128 systemd.user.services.xwayland-satellite = { 233 systemd.user.services.xwayland-satellite = {
129 Unit = { 234 Unit = {
@@ -150,461 +255,760 @@ in {
150 { event = "after-resume"; command = "${lib.getExe niri} msg action power-on-monitors"; } 255 { event = "after-resume"; command = "${lib.getExe niri} msg action power-on-monitors"; }
151 ]; 256 ];
152 timeouts = [ 257 timeouts = [
153 { timeout = 300; 258 { timeout = 540;
154 command = "${lib.getExe niri} msg action power-off-monitors"; 259 command = "${lib.getExe niri} msg action power-off-monitors";
155 } 260 }
156 ]; 261 ];
157 }; 262 };
158 263
159 programs.niri.settings = { 264 systemd.user.sockets.niri-workspace-history = {
160 prefer-no-csd = true; 265 Socket = {
161 screenshot-path = "${config.home.homeDirectory}/screenshots"; 266 ListenStream = "%t/niri-workspace-history.sock";
162 267 SocketMode = "0600";
163 hotkey-overlay.skip-at-startup = true;
164
165 input = {
166 keyboard.xkb = {
167 layout = "us,us";
168 variant = "dvp,";
169 options = "compose:caps,grp:win_space_toggle";
170 };
171
172 workspace-auto-back-and-forth = true;
173 # focus-follows-mouse.enable = true;
174 warp-mouse-to-focus = true;
175 }; 268 };
176 269 };
177 outputs = { 270 systemd.user.services.niri-workspace-history = {
178 "eDP-1" = { 271 Unit = {
179 scale = 1.5; 272 BindsTo = [ "niri.service" ];
180 position = { x = 0; y = 0; }; 273 After = [ "niri.service" ];
181 };
182 "Ancor Communications Inc ASUS PB287Q 0x0000DD9B" = {
183 scale = 1.5;
184 position = { x = 2560; y = 0; };
185 };
186 "HP Inc. HP 727pu CN4417143K" = {
187 mode = { width = 2560; height = 1440; refresh = 119.998; };
188 scale = 1;
189 position = { x = 2560; y = 0; };
190 variable-refresh-rate = "on-demand";
191 };
192 }; 274 };
193 275 Install = {
194 environment = { 276 WantedBy = [ "niri.service" ];
195 NIXOS_OZONE_WL = "1";
196 QT_QPA_PLATFORM = "wayland";
197 QT_WAYLAND_DISABLE_WINDOWDECORATION = "1";
198 GDK_BACKEND = "wayland";
199 SDL_VIDEODRIVER = "wayland";
200 DISPLAY = ":0";
201 }; 277 };
202 278 Service = {
203 debug.render-drm-device = "/dev/dri/by-path/pci-0000:00:02.0-render"; 279 Type = "simple";
204 280 Sockets = [ "niri-workspace-history.socket" ];
205 layout = { 281 ExecStart = pkgs.writers.writePython3 "niri-workspace-history" { flakeIgnore = ["E501"]; } ''
206 gaps = 8; 282 import os
207 struts = { left = 0; right = 0; top = 0; bottom = 0; }; 283 import socket
208 focus-ring = { 284 import json
209 width = 2; 285 # import sys
210 active.gradient = { 286 from collections import defaultdict
211 from = "hsla(195 100% 60% 0.75)"; 287 from threading import Thread, Lock
212 to = "hsla(155 100% 50% 0.75)"; 288 from socketserver import StreamRequestHandler, ThreadingTCPServer
213 angle = 29; 289 from contextlib import contextmanager
214 relative-to = "workspace-view"; 290 from io import TextIOWrapper
215 }; 291
216 inactive.gradient = { 292
217 from = "hsla(0 0% 42% 0.66)"; 293 @contextmanager
218 to = "hsla(0 0% 35% 0.66)"; 294 def detaching(thing):
219 angle = 29; 295 try:
220 relative-to = "workspace-view"; 296 yield thing
221 }; 297 finally:
222 }; 298 thing.detach()
223 299
224 preset-column-widths = [ 300
225 { proportion = 1. / 4.; } 301 workspace_history = defaultdict(list)
226 { proportion = 1. / 3.; } 302 history_lock = Lock()
227 { proportion = 1. / 2.; } 303
228 { proportion = 2. / 3.; } 304
229 { proportion = 3. / 4.; } 305 def monitor_niri():
230 ]; 306 workspaces = list()
231 default-column-width.proportion = 1. / 2.; 307
232 preset-window-heights = [ 308 def focus_workspace(output, workspace):
233 { proportion = 1. / 3.; } 309 with history_lock:
234 { proportion = 1. / 2.; } 310 workspace_history[output] = [workspace] + [ws for ws in workspace_history[output] if ws != workspace]
235 { proportion = 2. / 3.; } 311 # print(json.dumps(workspace_history), file=sys.stderr)
236 { proportion = 1.; } 312
237 ]; 313 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
238 314 sock.connect(os.environ["NIRI_SOCKET"])
239 always-center-single-column = true; 315 sock.send(b"\"EventStream\"\n")
316 for line in sock.makefile(buffering=1, encoding='utf-8'):
317 if line_json := json.loads(line):
318 if "WorkspacesChanged" in line_json:
319 workspaces = line_json["WorkspacesChanged"]["workspaces"]
320 for ws in workspaces:
321 if ws["is_focused"]:
322 focus_workspace(ws["output"], ws["id"])
323 if "WorkspaceActivated" in line_json:
324 for ws in workspaces:
325 if ws["id"] != line_json["WorkspaceActivated"]["id"]:
326 continue
327 focus_workspace(ws["output"], ws["id"])
328 break
329
330
331 class RequestHandler(StreamRequestHandler):
332 def handle(self):
333 with detaching(TextIOWrapper(self.wfile, encoding='utf-8', write_through=True)) as out:
334 with history_lock:
335 json.dump(workspace_history, out)
336
337
338 class Server(ThreadingTCPServer):
339 def __init__(self):
340 ThreadingTCPServer.__init__(self, ("", 8000), RequestHandler, bind_and_activate=False)
341 self.socket = socket.fromfd(3, self.address_family, self.socket_type)
342
343
344 def run_server():
345 with Server() as server:
346 server.serve_forever()
347
348
349 niri = Thread(target=monitor_niri)
350 niri.daemon = True
351 niri.start()
352
353 server_thread = Thread(target=run_server)
354 server_thread.daemon = True
355 server_thread.start()
356
357 while True:
358 server_thread.join(timeout=0.5)
359 niri.join(timeout=0.5)
360
361 if not (niri.is_alive() and server_thread.is_alive()):
362 break
363 '';
240 }; 364 };
241 365 };
242 cursor.hide-when-typing = true; 366 systemd.user.services.niri-workspace-sort = {
243 367 Unit = {
244 input = { 368 BindsTo = [ "niri.service" ];
245 touchpad.enable = false; 369 After = [ "niri.service" ];
246 trackball = {
247 scroll-method = "on-button-down";
248 scroll-button = 278;
249 };
250 }; 370 };
251 371 Install = {
252 workspaces = { 372 WantedBy = [ "niri.service" ];
253 "001" = { name = "pwctl"; open-on-output = "eDP-1"; };
254 "002" = { name = "kpxc"; open-on-output = "eDP-1"; };
255 "003" = { name = "bmgr"; open-on-output = "eDP-1"; };
256 "004" = { name = "term"; open-on-output = "eDP-1"; };
257 "005" = { name = "edit"; open-on-output = "eDP-1"; };
258 "101".name = "comm";
259 "102".name = "web";
260 # "104".name = "read";
261 # "105".name = "mon";
262 "110".name = "vid";
263 "120".name = "bmr";
264 }; 373 };
265 374 Service = {
266 window-rules = [ 375 Type = "simple";
267 # { 376 ExecStart = pkgs.writers.writePython3 "niri-workspace-sort" { flakeIgnore = ["E501"]; } ''
268 # geometry-corner-radius = 377 import os
269 # let 378 import sys
270 # allCorners = r: { bottom-left = r; bottom-right = r; top-left = r; top-right = r; }; 379 import socket
271 # in allCorners 4.; 380 import json
272 # clip-to-geometry = true; 381
273 # } 382 outputs = None
274 { 383 only = {'HDMI-A-1': {'bmr'}, 'eDP-1': {'vid'}}
275 matches = [ { app-id = "^com\.saivert\.pwvucontrol$"; } ]; 384
276 open-on-workspace = "pwctl"; 385
277 open-maximized = true; 386 class Niri(socket.socket):
278 } 387 def __init__(self):
279 { 388 super().__init__(socket.AF_UNIX, socket.SOCK_STREAM)
280 matches = [ { app-id = "^\.blueman-manager-wrapped$"; } ]; 389 super().connect(os.environ["NIRI_SOCKET"])
281 open-on-workspace = "bmgr"; 390 self.fh = super().makefile(mode='rw', buffering=1, encoding='utf-8')
282 open-maximized = true; 391
283 } 392 def cmd(self, obj):
284 { 393 print(json.dumps(obj, separators=(',', ':')), flush=True, file=self.fh)
285 matches = [ { app-id = "^org\.keepassxc\.KeePassXC$"; } ]; 394
286 block-out-from = "screencast"; 395 def event_stream(self):
287 } 396 self.cmd("EventStream")
288 { 397 return self.fh
289 matches = [ { app-id = "^org\.keepassxc\.KeePassXC$"; } ]; 398
290 excludes = [ 399
291 { title = "^Unlock Database.*"; } 400 with Niri() as niri, Niri().event_stream() as niri_stream:
292 { title = "^Access Request.*"; } 401 for line in niri_stream:
293 { title = ".*Passkey credentials$"; } 402 workspaces = None
294 ]; 403 if line_json := json.loads(line):
295 open-on-workspace = "kpxc"; 404 if "WorkspacesChanged" in line_json:
296 open-maximized = true; 405 workspaces = line_json["WorkspacesChanged"]["workspaces"]
297 open-focused = false; 406
298 } 407 if workspaces is None:
299 { 408 continue
300 matches = [ 409
301 { app-id = "^org\.keepassxc\.KeePassXC$"; title = "^Unlock Database.*"; } 410 old_outputs = outputs
302 { app-id = "^org\.keepassxc\.KeePassXC$"; title = "^Access Request.*"; } 411 outputs = {ws["output"] for ws in workspaces}
303 { app-id = "^org\.keepassxc\.KeePassXC$"; title = ".*Passkey credentials$"; } 412 if old_outputs is None:
304 ]; 413 print("Initial outputs: {}".format(outputs), file=sys.stderr)
305 open-focused = true; 414 continue
306 } 415
307 { 416 new_outputs = outputs - old_outputs
308 matches = [ { app-id = "^kitty-scratch$"; } ]; 417 if not new_outputs:
309 open-on-workspace = "term"; 418 continue
310 open-maximized = true; 419 print("New outputs: {}".format(new_outputs), file=sys.stderr)
311 } 420
312 { 421 relevant_workspaces = list(filter(lambda ws: (ws["name"] is not None) or (ws["active_window_id"] is not None), workspaces))
313 matches = [ { title = "^scratch$"; app-id = "^emacs$"; } ]; 422 target_output = next(iter(outputs - set(only.keys())))
314 open-on-workspace = "edit"; 423 if not target_output:
315 open-maximized = true; 424 continue
316 } 425 for ws in relevant_workspaces:
317 { 426 ws_ident = ws["name"] if ws["name"] is not None else (ws["output"], ws["idx"])
318 matches = [ 427 if ws["output"] not in set(only.keys()):
319 { app-id = "^emacs$"; } 428 continue
320 { app-id = "^firefox$"; } 429 if ws_ident in only[ws["output"]]:
321 ]; 430 continue
322 default-column-width.proportion = 2. / 3.; 431
323 } 432 print("{} -> {}".format(ws_ident, target_output), file=sys.stderr)
324 { 433 niri.cmd({"Action": {"MoveWorkspaceToMonitor": {"reference": {"Id": ws["id"]}, "output": target_output}}})
325 matches = [ 434 '';
326 { app-id = "^kitty$"; } 435 Restart = "on-failure";
327 { app-id = "^kitty-play$"; } 436 RestartSec = 10;
328 ];
329 default-column-width.proportion = 1. / 3.;
330 }
331 {
332 matches = [
333 { app-id = "^thunderbird$"; }
334 { app-id = "^Element$"; }
335 ];
336 open-on-workspace = "comm";
337 }
338 {
339 matches = [ { app-id = "^firefox$"; } ];
340 open-on-workspace = "web";
341 open-maximized = true;
342 variable-refresh-rate = true;
343 }
344 # {
345 # matches = [
346 # { app-id = "^evince$"; }
347 # { app-id = "^imv$"; }
348 # { app-id = "^org\.pwmt\.zathura$"; }
349 # ];
350 # open-on-workspace = "read";
351 # }
352 {
353 matches = [ { app-id = "^mpv$"; } ];
354 open-on-workspace = "vid";
355 default-column-width.proportion = 1.;
356 variable-refresh-rate = true;
357 }
358 {
359 matches = [ { app-id = "^kitty-play$"; } ];
360 open-on-workspace = "vid";
361 open-focused = false;
362 }
363 # {
364 # matches = [
365 # { app-id = "^qemu$"; }
366 # { app-id = "^virt-manager$"; }
367 # ];
368 # open-on-workspace = "mon";
369 # }
370 {
371 matches = [ { app-id = "^pdfpc$"; } ];
372 default-column-width.proportion = 1.;
373 }
374 {
375 matches = [ { app-id = "^pdfpc$"; title = "^pdfpc - presentation"; } ];
376 open-on-workspace = "bmr";
377 open-fullscreen = true;
378 }
379 {
380 matches = [
381 { app-id = "^Gimp-"; title = "^Quit GIMP$"; }
382 { app-id = "^org\.kde\.polkit-kde-authentication-agent-1$"; }
383 ];
384 open-floating = true;
385 }
386 ];
387 layer-rules = [
388 { matches = [
389 { namespace = "^notifications$"; }
390 { namespace = "^waybar$"; }
391 ];
392 block-out-from = "screencast";
393 }
394 ];
395
396 binds = with config.lib.niri.actions; {
397 "Mod+Slash".action = show-hotkey-overlay;
398
399 "Mod+Return".action = spawn terminal;
400 "Mod+Q".action = close-window;
401 "Mod+O".action = spawn (lib.getExe config.programs.fuzzel.package);
402 "Mod+Shift+O".action = spawn (lib.getExe config.programs.fuzzel.package) "--list-executables-in-path";
403
404 "Mod+Alt+E".action = spawn (lib.getExe' config.services.emacs.package "emacsclient") "-c";
405 "Mod+Alt+Y".action = spawn (lib.getExe (pkgs.writeShellApplication {
406 name = "queue-yt-dlp";
407 runtimeInputs = with pkgs; [ wl-clipboard-rs socat ];
408 text = ''
409 socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/yt-dlp.sock <<<$'{ "urls": ["'"$(wl-paste)"$'"] }'
410 '';
411 }));
412 "Mod+Alt+L".action = spawn (lib.getExe (pkgs.writeShellApplication {
413 name = "queue-yt-dlp";
414 runtimeInputs = with pkgs; [ wl-clipboard-rs config.programs.kitty.package ];
415 text = ''
416 exec -- kitty --app-id kitty-play --directory "$HOME"/media mpv "$(wl-paste)"
417 '';
418 }));
419
420 "Mod+U".action = spawn (lib.getExe (pkgs.writeShellApplication {
421 name = "qalc-fuzzel";
422 runtimeInputs = with pkgs; [ wl-clipboard-rs libqalculate config.programs.fuzzel.package coreutils findutils libnotify gnugrep ];
423 text = ''
424 RESULTS_DIR="$HOME/.cache/qalc-fuzzel"
425 prev() {
426 FOUND=false
427 while IFS= read -r line; do
428 [[ -n "$line" ]] || continue
429 FOUND=true
430 echo "$line"
431 done < <(export LC_ALL=C.UTF-8; echo; find "$RESULTS_DIR" -type f -printf $'%T@ %p\n' | sort -n | cut -d' ' -f2- | xargs -r cat)
432 $FOUND || echo
433 }
434 FUZZEL_RES=$(prev | fuzzel --dmenu --prompt "qalc> ") || exit $?
435 if [[ "$FUZZEL_RES" =~ .*\ =\ .* ]]; then
436 QALC_RES="$FUZZEL_RES"
437 QALC_RET=0
438 else
439 QALC_RES=$(qalc "$FUZZEL_RES" 2>&1)
440 QALC_RET=$?
441 fi
442 [[ -n "$QALC_RES" ]] || exit 1
443 EXISTING=false
444 set +o pipefail
445 grep -Fxrl "$QALC_RES" "$RESULTS_DIR" | xargs -r touch
446 [[ ''${PIPESTATUS[0]} -eq 0 ]] && EXISTING=true
447 set -o pipefail
448 if [[ $QALC_RET -eq 0 ]] && ! $EXISTING; then
449 set +o pipefail
450 RES_FILE="$RESULTS_DIR"/$(date -uIs).$(tr -Cd 'a-zA-Z0-9' </dev/random | head -c 10)
451 set -o pipefail
452 cat >"$RES_FILE" <<<"$QALC_RES"
453 fi
454 [[ "$QALC_RES" =~ .*\ =\ (.*) ]] && QALC_RES="''${BASH_REMATCH[1]}"
455 [[ $QALC_RET -eq 0 ]] && wl-copy "$QALC_RES"
456 notify-send "$QALC_RES"
457 '';
458 }));
459 "Mod+E".action = spawn (lib.getExe (pkgs.writeShellApplication {
460 name = "emoji-fuzzel";
461 runtimeInputs = with pkgs; [ config.programs.fuzzel.package wtype wl-clipboard-rs ];
462 text = ''
463 FUZZEL_RES=$(fuzzel --dmenu --prompt "emoji> " <"$HOME"/.local/share/emoji-data/list.txt) || exit $?
464 [[ -n "$FUZZEL_RES" ]] || exit 1
465 wl-copy "$(cut -d ':' -f 1 <<<"$FUZZEL_RES" | tr -d '\n')" && wtype -k XF86Paste
466 '';
467 }));
468 "Print".action = spawn (lib.getExe (pkgs.writeShellApplication {
469 name = "screenshot";
470 runtimeInputs = with pkgs; [ grim slurp wl-clipboard-rs coreutils ];
471 text = ''
472 grim -g "$(slurp -b 00000080 -c FFFFFFFF -s 00000000 -w 1)" - \
473 | tee "$HOME/screenshots/$(date +"%Y-%m-%dT%H:%M:%S").png" \
474 | wl-copy --type image/png
475 '';
476 }));
477 "Shift+Print".action = spawn (lib.getExe (pkgs.writeShellApplication {
478 name = "screenshot";
479 runtimeInputs = with pkgs; [ grim niri gojq wl-clipboard-rs coreutils ];
480 text = ''
481 grim -o "$(niri msg -j workspaces | jq -r '.[] | select(.is_focused) | .output')" - \
482 | tee "$HOME/screenshots/$(date +"%Y-%m-%dT%H:%M:%S").png" \
483 | wl-copy --type image/png
484 '';
485 }));
486 "Mod+B".action = with-select-window-action ".workspace_id == ($active_workspace | tonumber)" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
487 "Mod+Shift+B".action = with-select-window-action "true" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
488
489 "Mod+H".action = focus-column-left;
490 "Mod+T".action = focus-window-down;
491 "Mod+N".action = focus-window-up;
492 "Mod+S".action = focus-column-right;
493
494 "Mod+Shift+H".action = move-column-left;
495 "Mod+Shift+T".action = move-window-down;
496 "Mod+Shift+N".action = move-window-up;
497 "Mod+Shift+S".action = move-column-right;
498
499 "Mod+Control+H".action = focus-monitor-left;
500 "Mod+Control+T".action = focus-monitor-down;
501 "Mod+Control+N".action = focus-monitor-up;
502 "Mod+Control+S".action = focus-monitor-right;
503
504 "Mod+Shift+Control+H".action = move-workspace-to-monitor-left;
505 "Mod+Shift+Control+T".action = move-workspace-to-monitor-down;
506 "Mod+Shift+Control+N".action = move-workspace-to-monitor-up;
507 "Mod+Shift+Control+S".action = move-workspace-to-monitor-right;
508
509 "Mod+G".action = focus-adjacent-workspace "down";
510 "Mod+C".action = focus-adjacent-workspace "up";
511
512 "Mod+Shift+G".action = move-column-to-adjacent-workspace "down";
513 "Mod+Shift+C".action = move-column-to-adjacent-workspace "up";
514
515 "Mod+Shift+Control+G".action = move-workspace-down;
516 "Mod+Shift+Control+C".action = move-workspace-up;
517
518 "Mod+ParenLeft".action = focus-workspace "comm";
519 "Mod+Shift+ParenLeft".action = move-column-to-workspace "comm";
520
521 "Mod+ParenRight".action = focus-workspace "web";
522 "Mod+Shift+ParenRight".action = move-column-to-workspace "web";
523
524 "Mod+BraceRight".action = focus-workspace "read";
525 "Mod+Shift+BraceRight".action = move-column-to-workspace "read";
526
527 "Mod+BraceLeft".action = focus-workspace "mon";
528 "Mod+Shift+BraceLeft".action = move-column-to-workspace "mon";
529
530 "Mod+Asterisk".action = focus-workspace "vid";
531 "Mod+Shift+Asterisk".action = move-column-to-workspace "vid";
532
533 "Mod+Plus".action = with-unnamed-workspace-action ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
534 "Mod+Shift+Plus".action = with-unnamed-workspace-action ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}}}}'';
535
536 "Mod+M".action = consume-or-expel-window-left;
537 "Mod+W".action = consume-or-expel-window-right;
538
539 "Mod+R".action = switch-preset-column-width;
540 "Mod+Shift+R".action = switch-preset-window-height;
541 "Mod+F".action = center-column;
542 "Mod+Shift+F".action = maximize-column;
543 "Mod+Shift+Ctrl+F".action = fullscreen-window;
544
545 "Mod+V".action = switch-focus-between-floating-and-tiling;
546 "Mod+Shift+V".action = toggle-window-floating;
547
548 "Mod+Left".action = set-column-width "-10%";
549 "Mod+Down".action = set-window-height "-10%";
550 "Mod+Up".action = set-window-height "+10%";
551 "Mod+Right".action = set-column-width "+10%";
552
553 "Mod+Shift+Z" = {
554 action = spawn (lib.getExe niri) "msg" "action" "power-off-monitors";
555 allow-when-locked = true;
556 };
557 "Mod+Shift+L".action = spawn loginctl "lock-session";
558 "Mod+Shift+E".action = quit;
559 "Mod+Shift+Minus" = {
560 action = spawn systemctl "suspend";
561 allow-when-locked = true;
562 };
563 "Mod+Shift+Control+Minus" = {
564 action = spawn systemctl "hibernate";
565 allow-when-locked = true;
566 };
567 "Mod+Shift+P" = {
568 action = spawn (lib.getExe pkgs.playerctl) "-a" "pause";
569 allow-when-locked = true;
570 };
571
572 "XF86MonBrightnessUp" = {
573 action = spawn lightctl "-d" "-e4" "-n1" "up";
574 allow-when-locked = true;
575 };
576 "XF86MonBrightnessDown" = {
577 action = spawn lightctl "-d" "-e4" "-n1" "down";
578 allow-when-locked = true;
579 };
580 "XF86AudioRaiseVolume" = {
581 action = spawn volumectl "-d" "-u" "up";
582 allow-when-locked = true;
583 };
584 "XF86AudioLowerVolume" = {
585 action = spawn volumectl "-d" "-u" "down";
586 allow-when-locked = true;
587 };
588 "XF86AudioMute" = {
589 action = spawn volumectl "-d" "toggle-mute";
590 allow-when-locked = true;
591 };
592 "XF86AudioMicMute" = {
593 action = spawn volumectl "-d" "-m" "toggle-mute";
594 allow-when-locked = true;
595 };
596
597 "Mod+Semicolon".action = spawn makoctl "dismiss" "--group";
598 "Mod+Shift+Semicolon".action = spawn makoctl "dismiss" "--all";
599 "Mod+Period".action = spawn makoctl "menu" (lib.getExe config.programs.fuzzel.package) "--dmenu";
600 "Mod+Comma".action = spawn makoctl "restore";
601
602 "Mod+Control+A".action = focus-or-spawn-action-app_id "com.saivert.pwvucontrol" "pwctl" "pwvucontrol";
603 "Mod+Control+P".action = focus-or-spawn-action-app_id "org.keepassxc.KeePassXC" "kpxc" "keepassxc";
604 "Mod+Control+B".action = focus-or-spawn-action-app_id ".blueman-manager-wrapped" "bmgr" "blueman-manager";
605 "Mod+Control+Return".action = focus-or-spawn-action-app_id "kitty-scratch" "term" "kitty" "--app-id" "kitty-scratch";
606 "Mod+Control+E".action = focus-or-spawn-action "select(.app_id == \"emacs\" and .title == \"scratch\")" "edit" "emacsclient" "-c" "--frame-parameters=(quote (name . \"scratch\"))";
607 }; 437 };
608 }; 438 };
439
440 programs.niri.scratchspaces = [
441 { name = "pwctl";
442 key = "Mod+Control+A";
443 spawn = ["pwvucontrol"];
444 app-id = "com.saivert.pwvucontrol";
445 }
446 { name = "kpxc";
447 exclude = [
448 { title = "^Unlock Database.*"; }
449 { title = "^Access Request.*"; }
450 { title = ".*Passkey credentials$"; }
451 ];
452 windowRuleExtra = with kdl; [
453 (kdl.leaf "open-focused" false)
454 ];
455 key = "Mod+Control+P";
456 app-id = "org.keepassxc.KeePassXC";
457 spawn = [ "keepassxc" ];
458 }
459 { name = "bmgr";
460 key = "Mod+Control+B";
461 app-id = ".blueman-manager-wrapped";
462 spawn = [ "blueman-manager" ];
463 }
464 { name = "term";
465 key = "Mod+Control+Return";
466 app-id = "kitty-scratch";
467 spawn = [ "kitty" "--app-id" "kitty-scratch" ];
468 }
469 { name = "edit";
470 match = [ { title = "^scratch$"; app-id = "^emacs$"; } ];
471 key = "Mod+Control+E";
472 selector = "select(.app_id == \"emacs\" and .title == \"scratch\")";
473 spawn = [ "emacsclient" "-c" "--frame-parameters=(quote (name . \"scratch\"))" ];
474 }
475 { name = "eff";
476 key = "Mod+Control+O";
477 app-id = "com.github.wwmm.easyeffects";
478 spawn = [ "easyeffects" ];
479 }
480 { name = "time";
481 key = "Mod+Control+K";
482 app-id = "chrome-kimai.yggdrasil.li__-Default";
483 spawn = [ (toString (pkgs.resholve.writeScript "kimai" {
484 interpreter = pkgs.runtimeShell;
485 inputs = [ pkgs.dex ];
486 execer = [ "cannot:${lib.getExe pkgs.dex}" ];
487 } ''
488 exec dex $HOME/.local/state/nix/profile/share/applications/kimai.desktop
489 '')) ];
490 windowRuleExtra = with kdl; [
491 (leaf "block-out-from" "screencast")
492 ];
493 }
494 ];
495 programs.niri.config =
496 let
497 inherit (kdl) node plain leaf flag;
498 optional-node = cond: v:
499 if cond
500 then v
501 else null;
502 opt-props = lib.filterAttrs (lib.const (value: value != null));
503 in
504 [ (flag "prefer-no-csd")
505
506 (leaf "screenshot-path" "~/screenshots/%Y-%m-%dT%H:%M:%S.png")
507
508 (plain "hotkey-overlay" [
509 (flag "skip-at-startup")
510 ])
511
512 (plain "input" [
513 (plain "keyboard" [
514 (leaf "repeat-delay" 300)
515 (leaf "repeat-rate" 50)
516
517 (plain "xkb" [
518 (leaf "layout" "us,us")
519 (leaf "variant" "dvp,")
520 (leaf "options" "compose:caps,grp:win_space_toggle")
521 ])
522 ])
523
524 (flag "workspace-auto-back-and-forth")
525 # (leaf "focus-follows-mouse" {})
526 # (flag "warp-mouse-to-focus")
527
528 # (plain "touchpad" [ (flag "off") ])
529 (plain "trackball" [
530 (leaf "scroll-method" "on-button-down")
531 (leaf "scroll-button" 278)
532 ])
533 (plain "touch" [
534 (leaf "map-to-output" "eDP-1")
535 ])
536 ])
537
538 (plain "gestures" [
539 (plain "hot-corners" [(flag "off")])
540 ])
541
542 (plain "environment" (lib.mapAttrsToList leaf {
543 NIXOS_OZONE_WL = "1";
544 QT_QPA_PLATFORM = "wayland";
545 QT_WAYLAND_DISABLE_WINDOWDECORATION = "1";
546 GDK_BACKEND = "wayland";
547 SDL_VIDEODRIVER = "wayland";
548 DISPLAY = ":0";
549 ELECTRON_OZONE_PLATFORM_HINT = "auto";
550 SSH_ASKPASS_REQUIRE = "prefer";
551 SSH_ASKPASS = lib.getExe pkgs.kdePackages.ksshaskpass;
552 SUDO_ASKPASS = lib.getExe pkgs.kdePackages.ksshaskpass;
553 }))
554
555 (node "output" "eDP-1" [
556 (leaf "scale" 1.5)
557 (leaf "position" { x = 0; y = 0; })
558 ])
559 (node "output" "Ancor Communications Inc ASUS PB287Q 0x0000DD9B" [
560 (leaf "scale" 1.5)
561 (leaf "position" { x = 2560; y = 0; })
562 ])
563 (node "output" "HP Inc. HP 727pu CN4417143K" [
564 (leaf "mode" "2560x1440@119.998")
565 (leaf "scale" 1)
566 (leaf "position" { x = 2560; y = 0; })
567 (flag "variable-refresh-rate")
568 ])
569
570 (plain "debug" [
571 (leaf "render-drm-device" "/dev/dri/by-path/pci-0000:00:02.0-render")
572 ])
573
574 (plain "animations" [
575 (leaf "slowdown" 0.5)
576 (plain "workspace-switch" [(flag "off")])
577 ])
578
579 (plain "layout" [
580 (leaf "gaps" 8)
581 (plain "struts" [
582 (leaf "left" 26)
583 (leaf "right" 26)
584 (leaf "top" 0)
585 (leaf "bottom" 0)
586 ])
587 (plain "border" [
588 (leaf "width" 2)
589 (leaf "active-gradient" {
590 from = "hsla(195 100% 45% 1)";
591 to = "hsla(155 100% 37.5% 1)";
592 angle = 29;
593 relative-to = "workspace-view";
594 })
595 (leaf "inactive-gradient" {
596 from = "hsla(0 0% 27.7% 1)";
597 to = "hsla(0 0% 23% 1)";
598 angle = 29;
599 relative-to = "workspace-view";
600 })
601 ])
602 (plain "focus-ring" [
603 (flag "off")
604 ])
605
606 (plain "preset-column-widths" (map (prop: leaf "proportion" prop) [
607 (1. / 4.) (1. / 3.) (1. / 2.) (2. / 3.) (3. / 4.) (1.)
608 ]))
609 (plain "default-column-width" [ (leaf "proportion" (1. / 2.)) ])
610 (plain "preset-window-heights" (map (prop: leaf "proportion" prop) [
611 (1. / 3.) (1. / 2.) (2. / 3.) (1.)
612 ]))
613
614 (flag "always-center-single-column")
615
616 (plain "tab-indicator" [
617 (leaf "gap" 4)
618 (leaf "width" 8)
619 (leaf "gaps-between-tabs" 4)
620 (flag "place-within-column")
621 (leaf "length" { total-proportion = 1.; })
622 (leaf "active-gradient" {
623 from = "hsla(195 100% 60% 0.75)";
624 to = "hsla(155 100% 50% 0.75)";
625 angle = 29;
626 relative-to = "workspace-view";
627 })
628 (leaf "inactive-gradient" {
629 from = "hsla(0 0% 42% 0.66)";
630 to = "hsla(0 0% 35% 0.66)";
631 angle = 29;
632 relative-to = "workspace-view";
633 })
634 ])
635 ])
636
637 (plain "cursor" [
638 (flag "hide-when-typing")
639 ])
640
641 (map (name:
642 (node "workspace" name [
643 (leaf "open-on-output" "eDP-1")
644 ])
645 ) (map ({name, ...}: name) cfg.scratchspaces))
646 (map (name:
647 (leaf "workspace" name)
648 ) ["comm" "web" "vid" "bmr"])
649
650 (plain "window-rule" [
651 (leaf "clip-to-geometry" true)
652 ])
653
654 (plain "window-rule" [
655 (leaf "match" { is-floating = true; })
656 (leaf "geometry-corner-radius" 8)
657 (plain "shadow" [ (flag "on") ])
658 ])
659
660 (plain "window-rule" [
661 (leaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; })
662 (leaf "block-out-from" "screencast")
663 ])
664 (plain "window-rule" [
665 (map (title:
666 (leaf "match" { app-id = "^org\\.keepassxc\\.KeePassXC$"; inherit title; })
667 ) ["^Unlock Database.*" "^Access Request.*" ".*Passkey credentials$" "Browser Access Request$"])
668 (leaf "open-focused" true)
669 (leaf "open-floating" true)
670 ])
671
672 (map ({ name, match, exclude, windowRuleExtra, ... }:
673 (optional-node (match != []) (plain "window-rule" [
674 (map (leaf "match") match)
675 (map (leaf "exclude") exclude)
676 (leaf "open-on-workspace" name)
677 (leaf "open-maximized" true)
678 windowRuleExtra
679 ]))
680 ) cfg.scratchspaces)
681
682 (plain "window-rule" [
683 (leaf "match" { app-id = "^emacs$"; })
684 (leaf "match" { app-id = "^firefox$"; })
685 (plain "default-column-width" [(leaf "proportion" (2. / 3.))])
686 ])
687 (plain "window-rule" [
688 (leaf "match" { app-id = "^kitty$"; })
689 (leaf "match" { app-id = "^kitty-play$"; })
690 (plain "default-column-width" [(leaf "proportion" (1. / 3.))])
691 ])
692
693 (plain "window-rule" [
694 (leaf "match" { app-id = "^thunderbird$"; })
695 (leaf "match" { app-id = "^Element$"; })
696 (leaf "match" { app-id = "^chrome-web\.openrainbow\.com__-Default$"; })
697 (leaf "open-on-workspace" "comm")
698 ])
699 (plain "window-rule" [
700 (leaf "match" { app-id = "^firefox$"; })
701 (leaf "open-on-workspace" "web")
702 (leaf "open-maximized" true)
703 ])
704 (plain "window-rule" [
705 (leaf "match" { app-id = "^mpv$"; })
706 (leaf "open-on-workspace" "vid")
707 (plain "default-column-width" [(leaf "proportion" 1.)])
708 ])
709 (plain "window-rule" [
710 (leaf "match" { app-id = "^kitty-play$"; })
711 (leaf "open-on-workspace" "vid")
712 (leaf "open-focused" false)
713 ])
714 (plain "window-rule" [
715 (leaf "match" { app-id = "^chrome-audiobookshelf\.yggdrasil\.li__-Default$"; })
716 (leaf "match" { app-id = "^YouTube Music Desktop App$"; })
717 (leaf "open-on-workspace" "vid")
718 ])
719 (plain "window-rule" [
720 (leaf "match" { app-id = "^pdfpc$"; })
721 (plain "default-column-width" [(leaf "proportion" 1.)])
722 ])
723 (plain "window-rule" [
724 (leaf "match" { app-id = "^pdfpc$"; title = "^.*presentation.*$"; })
725 (plain "default-column-width" [(leaf "proportion" 1.)])
726 (leaf "open-fullscreen" true)
727 (leaf "open-on-workspace" "bmr")
728 (leaf "open-focused" false)
729 ])
730 (plain "window-rule" [
731 (map (leaf "match") [
732 { app-id = "^Gimp-"; title = "^Quit GIMP$"; }
733 { app-id = "^org\\.kde\\.polkit-kde-authentication-agent-1$"; }
734 { app-id = "^xdg-desktop-portal-gtk$"; }
735 ])
736 (leaf "open-floating" true)
737 ])
738 (plain "window-rule" [
739 (leaf "match" { app-id = "^org\\.pwmt\\.zathura$"; })
740 (leaf "match" { app-id = "^evince$"; })
741 (leaf "match" { app-id = "^org\\.gnome\\.Papers$"; })
742 (leaf "default-column-display" "tabbed")
743 ])
744
745 (plain "layer-rule" [
746 (leaf "match" { namespace = "^notifications$"; })
747 (leaf "match" { namespace = "^waybar$"; })
748 (leaf "match" { namespace = "^launcher$"; })
749 (leaf "block-out-from" "screencast")
750 ])
751
752 (plain "binds"
753 (let
754 bind = name: cfg: node name (opt-props {
755 cooldown-ms = cfg.cooldown-ms or null;
756 }
757 // (lib.optionalAttrs (!(cfg.repeat or true)) {
758 repeat = false;
759 })
760 // (lib.optionalAttrs (cfg.allow-when-locked or false) {
761 allow-when-locked = true;
762 })) (lib.mapAttrsToList leaf (lib.removeAttrs cfg.action ["__functor"]));
763 in
764 [
765 (lib.mapAttrsToList bind (with config.lib.niri.actions; {
766 "Mod+Slash".action = show-hotkey-overlay;
767
768 "Mod+Return".action = spawn terminal;
769 "Mod+Shift+Return".action =
770 let
771 nushellKitty = pkgs.symlinkJoin {
772 name = "nushell-kitty";
773 paths = [ config.programs.kitty.package ];
774 buildInputs = [ pkgs.makeWrapper ];
775 postBuild = ''
776 wrapProgram $out/bin/kitty \
777 --add-flags "--config ${pkgs.writeText "kitty.conf" ''
778 include $HOME/${config.xdg.configFile."kitty/kitty.conf".target}
779 shell ${lib.getExe config.programs.nushell.package}
780 ''}"
781 '';
782 };
783 in spawn (lib.getExe' nushellKitty "kitty");
784 "Mod+Q".action = close-window;
785 "Mod+O".action = spawn (lib.getExe config.programs.fuzzel.package);
786 "Mod+Shift+O".action = spawn (lib.getExe config.programs.fuzzel.package) "--list-executables-in-path";
787
788 "Mod+Alt+E".action = spawn (lib.getExe' config.services.emacs.package "emacsclient") "-c";
789 "Mod+Alt+Y".action = spawn (lib.getExe (pkgs.writeShellApplication {
790 name = "queue-yt-dlp";
791 runtimeInputs = with pkgs; [ wl-clipboard-rs socat ];
792 text = ''
793 socat STDIO UNIX-CONNECT:"$XDG_RUNTIME_DIR"/yt-dlp.sock <<<$'{ "urls": ["'"$(wl-paste)"$'"] }'
794 '';
795 }));
796 "Mod+Alt+L".action = spawn (lib.getExe (pkgs.writeShellApplication {
797 name = "queue-yt-dlp";
798 runtimeInputs = with pkgs; [ wl-clipboard-rs config.programs.kitty.package ];
799 text = ''
800 exec -- kitty --app-id kitty-play --directory "$HOME"/media mpv "$(wl-paste)"
801 '';
802 }));
803 "Mod+Alt+M".action = spawn (lib.getExe' pkgs.screen-message "sm") "-n" "Fira Mono" "-a" "1" "-f" "#fff" "-b" "#000";
804
805 "Mod+U".action = spawn (lib.getExe (pkgs.writeShellApplication {
806 name = "qalc-fuzzel";
807 runtimeInputs = with pkgs; [ wl-clipboard-rs libqalculate config.programs.fuzzel.package coreutils findutils libnotify gnugrep ];
808 text = ''
809 RESULTS_DIR="$HOME/.cache/qalc-fuzzel"
810 prev() {
811 FOUND=false
812 while IFS= read -r line; do
813 [[ -n "$line" ]] || continue
814 FOUND=true
815 echo "$line"
816 done < <(export LC_ALL=C.UTF-8; echo; find "$RESULTS_DIR" -type f -printf $'%T@ %p\n' | sort -n | cut -d' ' -f2- | xargs -r cat)
817 $FOUND || echo
818 }
819 FUZZEL_RES=$(prev | fuzzel --dmenu --prompt "qalc> " --width=60) || exit $?
820 if [[ "$FUZZEL_RES" =~ .*\ =\ .* ]]; then
821 QALC_RES="$FUZZEL_RES"
822 QALC_RET=0
823 else
824 QALC_RES=$(qalc -set "autocalc off" "$FUZZEL_RES" 2>&1)
825 QALC_RET=$?
826 fi
827 [[ -n "$QALC_RES" ]] || exit 1
828 EXISTING=false
829 set +o pipefail
830 grep -Fxrl "$QALC_RES" "$RESULTS_DIR" | xargs -r touch
831 [[ ''${PIPESTATUS[0]} -eq 0 ]] && EXISTING=true
832 set -o pipefail
833 if [[ $QALC_RET -eq 0 ]] && ! $EXISTING; then
834 set +o pipefail
835 RES_FILE="$RESULTS_DIR"/$(date -uIs).$(tr -Cd 'a-zA-Z0-9' </dev/random | head -c 10)
836 set -o pipefail
837 cat >"$RES_FILE" <<<"$QALC_RES"
838 fi
839 [[ "$QALC_RES" =~ .*\ =\ (.*) ]] && QALC_RES="''${BASH_REMATCH[1]}"
840 [[ $QALC_RET -eq 0 ]] && wl-copy "$QALC_RES"
841 notify-send "$QALC_RES"
842 '';
843 }));
844 "Mod+Shift+U".action =
845 let
846 qalcKitty = pkgs.symlinkJoin {
847 name = "qalc-kitty";
848 paths = [ config.programs.kitty.package ];
849 buildInputs = [ pkgs.makeWrapper ];
850 postBuild = ''
851 wrapProgram $out/bin/kitty \
852 --add-flags "--config ${pkgs.writeText "kitty.conf" ''
853 include $HOME/${config.xdg.configFile."kitty/kitty.conf".target}
854 shell ${lib.getExe pkgs.libqalculate}
855 ''}"
856 '';
857 };
858 in spawn (lib.getExe' qalcKitty "kitty");
859 "Mod+E".action = spawn (lib.getExe (pkgs.writeShellApplication {
860 name = "emoji-fuzzel";
861 runtimeInputs = with pkgs; [ config.programs.fuzzel.package wtype wl-clipboard-rs ];
862 text = ''
863 FUZZEL_RES=$(fuzzel --dmenu --prompt "emoji> " --cache "$HOME"/.cache/fuzzel-emoji --width=60 <"$HOME"/.local/share/emoji-data/list.txt) || exit $?
864 [[ -n "$FUZZEL_RES" ]] || exit 1
865 wl-copy "$(cut -d ':' -f 1 <<<"$FUZZEL_RES" | tr -d '\n')" && wtype -k XF86Paste
866 '';
867 }));
868 "Print".action = screenshot;
869 "Control+Print".action = screenshot-window;
870 "Shift+Print".action = kdl.magic-leaf "screenshot-screen";
871 "Mod+B".action = with-select-window-action ".workspace_id == ($active_workspace | tonumber)" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
872 "Mod+Shift+B".action = with-select-window-action "true" "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
873
874 "Mod+Escape" = {
875 allow-inhibiting = false;
876 action = toggle-keyboard-shortcuts-inhibit;
877 };
878
879 "Mod+H".action = focus-column-left;
880 "Mod+T".action = focus-window-down;
881 "Mod+N".action = focus-window-up;
882 "Mod+S".action = focus-column-right;
883
884 "Mod+Shift+H".action = move-column-left;
885 "Mod+Shift+T".action = move-window-down;
886 "Mod+Shift+N".action = move-window-up;
887 "Mod+Shift+S".action = move-column-right;
888
889 "Mod+Control+H".action = focus-monitor-left;
890 "Mod+Control+T".action = focus-monitor-down;
891 "Mod+Control+N".action = focus-monitor-up;
892 "Mod+Control+S".action = focus-monitor-right;
893
894 "Mod+Shift+Control+H".action = move-workspace-to-monitor-left;
895 "Mod+Shift+Control+T".action = move-workspace-to-monitor-down;
896 "Mod+Shift+Control+N".action = move-workspace-to-monitor-up;
897 "Mod+Shift+Control+S".action = move-workspace-to-monitor-right;
898
899 "Mod+G".action = focus-adjacent-workspace "down";
900 "Mod+C".action = focus-adjacent-workspace "up";
901
902 "Mod+Shift+G".action = move-column-to-adjacent-workspace "down";
903 "Mod+Shift+C".action = move-column-to-adjacent-workspace "up";
904
905 "Mod+Shift+Control+G".action = move-workspace-down;
906 "Mod+Shift+Control+C".action = move-workspace-up;
907
908 "Mod+ParenLeft".action = focus-workspace "comm";
909 "Mod+Shift+ParenLeft".action = kdl.magic-leaf "move-column-to-workspace" "comm";
910
911 "Mod+ParenRight".action = focus-workspace "web";
912 "Mod+Shift+ParenRight".action = kdl.magic-leaf "move-column-to-workspace" "web";
913
914 "Mod+BraceRight".action = focus-workspace "read";
915 "Mod+Shift+BraceRight".action = kdl.magic-leaf "move-column-to-workspace" "read";
916
917 "Mod+BraceLeft".action = focus-workspace "mon";
918 "Mod+Shift+BraceLeft".action = kdl.magic-leaf "move-column-to-workspace" "mon";
919
920 "Mod+Asterisk".action = focus-workspace "vid";
921 "Mod+Shift+Asterisk".action = kdl.magic-leaf "move-column-to-workspace" "vid";
922
923 "Mod+Plus".action = with-unnamed-workspace-action ''{"Action":{"FocusWorkspace":{"reference":{"Id": .id}}}}'';
924 "Mod+Shift+Plus".action = with-unnamed-workspace-action ''{"Action":{"MoveColumnToWorkspace":{"reference":{"Id": .id}, "focus": true}}}'';
925
926 "Mod+M".action = consume-or-expel-window-left;
927 "Mod+W".action = consume-or-expel-window-right;
928
929 "Mod+Shift+M".action = toggle-column-tabbed-display;
930
931 "Mod+R".action = switch-preset-column-width;
932 "Mod+Shift+R".action = maximize-column;
933 "Mod+Shift+Ctrl+R".action = switch-preset-window-height;
934 "Mod+F".action = center-column;
935 "Mod+Shift+F".action = toggle-windowed-fullscreen;
936 "Mod+Ctrl+Shift+F".action = fullscreen-window;
937
938 "Mod+V".action = switch-focus-between-floating-and-tiling;
939 "Mod+Shift+V".action = toggle-window-floating;
940
941 "Mod+Left".action = set-column-width "-10%";
942 "Mod+Down".action = set-window-height "-10%";
943 "Mod+Up".action = set-window-height "+10%";
944 "Mod+Right".action = set-column-width "+10%";
945
946 "Mod+Shift+Z" = {
947 action = spawn (lib.getExe niri) "msg" "action" "power-off-monitors";
948 allow-when-locked = true;
949 };
950 "Mod+Shift+L".action = spawn loginctl "lock-session";
951 "Mod+Shift+E".action = quit;
952 "Mod+Shift+Minus" = {
953 action = spawn systemctl "suspend";
954 allow-when-locked = true;
955 };
956 "Mod+Shift+Control+Minus" = {
957 action = spawn systemctl "hibernate";
958 allow-when-locked = true;
959 };
960 "Mod+Shift+P" = {
961 action = spawn (lib.getExe pkgs.playerctl) "-a" "pause";
962 allow-when-locked = true;
963 };
964
965 "XF86MonBrightnessUp" = {
966 action = spawn swayosd-client "--brightness" "raise";
967 allow-when-locked = true;
968 };
969 "XF86MonBrightnessDown" = {
970 action = spawn swayosd-client "--brightness" "lower";
971 allow-when-locked = true;
972 };
973 "XF86AudioRaiseVolume" = {
974 action = spawn swayosd-client "--output-volume" "raise";
975 allow-when-locked = true;
976 };
977 "XF86AudioLowerVolume" = {
978 action = spawn swayosd-client "--output-volume" "lower";
979 allow-when-locked = true;
980 };
981 "XF86AudioMute" = {
982 action = spawn swayosd-client "--output-volume" "mute-toggle";
983 allow-when-locked = true;
984 };
985 "XF86AudioMicMute" = {
986 action = spawn swayosd-client "--input-volume" "mute-toggle";
987 allow-when-locked = true;
988 };
989
990 "Mod+Semicolon".action = spawn makoctl "dismiss" "--group";
991 "Mod+Shift+Semicolon".action = spawn makoctl "dismiss" "--all";
992 "Mod+Period".action = spawn makoctl "menu" "--" (lib.getExe config.programs.fuzzel.package) "--dmenu";
993 "Mod+Comma".action = spawn makoctl "restore";
994
995 "Mod+Control+W".action = with-empty-unnamed-workspace-action "{\"Action\":{\"FocusWorkspace\":{\"reference\":{\"Id\": $workspace_id}}}}";
996 "Mod+Control+Shift+W".action = with-empty-unnamed-workspace-action "{\"Action\":{\"MoveColumnToWorkspace\":{\"reference\":{\"Id\": $workspace_id}, \"focus\": true}}}";
997
998 "Mod+X".action = set-dynamic-cast-window;
999 "Mod+Shift+X".action = set-dynamic-cast-monitor;
1000 "Mod+Control+Shift+X".action = clear-dynamic-cast-target;
1001
1002 "Mod+D".action = with-urgent-window-action "{\"Action\":{\"FocusWindow\":{\"id\": .id}}}";
1003 "Mod+Shift+D".action = with-focused-window-action "{\"Action\":{\"UnsetUrgent\":{\"id\": .id}}}";
1004
1005 "Mod+K".action = spawn (lib.getExe' pkgs.worktime "worktime-ui");
1006 "Mod+Shift+K".action = spawn (lib.getExe' pkgs.worktime "worktime-stop");
1007 }))
1008 (map ({ name, selector, spawn, key, ...}: if key != null && selector != null && spawn != null then bind key { action = focus-or-spawn-action selector name spawn; } else null) cfg.scratchspaces)
1009 (map ({ name, moveKey, ...}: if moveKey != null then bind moveKey { action = kdl.magic-leaf "move-column-to-workspace" name; } else null) cfg.scratchspaces)
1010 ]
1011 ))
1012 ];
609 }; 1013 };
610} 1014}
diff --git a/accounts/gkleen@sif/niri/mako.nix b/accounts/gkleen@sif/niri/mako.nix
index e06d3b3a..eba26caa 100644
--- a/accounts/gkleen@sif/niri/mako.nix
+++ b/accounts/gkleen@sif/niri/mako.nix
@@ -1,35 +1,41 @@
1{ config, lib, ... }: 1{ config, lib, pkgs, ... }:
2{ 2{
3 config = { 3 config = {
4 services.mako = { 4 services.mako = {
5 enable = true; 5 enable = true;
6 font = "Fira Sans 10"; 6 settings = {
7 format = "<i>%s</i>\\n%b"; 7 font = "Fira Sans 10";
8 margin = "2"; 8 format = "<i>%s</i>\\n%b";
9 maxVisible = -1; 9 margin = "2";
10 backgroundColor = "#000000dd"; 10 max-visible = -1;
11 progressColor = "source #223544ff"; 11 background-color = "#000000dd";
12 width = 384; 12 progress-color = "source #223544ff";
13 extraConfig = '' 13 width = 384;
14 outer-margin=1 14 outer-margin = 1;
15 max-history=100 15 max-history = 100;
16 max-icon-size=48 16 max-icon-size = 48;
17 17 };
18 [grouped] 18 criteria = {
19 format=<b>(%g)</b> <i>%s</i>\n%b 19 grouped.format = "<b>(%g)</b> <i>%s</i>\\n%b";
20 20 "urgency=low".text-color = "#999999ff";
21 [urgency=low] 21 "urgency=critical".background-color = "#900000dd";
22 text-color=#999999ff 22 "app-name=Element".group-by = "summary";
23 23 "app-name=poweralertd" = {
24 [urgency=critical] 24 history = false;
25 background-color=#900000dd 25 ignore-timeout = true;
26 26 default-timeout = 2000;
27 [app-name=Element] 27 };
28 group-by=summary 28 "app-name=worktime".history = false;
29 29 "mode=silent".invisible = true;
30 [mode=silent] 30 };
31 invisible=1 31 package = pkgs.symlinkJoin {
32 ''; 32 name = "${pkgs.mako.name}-wrapped";
33 paths = with pkgs; [ mako ];
34 inherit (pkgs.mako) meta;
35 postBuild = ''
36 rm -r $out/share/dbus-1
37 '';
38 };
33 }; 39 };
34 systemd.user.services.mako = { 40 systemd.user.services.mako = {
35 Unit = { 41 Unit = {
diff --git a/accounts/gkleen@sif/niri/swayosd.nix b/accounts/gkleen@sif/niri/swayosd.nix
new file mode 100644
index 00000000..54ebb302
--- /dev/null
+++ b/accounts/gkleen@sif/niri/swayosd.nix
@@ -0,0 +1,66 @@
1{ pkgs, ... }:
2{
3 config = {
4 services.swayosd = {
5 enable = true;
6 topMargin = 0.4769706078;
7 stylePath = pkgs.runCommand "style.css" {
8 passAsFile = [ "src" ];
9 src = ''
10 window#osd {
11 padding: 12px 20px;
12 border-radius: 999px;
13 border: none;
14 background: rgba(0, 0, 0, 0.87);
15
16 #container {
17 margin: 16px;
18 }
19
20 image,
21 label {
22 color: rgb(255, 255, 255);
23
24 &:disabled {
25 opacity: 1;
26 color: rgb(84, 84, 84);
27 }
28 }
29
30 progressbar {
31 min-height: 6px;
32 border-radius: 999px;
33 background: transparent;
34 border: none;
35
36 trough, progress {
37 min-height: inherit;
38 border-radius: inherit;
39 border: none;
40 }
41
42 trough {
43 background: rgb(127, 127, 127);
44 }
45 progress {
46 background: rgb(255, 255, 255);
47 }
48
49 &:disabled {
50 opacity: 1;
51
52 trough {
53 background: rgb(19, 19, 19);
54 }
55 progress {
56 background: rgb(38, 38, 38);
57 }
58 }
59 }
60 }
61 '';
62 buildInputs = with pkgs; [sass];
63 } "scss -C --sourcemap=none --style=compact $srcPath $out";
64 };
65 };
66}
diff --git a/accounts/gkleen@sif/niri/waybar.nix b/accounts/gkleen@sif/niri/waybar.nix
index 79c429f8..c02a9a76 100644
--- a/accounts/gkleen@sif/niri/waybar.nix
+++ b/accounts/gkleen@sif/niri/waybar.nix
@@ -1,5 +1,7 @@
1{ lib, pkgs, ... }: 1{ lib, config, pkgs, ... }:
2{ 2let
3 swayosd-client = lib.getExe' config.services.swayosd.package "swayosd-client";
4in {
3 config = { 5 config = {
4 programs.waybar = { 6 programs.waybar = {
5 enable = true; 7 enable = true;
@@ -18,15 +20,21 @@
18 { 20 {
19 layer = "top"; 21 layer = "top";
20 position = "top"; 22 position = "top";
21 height = 14; 23 height = 21;
22 output = [ "eDP-1" "DP-2" "DP-3" ]; 24 output = [ "eDP-1" "DP-2" "DP-3" ];
23 modules-left = [ "niri/workspaces" ]; 25 modules-left = [ "niri/workspaces" ];
24 modules-center = [ "niri/window" ]; 26 modules-center = [ "niri/window" ];
25 modules-right = [ # "custom/worktime" "custom/worktime-today" 27 modules-right = [ "custom/worktime" "custom/worktime-today"
26 "custom/weather" 28 "custom/weather"
27 "custom/keymap" 29 "custom/keymap"
28 "privacy" "tray" "wireplumber" "backlight" "battery" "idle_inhibitor" "custom/mako" "clock" ]; 30 "privacy" "tray" "wireplumber" "backlight" "battery" "idle_inhibitor" "custom/mako" "custom/lid_inhibitor" "clock" ];
29 31
32 "custom/lid_inhibitor" = {
33 format = "{}";
34 return-type = "json";
35 exec = lib.getExe pkgs.waybar-systemd-inhibit;
36 on-click = lib.getExe' pkgs.waybar-systemd-inhibit "waybar-systemd-inhibit-toggle";
37 };
30 "custom/mako" = { 38 "custom/mako" = {
31 format = "{}"; 39 format = "{}";
32 return-type = "json"; 40 return-type = "json";
@@ -59,7 +67,7 @@
59 text = f"<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>" # noqa: E501 67 text = f"<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>" # noqa: E501
60 if is_silent: 68 if is_silent:
61 text = f"<span color=\"#ffffff\">{text}</span>" 69 text = f"<span color=\"#ffffff\">{text}</span>"
62 print(json.dumps({'text': text}, separators=(',', ':')), flush=True) # noqa: E501 70 print(json.dumps({'text': text, 'tooltip': ', '.join(modes)}, separators=(',', ':')), flush=True) # noqa: E501
63 71
64 async def on_properties_changed(interface_name, changed_properties, invalidated_properties): # noqa: E501 72 async def on_properties_changed(interface_name, changed_properties, invalidated_properties): # noqa: E501
65 if "Modes" not in invalidated_properties: 73 if "Modes" not in invalidated_properties:
@@ -120,16 +128,16 @@
120 }; 128 };
121 "custom/worktime" = { 129 "custom/worktime" = {
122 interval = 60; 130 interval = 60;
123 exec = lib.getExe pkgs.worktime; 131 exec = "${lib.getExe pkgs.worktime} time --waybar";
124 tooltip = false; 132 return-type = "json";
125 }; 133 };
126 "custom/worktime-today" = { 134 "custom/worktime-today" = {
127 interval = 60; 135 interval = 60;
128 exec = "${lib.getExe pkgs.worktime} today"; 136 exec = "${lib.getExe pkgs.worktime} today --waybar";
129 tooltip = false; 137 return-type = "json";
130 }; 138 };
131 "niri/workspaces" = { 139 "niri/workspaces" = {
132 ignore = ["pwctl" "kpxc" "bmgr" "edit" "term"]; 140 ignore = map ({ name, ... }: name) config.programs.niri.scratchspaces;
133 }; 141 };
134 "niri/window" = { 142 "niri/window" = {
135 separate-outputs = true; 143 separate-outputs = true;
@@ -190,8 +198,8 @@
190 icon-size = iconSize; 198 icon-size = iconSize;
191 tooltip-format = "{percent}%"; 199 tooltip-format = "{percent}%";
192 format-icons = ["&#xf00da;" "&#xf00db;" "&#xf00dc;" "&#xf00dd;" "&#xf00de;" "&#xf00df;" "&#xf00e0;"]; 200 format-icons = ["&#xf00da;" "&#xf00db;" "&#xf00dc;" "&#xf00dd;" "&#xf00de;" "&#xf00df;" "&#xf00e0;"];
193 on-scroll-up = "lightctl -d -e4 -n1 up"; 201 on-scroll-up = "${swayosd-client} --brightness raise";
194 on-scroll-down = "lightctl -d -e4 -n1 down"; 202 on-scroll-down = "${swayosd-client} --brightness lower";
195 }; 203 };
196 wireplumber = { 204 wireplumber = {
197 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>"; 205 format = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">{icon}</span>";
@@ -200,22 +208,22 @@
200 format-icons = ["&#xf057f;" "&#xf0580;" "&#xf057e;"]; 208 format-icons = ["&#xf057f;" "&#xf0580;" "&#xf057e;"];
201 format-muted = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">&#xf075f;</span>"; 209 format-muted = "<span font=\"Symbols Nerd Font Mono\" size=\"90%\">&#xf075f;</span>";
202 # ignored-sinks = ["Easy Effects Sink"]; 210 # ignored-sinks = ["Easy Effects Sink"];
203 on-scroll-up = "volumectl -d -u up"; 211 on-scroll-up = "${swayosd-client} --output-volume raise";
204 on-scroll-down = "volumectl -d -u down"; 212 on-scroll-down = "${swayosd-client} --output-volume lower";
205 on-click = "volumectl -d toggle-mute"; 213 on-click = "${swayosd-client} --output-volume mute-toggle";
206 }; 214 };
207 } 215 }
208 { 216 {
209 layer = "top"; 217 layer = "top";
210 position = "top"; 218 position = "top";
211 height = 14; 219 height = 14;
212 output = [ "!eDP-1" "!DP-2" "!DP-3" ]; 220 output = [ "!eDP-1" "!DP-2" "!DP-3" "*" ];
213 modules-left = [ "niri/workspaces" ]; 221 modules-left = [ "niri/workspaces" ];
214 modules-center = [ "niri/window" ]; 222 modules-center = [ "niri/window" ];
215 modules-right = [ "clock" ]; 223 modules-right = [ "clock" ];
216 224
217 "niri/workspaces" = { 225 "niri/workspaces" = {
218 ignore = ["pwctl" "kpxc" "bmgr" "edit" "term"]; 226 ignore = map ({ name, ... }: name) config.programs.niri.scratchspaces;
219 }; 227 };
220 "niri/window" = { 228 "niri/window" = {
221 separate-outputs = true; 229 separate-outputs = true;
@@ -241,7 +249,7 @@
241 249
242 * { 250 * {
243 border: none; 251 border: none;
244 font-family: "Fira Sans Nerd Font"; 252 font-family: "Fira Sans";
245 font-size: 10pt; 253 font-size: 10pt;
246 min-height: 0; 254 min-height: 0;
247 } 255 }
@@ -252,10 +260,10 @@
252 } 260 }
253 261
254 .modules-left { 262 .modules-left {
255 margin-left: 8px; 263 margin-left: 38px;
256 } 264 }
257 .modules-right { 265 .modules-right {
258 margin-right: 8px; 266 margin-right: 38px;
259 } 267 }
260 268
261 .module { 269 .module {
@@ -280,17 +288,18 @@
280 color: @grey; 288 color: @grey;
281 margin: 0 5px; 289 margin: 0 5px;
282 } 290 }
283 #custom-weather, #custom-worktime-today { 291 #custom-weather {
284 margin-right: 3px; 292 margin-right: 3px;
285 } 293 }
286 #custom-keymap, #custom-weather { 294 #custom-keymap {
287 margin-left: 3px; 295 margin-left: 3px;
296 margin-right: 3px;
288 } 297 }
289 298
290 #tray { 299 #tray {
291 margin: 0; 300 margin: 0;
292 } 301 }
293 #battery, #idle_inhibitor, #backlight, #wireplumber, #custom-mako { 302 #battery, #idle_inhibitor, #backlight, #wireplumber, #custom-mako, #custom-lid_inhibitor {
294 color: @grey; 303 color: @grey;
295 margin: 0 5px 0 2px; 304 margin: 0 5px 0 2px;
296 } 305 }
@@ -299,7 +308,11 @@
299 margin-left: 6px; 308 margin-left: 6px;
300 } 309 }
301 #custom-mako { 310 #custom-mako {
302 margin-right: 2px; 311 margin-right: 4px;
312 margin-left: 3px;
313 }
314 #custom-lid_inhibitor {
315 margin-right: 3px;
303 margin-left: 3px; 316 margin-left: 3px;
304 } 317 }
305 #battery { 318 #battery {
@@ -320,17 +333,24 @@
320 #idle_inhibitor.activated { 333 #idle_inhibitor.activated {
321 color: @white; 334 color: @white;
322 } 335 }
336 #custom-worktime.running, #custom-worktime-today.running {
337 color: @white;
338 }
339 #custom-worktime.over, #custom-worktime-today.over {
340 color: @orange;
341 }
323 342
324 #idle_inhibitor { 343 #idle_inhibitor, #custom-lid_inhibitor {
325 padding-top: 1px; 344 padding-top: 1px;
326 } 345 }
327 346
328 #privacy { 347 #privacy {
329 color: @red; 348 color: @red;
330 margin: -1px 2px 0px 5px; 349 margin: -1px 4px 0px 3px;
331 } 350 }
332 #clock { 351 #clock {
333 /* margin-right: 5px; */ 352 /* margin-right: 5px; */
353 font-feature-settings: "tnum";
334 } 354 }
335 ''; 355 '';
336 }; 356 };
diff --git a/accounts/gkleen@sif/ssh-hosts.nix b/accounts/gkleen@sif/ssh-hosts.nix
index 107f1e76..a250509b 100644
--- a/accounts/gkleen@sif/ssh-hosts.nix
+++ b/accounts/gkleen@sif/ssh-hosts.nix
@@ -1,5 +1,12 @@
1{ pkgs, ... }: 1{ lib, pkgs, ... }:
2{ 2let
3 autosshProxyPorts = {
4 "ssh.math.lmu.de" = 8118;
5 "mathw0h" = 8122;
6 "mathw0e" = 8124;
7 };
8 autosshProxy = host: "${lib.getExe pkgs.socat} - SOCKS4A:127.0.0.1:%h:%p,socksport=${toString autosshProxyPorts.${host}}";
9in {
3 "git.ymir" = 10 "git.ymir" =
4 { hostname = "ymir.yggdrasil.li"; 11 { hostname = "ymir.yggdrasil.li";
5 user = "gitolite"; 12 user = "gitolite";
@@ -290,15 +297,15 @@
290 }; 297 };
291 "mathw0d" = 298 "mathw0d" =
292 { hostname = "mathw0d.mathinst.loc"; 299 { hostname = "mathw0d.mathinst.loc";
293 proxyJump = "mathw0h"; 300 proxyCommand = autosshProxy "mathw0h";
294 }; 301 };
295 "mathw0e" = 302 "mathw0e" =
296 { hostname = "mathw0e.mathinst.loc"; 303 { hostname = "mathw0e.mathinst.loc";
297 proxyJump = "mathw0h"; 304 proxyCommand = autosshProxy "mathw0h";
298 }; 305 };
299 "mathw0f" = 306 "mathw0f" =
300 { hostname = "mathw0f.mathinst.loc"; 307 { hostname = "mathw0f.mathinst.loc";
301 proxyJump = "mathw0h"; 308 proxyCommand = autosshProxy "mathw0h";
302 }; 309 };
303 "mathw0g" = 310 "mathw0g" =
304 { hostname = "mathw0g.mathinst.loc"; 311 { hostname = "mathw0g.mathinst.loc";
@@ -306,8 +313,8 @@
306 "mathw0h" = 313 "mathw0h" =
307 { hostname = "mathw0h.mathinst.loc"; 314 { hostname = "mathw0h.mathinst.loc";
308 }; 315 };
309 "proxy.mathw0g" = 316 "proxy.ssh.math.lmu.de" =
310 { hostname = "mathw0g.mathinst.loc"; 317 { hostname = "ssh.math.lmu.de";
311 extraOptions = { 318 extraOptions = {
312 ControlPath = "none"; 319 ControlPath = "none";
313 ExitOnForwardFailure = "yes"; 320 ExitOnForwardFailure = "yes";
@@ -317,7 +324,17 @@
317 }; 324 };
318 "proxy.mathw0h" = 325 "proxy.mathw0h" =
319 { hostname = "mathw0h.mathinst.loc"; 326 { hostname = "mathw0h.mathinst.loc";
320 proxyJump = "proxy.mathw0g"; 327 proxyCommand = autosshProxy "ssh.math.lmu.de";
328 extraOptions = {
329 ControlPath = "none";
330 ExitOnForwardFailure = "yes";
331 ServerAliveCountMax = "15";
332 ServerAliveInterval = "2";
333 };
334 };
335 "proxy.mathw0e" =
336 { hostname = "mathw0e.mathinst.loc";
337 proxyCommand = autosshProxy "mathw0h";
321 extraOptions = { 338 extraOptions = {
322 ControlPath = "none"; 339 ControlPath = "none";
323 ExitOnForwardFailure = "yes"; 340 ExitOnForwardFailure = "yes";
@@ -327,7 +344,7 @@
327 }; 344 };
328 "vrt-kvm06" = 345 "vrt-kvm06" =
329 { hostname = "vrt-kvm06"; 346 { hostname = "vrt-kvm06";
330 proxyJump = "mathw0e"; 347 proxyCommand = autosshProxy "mathw0e";
331 user = "root"; 348 user = "root";
332 extraOptions = { 349 extraOptions = {
333 PasswordAuthentication = "yes"; 350 PasswordAuthentication = "yes";
@@ -336,7 +353,7 @@
336 }; 353 };
337 "vrt-kvm05" = 354 "vrt-kvm05" =
338 { hostname = "vrt-kvm05"; 355 { hostname = "vrt-kvm05";
339 proxyJump = "mathw0e"; 356 proxyCommand = autosshProxy "mathw0e";
340 user = "root"; 357 user = "root";
341 extraOptions = { 358 extraOptions = {
342 PasswordAuthentication = "yes"; 359 PasswordAuthentication = "yes";
@@ -345,7 +362,7 @@
345 }; 362 };
346 "vrt-kvm04" = 363 "vrt-kvm04" =
347 { hostname = "vrt-kvm04"; 364 { hostname = "vrt-kvm04";
348 proxyJump = "mathw0e"; 365 proxyCommand = autosshProxy "mathw0e";
349 user = "root"; 366 user = "root";
350 extraOptions = { 367 extraOptions = {
351 PasswordAuthentication = "yes"; 368 PasswordAuthentication = "yes";
@@ -354,7 +371,7 @@
354 }; 371 };
355 "vrt-kvm02" = 372 "vrt-kvm02" =
356 { hostname = "vrt-kvm02"; 373 { hostname = "vrt-kvm02";
357 proxyJump = "mathw0e"; 374 proxyCommand = autosshProxy "mathw0e";
358 user = "root"; 375 user = "root";
359 extraOptions = { 376 extraOptions = {
360 PasswordAuthentication = "yes"; 377 PasswordAuthentication = "yes";
@@ -363,7 +380,7 @@
363 }; 380 };
364 "vrt-kvm03" = 381 "vrt-kvm03" =
365 { hostname = "vrt-kvm03"; 382 { hostname = "vrt-kvm03";
366 proxyJump = "mathw0e"; 383 proxyCommand = autosshProxy "mathw0e";
367 user = "root"; 384 user = "root";
368 extraOptions = { 385 extraOptions = {
369 PasswordAuthentication = "yes"; 386 PasswordAuthentication = "yes";
@@ -372,7 +389,7 @@
372 }; 389 };
373 "vrt-kvm01" = 390 "vrt-kvm01" =
374 { hostname = "vrt-kvm01"; 391 { hostname = "vrt-kvm01";
375 proxyJump = "mathw0e"; 392 proxyCommand = autosshProxy "mathw0e";
376 user = "root"; 393 user = "root";
377 extraOptions = { 394 extraOptions = {
378 PasswordAuthentication = "yes"; 395 PasswordAuthentication = "yes";
@@ -381,39 +398,44 @@
381 }; 398 };
382 "tts-www01" = 399 "tts-www01" =
383 { hostname = "tts-www01.mathinst.loc"; 400 { hostname = "tts-www01.mathinst.loc";
384 proxyJump = "mathw0h"; 401 proxyCommand = autosshProxy "mathw0h";
385 user = "root"; 402 user = "root";
386 }; 403 };
387 "vpn-wg01" = 404 "vpn-wg01" =
388 { hostname = "vpn-wg01.mathinst.loc"; 405 { hostname = "vpn-wg01.mathinst.loc";
389 proxyJump = "mathw0h"; 406 proxyCommand = autosshProxy "mathw0h";
390 user = "root"; 407 user = "root";
391 }; 408 };
392 "repo-apt01" = 409 "repo-apt01" =
393 { hostname = "repo-apt01.mathinst.loc"; 410 { hostname = "repo-apt01.mathinst.loc";
394 proxyJump = "mathw0h"; 411 proxyCommand = autosshProxy "mathw0h";
395 user = "root"; 412 user = "root";
396 }; 413 };
397 "ldap-lmumr01" = 414 "ldap-lmumr01" =
398 { hostname = "ldap-lmumr01.mathinst.loc"; 415 { hostname = "ldap-lmumr01.mathinst.loc";
399 proxyJump = "mathw0h"; 416 proxyCommand = autosshProxy "mathw0h";
400 user = "root"; 417 user = "root";
401 }; 418 };
402 "mail-mi01" = 419 "mail-mi01" =
403 { hostname = "mail-mi01.mathinst.loc"; 420 { hostname = "mail-mi01.mathinst.loc";
404 proxyJump = "mathw0h"; 421 proxyCommand = autosshProxy "mathw0h";
405 }; 422 };
406 "mail-www02" = 423 "mail-www02" =
407 { hostname = "mail-www02.mathinst.loc"; 424 { hostname = "mail-www02.mathinst.loc";
408 proxyJump = "mathw0h"; 425 proxyCommand = autosshProxy "mathw0h";
409 }; 426 };
410 "dpl-fai01" = 427 "dpl-fai01" =
411 { hostname = "dpl-fai01.mathinst.loc"; 428 { hostname = "dpl-fai01.mathinst.loc";
412 user = "root"; 429 user = "root";
413 }; 430 };
431 "dpl-fai02" =
432 { hostname = "dpl-fai02.mathinst.loc";
433 user = "root";
434 proxyJump = "mgmt01";
435 };
414 "math05" = 436 "math05" =
415 { hostname = "math05.mathinst.loc"; 437 { hostname = "math05.mathinst.loc";
416 proxyJump = "mathw0h"; 438 proxyCommand = autosshProxy "mathw0h";
417 extraOptions.KexAlgorithms = "+diffie-hellman-group1-sha1"; 439 extraOptions.KexAlgorithms = "+diffie-hellman-group1-sha1";
418 }; 440 };
419 "switch01" = 441 "switch01" =
@@ -439,20 +461,20 @@
439 }; 461 };
440 "www-mi01" = 462 "www-mi01" =
441 { hostname = "www-mi01.mathinst.loc"; 463 { hostname = "www-mi01.mathinst.loc";
442 proxyJump = "mathw0h"; 464 proxyCommand = autosshProxy "mathw0h";
443 }; 465 };
444 "cip04" = 466 "cip04" =
445 { hostname = "cip04.cipmath.loc"; 467 { hostname = "cip04.cipmath.loc";
446 proxyJump = "mathw0h"; 468 proxyCommand = autosshProxy "mathw0h";
447 }; 469 };
448 "mgmt-cls01" = 470 "mgmt-cls01" =
449 { user = "root"; 471 { user = "root";
450 hostname = "mgmt-cls01.cipmath.loc"; 472 hostname = "mgmt-cls01.cipmath.loc";
451 proxyJump = "ssh.math.lmu.de"; 473 proxyCommand = autosshProxy "ssh.math.lmu.de";
452 }; 474 };
453 "mgmt01" = 475 "mgmt01" =
454 { hostname = "mgmt01.mathinst.loc"; 476 { hostname = "mgmt01.mathinst.loc";
455 proxyJump = "mathw0h"; 477 proxyCommand = autosshProxy "mathw0h";
456 user = "root"; 478 user = "root";
457 }; 479 };
458 "ssh-lb01" = 480 "ssh-lb01" =
@@ -471,17 +493,17 @@
471 "rdlx02" = { hostname = "rdlx02.mathinst.loc"; proxyJump = "mgmt01"; }; 493 "rdlx02" = { hostname = "rdlx02.mathinst.loc"; proxyJump = "mgmt01"; };
472 "math0d" = 494 "math0d" =
473 { hostname = "math0d.mathinst.loc"; 495 { hostname = "math0d.mathinst.loc";
474 proxyJump = "mathw0h"; 496 proxyCommand = autosshProxy "mathw0h";
475 }; 497 };
476 "dhcp01" = 498 "dhcp01" =
477 { hostname = "dhcp01.mathinst.loc"; 499 { hostname = "dhcp01.mathinst.loc";
478 user = "root"; 500 user = "root";
479 proxyJump = "mathw0h"; 501 proxyCommand = autosshProxy "mathw0h";
480 }; 502 };
481 "dhcp02" = 503 "dhcp02" =
482 { hostname = "dhcp02.mathinst.loc"; 504 { hostname = "dhcp02.mathinst.loc";
483 user = "root"; 505 user = "root";
484 proxyJump = "mathw0h"; 506 proxyCommand = autosshProxy "mathw0h";
485 }; 507 };
486 "cc-gpu-l01" = 508 "cc-gpu-l01" =
487 { hostname = "cc-gpu-l01.mathinst.loc"; 509 { hostname = "cc-gpu-l01.mathinst.loc";
@@ -546,7 +568,7 @@
546 user = "root"; 568 user = "root";
547 }; 569 };
548 "nas*" = 570 "nas*" =
549 { proxyJump = "mathw0e"; 571 { proxyCommand = autosshProxy "mathw0e";
550 user = "admin"; 572 user = "admin";
551 extraOptions = { 573 extraOptions = {
552 PasswordAuthentication = "yes"; 574 PasswordAuthentication = "yes";
@@ -554,9 +576,4 @@
554 HostKeyAlgorithms = "+ecdsa-sha2-nistp256"; 576 HostKeyAlgorithms = "+ecdsa-sha2-nistp256";
555 }; 577 };
556 }; 578 };
557 "game01" =
558 { hostname = "game01.yggdrasil.li";
559 user = "factorio";
560 identityFile = "~/.ssh/gkleen@sif.midgard.yggdrasil";
561 };
562} 579}
diff --git a/accounts/gkleen@sif/synadm/default.nix b/accounts/gkleen@sif/synadm/default.nix
new file mode 100644
index 00000000..0a8e0d4c
--- /dev/null
+++ b/accounts/gkleen@sif/synadm/default.nix
@@ -0,0 +1,9 @@
1{ config, pkgs, ... }:
2{
3 home.packages = with pkgs; [ synadm ];
4 sops.secrets."synadm.yaml" = {
5 format = "binary";
6 sopsFile = ./synadm_yaml;
7 path = config.xdg.configHome + "/synadm.yaml";
8 };
9}
diff --git a/accounts/gkleen@sif/synadm/synadm_yaml b/accounts/gkleen@sif/synadm/synadm_yaml
new file mode 100644
index 00000000..8d951ccc
--- /dev/null
+++ b/accounts/gkleen@sif/synadm/synadm_yaml
@@ -0,0 +1,15 @@
1{
2 "data": "ENC[AES256_GCM,data:qJy4Pmbbxja4jmW7OaHsD0mQZ7anZwLhiVmAgkavb+CqwWGDnUBXdz22/MHCbxng5NshcFSpBoCBhgY6B9V2bUiES6bH9AtMlDcs9ebKGMArBTUTnQ2MjWQGfQTqraWdNgy+n327uj9swwCH8EZXdYH/Hlv0t/re470W+VOHeXhGghQ3Y9IGz2sgfvMGr8QxaJNydZz85rgs5QUP/PglCwWIOw2mY1EX2vYwnmiAo49LmIEaxWvRi++KHaeBveDt0nlkJwzUlipL2VOKWxkgpK3yGucQn2mz+FRe1btp+4KGm8H17eUI9FO9sBwq,iv:kgM921ovwCgDYHQj3c5Rupy/8JxHehxUD2jb1k9Ik2Y=,tag:3TLQkJbv679VWy8V2TMugw==,type:str]",
3 "sops": {
4 "age": [
5 {
6 "recipient": "age1rmmhetcmllq0ahl5qznlr0eya2zdxwl9h6y5wnl97d2wtyx5t99sm2u866",
7 "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6bzVHUGNxZTF2WC9MYmZr\neGdVVzJXN3lGdEk3cTBER3J6UTFtcUJna2d3CjdNQmRXd2haZW1MYlJzNkk1dWVD\nVTFQc2gvS0JrejJ6SFh2MXpPWDZpRE0KLS0tIE0wTC85bEpvSnlGdGFkZVFhNjFZ\nbzRiZkxMWUg2ODNVUlBmNFlPNGRrZlkK1VXLJWcssv3ETyZSSM/Hhn5VIaI9iov9\nzShZA9Zx/FX6PYTuUMC29pJ57gKourcIxa/7HwSv/xYn1A6WcYfgSg==\n-----END AGE ENCRYPTED FILE-----\n"
8 }
9 ],
10 "lastmodified": "2025-05-18T11:03:42Z",
11 "mac": "ENC[AES256_GCM,data:yonJC68PhilAgEHNNJQ8nO53Qo3rx/LnfiOWfuMm24bOUIH9QM3WZZxpigd7bHI4eC4TqRb4LvcSi0nEURTRAhwiTqGNrWbpw2Iv3n5dhLEN9aTcetG5ZuhaXqfVUoML45/ovdBZG/0l8+XIHqxN2M/g/h4JwKoR/6lqzcrVhgo=,iv:xvxBJwy+E5zUdjhGPdZPdy7tnBIEj50hfiDJFsS3wNg=,tag:L4Fas36ZOg4h0QQwC4gjNA==,type:str]",
12 "unencrypted_suffix": "_unencrypted",
13 "version": "3.10.2"
14 }
15}
diff --git a/accounts/gkleen@sif/systemd.nix b/accounts/gkleen@sif/systemd.nix
index d3a1a4c7..90cccc58 100644
--- a/accounts/gkleen@sif/systemd.nix
+++ b/accounts/gkleen@sif/systemd.nix
@@ -6,7 +6,7 @@ let
6 cfg = config.home-manager.users.${userName}; 6 cfg = config.home-manager.users.${userName};
7 7
8 autossh-socks-script = pkgs.writeScript "autossh" '' 8 autossh-socks-script = pkgs.writeScript "autossh" ''
9 #!${pkgs.zsh}/bin/zsh -xe 9 #!${lib.getExe pkgs.zsh} -xe
10 10
11 host="''${1%:*}" 11 host="''${1%:*}"
12 port="''${1#*:}" 12 port="''${1#*:}"
@@ -15,31 +15,29 @@ let
15 cmd=() 15 cmd=()
16 16
17 if [[ -n "''${SSHPASS_SECRET}" ]]; then 17 if [[ -n "''${SSHPASS_SECRET}" ]]; then
18 cmd+=(${pkgs.sshpassSecret}/bin/sshpass-secret) 18 cmd+=(${lib.getExe' pkgs.sshpassSecret "sshpass-secret"})
19 cmd+=("''${(@s/:/)SSHPASS_SECRET}") 19 cmd+=("''${(@s/:/)SSHPASS_SECRET}")
20 cmd+=(--) 20 cmd+=(--)
21 fi 21 fi
22 22
23 cmd+=(${pkgs.openssh}/bin/ssh -vN -D localhost:''${port} "''${host}") 23 cmd+=(${lib.getExe' pkgs.openssh "ssh"} -vN -D 127.0.0.1:''${port} "''${host}")
24 24
25 ( exec -a "''${cmd[1]}" -- ''${cmd} ) & 25 ( exec -a "''${cmd[1]}" -- ''${cmd} ) &
26 pid=$! 26 pid=$!
27 27
28 newpid="" 28 newpid=""
29 i=200 29 i=200
30 while ! newpid=$(${pkgs.lsof}/bin/lsof -Pi @localhost:"''${port}" -sTCP:LISTEN -t); do 30 while ! newpid=$(${lib.getExe pkgs.lsof} -Pi @localhost:"''${port}" -sTCP:LISTEN -t); do
31 if ! kill -0 "''${pid}"; then 31 if ! kill -0 "''${pid}"; then
32 wait "''${pid}" 32 wait "''${pid}"
33 exit $? 33 exit $?
34 fi 34 fi
35 [[ "''${i}" -gt 0 ]] || exit 1 35 [[ "''${i}" -gt 0 ]] || exit 1
36 i=$((''${i} - 1)) 36 i=$((''${i} - 1))
37 ${pkgs.coreutils}/bin/sleep 0.1 37 ${lib.getExe' pkgs.coreutils "sleep"} 0.1
38 done 38 done
39 39
40 ${config.systemd.package}/bin/systemd-notify --ready 40 ${lib.getExe' config.systemd.package "systemd-notify"} --pid=''${newpid} --ready
41
42 wait "''${pid}" "''${newpid}"
43 ''; 41 '';
44in { 42in {
45 tmpfiles.rules = [ 43 tmpfiles.rules = [
@@ -48,11 +46,11 @@ in {
48 ]; 46 ];
49 47
50 services = { 48 services = {
51 sync-keepass = { 49 "sync-keepass@" = {
52 Service = { 50 Service = {
53 Type = "oneshot"; 51 Type = "oneshot";
54 WorkingDirectory = "~"; 52 WorkingDirectory = "~";
55 ExecStart = toString (pkgs.writers.writePython3 "sync-keepass" { 53 ExecStart = "${pkgs.writers.writePython3 "sync-keepass" {
56 libraries = with pkgs.python3Packages; [ python-dateutil ]; 54 libraries = with pkgs.python3Packages; [ python-dateutil ];
57 } '' 55 } ''
58 import json 56 import json
@@ -61,13 +59,13 @@ in {
61 from datetime import datetime 59 from datetime import datetime
62 from dateutil.tz import tzlocal 60 from dateutil.tz import tzlocal
63 from dateutil.parser import isoparse 61 from dateutil.parser import isoparse
64 from sys import stderr 62 from sys import stderr, argv
65 63
66 64
67 remote_fs = 'surtr' 65 remote_fs = 'surtr' if argv[1] == 'store.kdbx' else 'mathcloud'
68 remote_file = 'store.kdbx' 66 remote_file = argv[1]
69 target_file = expanduser('~/store.kdbx') 67 target_file = expanduser(f'~/{argv[1]}')
70 meta_file = expanduser('~/.store.kdbx.json') 68 meta_file = expanduser(f'~/.{argv[1]}.json')
71 69
72 upload_time = None 70 upload_time = None
73 our_last_upload_time = None 71 our_last_upload_time = None
@@ -117,7 +115,7 @@ in {
117 do_upload() 115 do_upload()
118 elif upload_time is not None and (mod_time is None or upload_time > mod_time) and (our_last_upload_time is None or upload_time > our_last_upload_time): # noqa: E501 116 elif upload_time is not None and (mod_time is None or upload_time > mod_time) and (our_last_upload_time is None or upload_time > our_last_upload_time): # noqa: E501
119 do_download() 117 do_download()
120 ''); 118 ''} \"%I\"";
121 Environment = [ "RCLONE_PASSWORD_COMMAND=\"${pkgs.coreutils}/bin/cat ${config.sops.secrets.gkleen-rclone.path}\"" "PATH=${pkgs.rclone}/bin" ]; 119 Environment = [ "RCLONE_PASSWORD_COMMAND=\"${pkgs.coreutils}/bin/cat ${config.sops.secrets.gkleen-rclone.path}\"" "PATH=${pkgs.rclone}/bin" ];
122 }; 120 };
123 }; 121 };
@@ -146,7 +144,7 @@ in {
146 Service.ExecStart = "${pkgs.bluez}/bin/mpris-proxy"; 144 Service.ExecStart = "${pkgs.bluez}/bin/mpris-proxy";
147 Install.WantedBy = [ "default.target" ]; 145 Install.WantedBy = [ "default.target" ];
148 }; 146 };
149 "autossh-socks@proxy.mathw0h:8119" = { 147 "autossh-socks@proxy.ssh.math.lmu.de:8119" = {
150 Service = { 148 Service = {
151 Type = "notify"; 149 Type = "notify";
152 NotifyAccess = "all"; 150 NotifyAccess = "all";
@@ -154,7 +152,7 @@ in {
154 Restart = "always"; 152 Restart = "always";
155 RestartSec = "23s"; 153 RestartSec = "23s";
156 ExecStart = "${autossh-socks-script} \"%I\""; 154 ExecStart = "${autossh-socks-script} \"%I\"";
157 Environment = [ "SSHPASS_SECRET=gkleen@mathw0g.math.lmu.de" ]; 155 Environment = [ "SSHPASS_SECRET=gkleen@ssh.math.lmu.de" ];
158 }; 156 };
159 Unit = { 157 Unit = {
160 StopWhenUnneeded = true; 158 StopWhenUnneeded = true;
@@ -175,6 +173,38 @@ in {
175 StopWhenUnneeded = true; 173 StopWhenUnneeded = true;
176 }; 174 };
177 }; 175 };
176 "autossh-socks@proxy.mathw0h:8123" = {
177 Service = {
178 Type = "notify";
179 NotifyAccess = "all";
180 WorkingDirectory = "~";
181 Restart = "always";
182 RestartSec = "23s";
183 ExecStart = "${autossh-socks-script} \"%I\"";
184 Environment = [ "SSHPASS_SECRET=gkleen@mathw0h.mathinst.loc" ];
185 };
186 Unit = {
187 StopWhenUnneeded = true;
188 StartLimitInterval = "180s";
189 StartLimitBurst = 7;
190 };
191 };
192 "autossh-socks@proxy.mathw0e:8125" = {
193 Service = {
194 Type = "notify";
195 NotifyAccess = "all";
196 WorkingDirectory = "~";
197 Restart = "always";
198 RestartSec = "23s";
199 ExecStart = "${autossh-socks-script} \"%I\"";
200 Environment = [ "SSHPASS_SECRET=gkleen@mathw0e.mathinst.loc" ];
201 };
202 Unit = {
203 StopWhenUnneeded = true;
204 StartLimitInterval = "180s";
205 StartLimitBurst = 7;
206 };
207 };
178 swayidle = { 208 swayidle = {
179 Service = { 209 Service = {
180 RuntimeDirectory = "swayidle"; 210 RuntimeDirectory = "swayidle";
@@ -212,7 +242,7 @@ in {
212 "-${lib.getExe pkgs.playerctl} -a pause" 242 "-${lib.getExe pkgs.playerctl} -a pause"
213 "-${lib.getExe (pkgs.writeShellApplication { 243 "-${lib.getExe (pkgs.writeShellApplication {
214 name = "generate-css"; 244 name = "generate-css";
215 runtimeInputs = with pkgs; [cfg.programs.wpaperd.package jq coreutils imagemagick findutils]; 245 runtimeInputs = with pkgs; [cfg.services.wpaperd.package jq coreutils imagemagick findutils];
216 text = '' 246 text = ''
217 declare -A monitors 247 declare -A monitors
218 monitors=() 248 monitors=()
@@ -303,21 +333,21 @@ in {
303 ExecStopPost = "${pkgs.coreutils}/bin/rm -rfv \"$CACHE_DIRECTORY\""; 333 ExecStopPost = "${pkgs.coreutils}/bin/rm -rfv \"$CACHE_DIRECTORY\"";
304 }; 334 };
305 }; 335 };
306 wpaperd = { 336 # wpaperd = {
307 Install = { 337 # Install = {
308 WantedBy = ["graphical-session.target"]; 338 # WantedBy = ["graphical-session.target"];
309 }; 339 # };
310 Unit = { 340 # Unit = {
311 After = [ "graphical-session.target" ]; 341 # After = [ "graphical-session.target" ];
312 PartOf = [ "graphical-session.target" ]; 342 # PartOf = [ "graphical-session.target" ];
313 }; 343 # };
314 Service = { 344 # Service = {
315 ExecStart = lib.getExe cfg.programs.wpaperd.package; 345 # ExecStart = lib.getExe cfg.services.wpaperd.package;
316 Type = "simple"; 346 # Type = "simple";
317 Restart = "always"; 347 # Restart = "always";
318 RestartSec = "2s"; 348 # RestartSec = "2s";
319 }; 349 # };
320 }; 350 # };
321 xembed-sni-proxy = { 351 xembed-sni-proxy = {
322 Unit = { 352 Unit = {
323 PartOf = lib.mkForce ["tray.target"]; 353 PartOf = lib.mkForce ["tray.target"];
@@ -344,16 +374,19 @@ in {
344 Unit = { 374 Unit = {
345 PartOf = lib.mkForce ["tray.target"]; 375 PartOf = lib.mkForce ["tray.target"];
346 }; 376 };
377 Install = {
378 WantedBy = lib.mkForce ["tray.target"];
379 };
347 }; 380 };
348 } // listToAttrs (map ({host, port}: nameValuePair "proxy-to-autossh-socks@${toString port}" { 381 } // listToAttrs (map ({host, port}: nameValuePair "proxy-to-autossh-socks@${toString port}" {
349 Unit = { 382 Unit = {
350 Requires = ["autossh-socks@${host}:${toString (port + 1)}.service" "proxy-to-autossh-socks@${toString port}.socket"]; 383 BindsTo = ["autossh-socks@${host}:${toString (port + 1)}.service" "proxy-to-autossh-socks@${toString port}.socket"];
351 After = ["autossh-socks@${host}:${toString (port + 1)}.service" "proxy-to-autossh-socks@${toString port}.socket"]; 384 After = ["autossh-socks@${host}:${toString (port + 1)}.service" "proxy-to-autossh-socks@${toString port}.socket"];
352 }; 385 };
353 Service = { 386 Service = {
354 ExecStart = "${config.systemd.package}/lib/systemd/systemd-socket-proxyd --exit-idle-time=10s localhost:${toString (port + 1)}"; 387 ExecStart = "${config.systemd.package}/lib/systemd/systemd-socket-proxyd --exit-idle-time=60s 127.0.0.1:${toString (port + 1)}";
355 }; 388 };
356 }) [{ host = "proxy.mathw0h"; port = 8118; } { host = "proxy.vidhar"; port = 8120; }]); 389 }) [{ host = "proxy.ssh.math.lmu.de"; port = 8118; } { host = "proxy.vidhar"; port = 8120; } { host = "proxy.mathw0h"; port = 8122; } { host = "proxy.mathw0e"; port = 8124; }]);
357 sockets = listToAttrs (map (port: nameValuePair "proxy-to-autossh-socks@${toString port}" { 390 sockets = listToAttrs (map (port: nameValuePair "proxy-to-autossh-socks@${toString port}" {
358 Socket = { 391 Socket = {
359 ListenStream = "%I"; 392 ListenStream = "%I";
@@ -361,7 +394,7 @@ in {
361 Install = { 394 Install = {
362 WantedBy = ["default.target"]; 395 WantedBy = ["default.target"];
363 }; 396 };
364 }) [8118 8120]) // { 397 }) [8118 8120 8122 8124]) // {
365 "yt-dlp" = { 398 "yt-dlp" = {
366 Socket = { 399 Socket = {
367 SocketMode = "0600"; 400 SocketMode = "0600";
@@ -375,7 +408,7 @@ in {
375 }; 408 };
376 }; 409 };
377 timers = { 410 timers = {
378 sync-keepass = { 411 "sync-keepass@store.kdbx" = {
379 Timer = { 412 Timer = {
380 OnActiveSec = "1m"; 413 OnActiveSec = "1m";
381 OnUnitActiveSec = "1m"; 414 OnUnitActiveSec = "1m";
@@ -385,6 +418,16 @@ in {
385 WantedBy = ["default.target"]; 418 WantedBy = ["default.target"];
386 }; 419 };
387 }; 420 };
421 "sync-keepass@rz.kdbx" = {
422 Timer = {
423 OnActiveSec = "1d";
424 OnUnitActiveSec = "1d";
425 };
426
427 Install = {
428 WantedBy = ["default.target"];
429 };
430 };
388 }; 431 };
389 targets = { 432 targets = {
390 graphical-session = { 433 graphical-session = {
diff --git a/accounts/gkleen@sif/utils/async-yt-dlp.nix b/accounts/gkleen@sif/utils/async-yt-dlp.nix
new file mode 100644
index 00000000..c3b82ec5
--- /dev/null
+++ b/accounts/gkleen@sif/utils/async-yt-dlp.nix
@@ -0,0 +1,57 @@
1{ writers, python3Packages, ... }:
2
3writers.writePython3Bin "async-yt-dlp" {
4 libraries = with python3Packages; [ yt-dlp ];
5 flakeIgnore = ["E501"];
6} ''
7import sys
8import os
9
10import yt_dlp
11import yt_dlp.options
12from collections import namedtuple
13import socket
14from pathlib import Path
15import json
16
17create_parser = yt_dlp.options.create_parser
18
19
20def parse_patched_options(opts):
21 patched_parser = create_parser()
22 patched_parser.defaults.update({
23 'ignoreerrors': False,
24 'retries': 0,
25 'fragment_retries': 0,
26 'extract_flat': False,
27 'concat_playlist': 'never',
28 })
29 yt_dlp.options.create_parser = lambda: patched_parser
30 try:
31 return yt_dlp.parse_options(opts)
32 finally:
33 yt_dlp.options.create_parser = create_parser
34
35
36default_opts = parse_patched_options([]).ydl_opts
37
38
39def cli_to_api(opts):
40 opts = parse_patched_options(opts)
41 urls = opts.urls
42 opts = opts.ydl_opts
43
44 diff = {k: v for k, v in opts.items() if default_opts[k] != v}
45 if 'postprocessors' in diff:
46 diff['postprocessors'] = [pp for pp in diff['postprocessors']
47 if pp not in default_opts['postprocessors']]
48 return namedtuple('Options', ('params', 'urls'))(diff, urls)
49
50
51if __name__ == '__main__':
52 opts = cli_to_api(sys.argv[1:])
53 with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
54 sock.connect(str(Path(os.environ["XDG_RUNTIME_DIR"]) / "yt-dlp.sock").encode('utf-8'))
55 with sock.makefile(mode='w', buffering=1, encoding='utf-8') as fh:
56 json.dump(opts._asdict(), fh)
57''
diff --git a/accounts/gkleen@sif/utils/sieve-edit.nix b/accounts/gkleen@sif/utils/sieve-edit.nix
new file mode 100644
index 00000000..f985a3f6
--- /dev/null
+++ b/accounts/gkleen@sif/utils/sieve-edit.nix
@@ -0,0 +1,24 @@
1pkgs@{ lib, resholve, zsh, sieve-connect, sops, ... }:
2
3resholve.writeScriptBin "sieve-edit" {
4 inputs = with pkgs; [sieve-connect sops];
5 interpreter = lib.getExe zsh;
6 execer = with pkgs; [
7 "cannot:${lib.getExe sieve-connect}"
8 "cannot:${lib.getExe sops}"
9 ];
10} ''
11 host=$1; shift
12 case "$host" in
13 surtr)
14 sieve-connect -s surtr.yggdrasil.li -m EXTERNAL --clientkey <(sops decrypt $HOME/projects/machines/hosts/surtr/email/ca/gkleen@sif.key) --clientcert $HOME/projects/machines/hosts/surtr/email/ca/gkleen@sif.crt --edit --remotesieve sieve
15 ;;
16 ymir)
17 sieve-connect -s ymir.yggdrasil.li -u gkleen --edit --remotesieve sieve
18 ;;
19 *)
20 echo "Unknown host: ‘$host’" >&2
21 return 2
22 ;;
23 esac
24''
diff --git a/accounts/gkleen@sif/zshrc b/accounts/gkleen@sif/zshrc
index e3f675a1..abc200c6 100644
--- a/accounts/gkleen@sif/zshrc
+++ b/accounts/gkleen@sif/zshrc
@@ -2,17 +2,14 @@ dir() {
2 curlArchive=false 2 curlArchive=false
3 templateArchive="" 3 templateArchive=""
4 repoUrl="" 4 repoUrl=""
5 nixShell=""
6 findNix=false
7 dir="" 5 dir=""
8 forceShell=false 6 forceShell=false
9 wormhole=false 7 wormhole=false
10 gitWorktree="" 8 gitWorktree=""
11 # notmuchMsg=""
12 quickserve=false
13 modifyPDF="" 9 modifyPDF=""
10 miniserve=false
14 11
15 while getopts ':t:a:s:Sd:ir:wqg:p:' arg; do 12 while getopts ':t:a:d:ir:wg:p:m' arg; do
16 case $arg in 13 case $arg in
17 "t") ;; 14 "t") ;;
18 "a") 15 "a")
@@ -23,16 +20,13 @@ dir() {
23 templateArchive=${OPTARG:a} 20 templateArchive=${OPTARG:a}
24 fi 21 fi
25 ;; 22 ;;
26 "s") nixShell=${OPTARG:a} ;;
27 "S") findNix=true ;;
28 "d") dir=${OPTARG} ;; 23 "d") dir=${OPTARG} ;;
29 "i") forceShell=true ;; 24 "i") forceShell=true ;;
30 "r") repoUrl=${OPTARG} ;; 25 "r") repoUrl=${OPTARG} ;;
31 "w") wormhole=true ;; 26 "w") wormhole=true ;;
32 "g") gitWorktree=${OPTARG} ;; 27 "g") gitWorktree=${OPTARG} ;;
33 # "n") notmuchMsg=${OPTARG} ;;
34 "q") quickserve=true ;;
35 "p") modifyPDF=${OPTARG:a} ;; 28 "p") modifyPDF=${OPTARG:a} ;;
29 "m") miniserve=true ;;
36 *) printf "Invalid option: %s\n" $arg >&2; exit 2 ;; 30 *) printf "Invalid option: %s\n" $arg >&2; exit 2 ;;
37 esac 31 esac
38 done 32 done
@@ -56,20 +50,34 @@ dir() {
56 gitWorktree="" 50 gitWorktree=""
57 fi 51 fi
58 52
53 miniservePIDFile=""
54 if [[ ${miniserve} = "true" ]]; then
55 miniservePIDFile=$(mktemp --tmpdir --suffix=.pid)
56 fi
57
59 cleanup() 58 cleanup()
60 { 59 {
61 cd ${modifyPDF:h} 60 if [[ -n ${modifyPDF} ]]; then
62 [[ -n ${modifyPDF} ]] && nix shell nixos#imagemagick -c convert -verbose ${dir}/${modifyPDF:t:r}_*.png(on) ${modifyPDF} 61 cd ${modifyPDF:h}
62 typeset -a pages
63 eval 'pages=(${dir}/${modifyPDF:t:r}_*.png(on))'
64 magick -verbose "$pages" ${modifyPDF}
65 modifyPDF=""
66 fi
67 if [[ -n ${miniservePIDFile} ]]; then
68 command kill --verbose -- $(cat ${miniservePIDFile}) && wait $(cat ${miniservePIDFile})
69 miniservePIDFile=""
70 fi
63 } 71 }
64 72
65 ( 73 (
74 set -o localoptions -o localtraps
75 trap 'return 1' INT TERM
66 trap cleanup EXIT 76 trap cleanup EXIT
67 77
68 cd ${dir} 78 cd ${dir}
69 export dir; 79 export dir;
70 80
71 ${findNix} && { nixShell=$(findNix) || return $? }
72
73 [[ -n ${repoUrl} ]] && git clone -- ${repoUrl} . 81 [[ -n ${repoUrl} ]] && git clone -- ${repoUrl} .
74 82
75 [[ -n ${modifyPDF} ]] && templateArchive=${modifyPDF} 83 [[ -n ${modifyPDF} ]] && templateArchive=${modifyPDF}
@@ -82,7 +90,7 @@ dir() {
82 } 90 }
83 trap cleanup EXIT 91 trap cleanup EXIT
84 92
85 if ${curlArchive}; then 93 if [[ $curlArchive = "true" ]]; then
86 archiveFile=$(mktemp -t "archive.XXXXXXXXXX.${templateArchive:t}") 94 archiveFile=$(mktemp -t "archive.XXXXXXXXXX.${templateArchive:t}")
87 95
88 curl -L -o ${archiveFile} ${templateArchive} 96 curl -L -o ${archiveFile} ${templateArchive}
@@ -91,14 +99,14 @@ dir() {
91 fi 99 fi
92 100
93 unpack=true 101 unpack=true
94 while ${unpack}; do 102 while [[ $unpack = "true" ]]; do
95 case $(file --brief --mime-type --dereference ${templateArchive}) in 103 case $(file --brief --mime-type --dereference ${templateArchive}) in
96 application/zip) 104 application/zip)
97 unzip ${templateArchive} 105 unzip ${templateArchive}
98 unpack=false 106 unpack=false
99 ;; 107 ;;
100 application/vnd.debian.binary-package) 108 application/vnd.debian.binary-package)
101 nix shell nixos#binutils --command ar x ${templateArchive} 109 ar x ${templateArchive}
102 mkdir control data 110 mkdir control data
103 tar -C control -xvaf control.* 111 tar -C control -xvaf control.*
104 tar -C data -xvaf data.* 112 tar -C data -xvaf data.*
@@ -106,7 +114,7 @@ dir() {
106 ;; 114 ;;
107 application/x-rpm) 115 application/x-rpm)
108 cpioArchive=$(mktemp -t "archive.XXXXXXXXXX.${templateArchive:t:r}.cpio") 116 cpioArchive=$(mktemp -t "archive.XXXXXXXXXX.${templateArchive:t:r}.cpio")
109 nix shell nixos#busybox --command rpm2cpio ${templateArchive} > ${cpioArchive} 117 rpm2cpio ${templateArchive} > ${cpioArchive}
110 templateArchive=${cpioArchive} 118 templateArchive=${cpioArchive}
111 unpack=true 119 unpack=true
112 ;; 120 ;;
@@ -115,12 +123,12 @@ dir() {
115 unpack=false 123 unpack=false
116 ;; 124 ;;
117 application/pdf) 125 application/pdf)
118 nix shell nixos#ghostscript nixos#imagemagick -c convert -verbose -density 400 ${templateArchive} ${modifyPDF:t:r}_%0d.png 126 magick -verbose -density 400 ${templateArchive} ${modifyPDF:t:r}_%0d.png
119 unpack=false 127 unpack=false
120 ;; 128 ;;
121 application/octet-stream) 129 application/octet-stream)
122 if [[ $(file --brief --dereferenc ${templateArchive}) =~ Squashfs ]]; then 130 if [[ $(file --brief --dereference ${templateArchive}) =~ Squashfs ]]; then
123 nix shell nixos#squashfsTools -c unsquashfs -d . ${templateArchive} 131 unsquashfs -d . ${templateArchive}
124 unpack=false 132 unpack=false
125 fi 133 fi
126 ;; 134 ;;
@@ -134,25 +142,21 @@ dir() {
134 fi 142 fi
135 143
136 144
137 ${wormhole} && wormhole receive --accept-file 145 [[ $wormhole = "true" ]] && wormhole receive --accept-file
138 146
139 147
140 if ${quickserve}; then 148 if [[ ${#@} -gt 0 ]]; then
141 quickserve --root . --upload . --show-hidden --tar gz 149 ${@}
142 fi 150 fi
143 151
152 cd $(pwd) # Needed for mounting to work
144 153
145 if [[ ${#@} -eq 0 ]] || ${forceShell}; then 154 if [[ ${miniserve} = "true" ]]; then
146 if [[ ${#@} -gt 0 ]]; then 155 miniserve --random-route --hidden --enable-tar-gz --enable-zip . &
147 if [[ -z ${nixShell} ]]; then 156 echo $! > "${miniservePIDFile}"
148 ${@} 157 fi
149 else
150 nix-shell ${nixShell} --run "${@}"
151 fi
152 fi
153
154 cd $(pwd) # Needed for mounting to work
155 158
159 if [[ ${#@} -eq 0 ]] && [[ ${miniserve} != "true" ]] || [[ $forceShell = "true" ]]; then
156 isSingleDir() { 160 isSingleDir() {
157 typeset -a contents 161 typeset -a contents
158 contents=(*(N) .*(N)) 162 contents=(*(N) .*(N))
@@ -166,18 +170,9 @@ dir() {
166 } 170 }
167 while d=$(isSingleDir); do cd ${d}; done 171 while d=$(isSingleDir); do cd ${d}; done
168 172
169 173 zsh
170 if [[ -z ${nixShell} ]]; then 174 elif [[ ${miniserve} == "true" ]]; then
171 zsh 175 wait $(cat "${miniservePIDFile}")
172 else
173 nix-shell ${nixShell} --run zsh
174 fi
175 else
176 if [[ -z ${nixShell} ]]; then
177 ${@}
178 else
179 nix-shell ${nixShell} --run "${@}"
180 fi
181 fi 176 fi
182 ) 177 )
183} 178}
@@ -185,27 +180,30 @@ dir() {
185tmpdir() { 180tmpdir() {
186 cleanup() 181 cleanup()
187 { 182 {
188 cd / 183 cd /
189 unmount() { 184 unmount() {
190 printf "Unmounting %s\n" ${1} >&2 185 printf "Unmounting %s\n" ${1} >&2
191 fusermount -u ${1} || umount ${1} || sudo umount ${1} 186 fusermount -u ${1} || umount ${1} || sudo umount ${1}
192 } 187 }
193 188
194 if mountpoint -q -- ${dir}; then 189 if [[ -n ${dir} ]]; then
195 unmount ${dir} || return $? 190 if mountpoint -q -- ${dir}; then
196 else 191 unmount ${dir} || return $?
197 while read -d $'\0' subDir; do 192 else
198 mountpoint -q -- ${subDir} || continue 193 while read -d $'\0' subDir; do
199 unmount ${subDir} || return $? 194 mountpoint -q -- ${subDir} || continue
200 done <<<$(find ${dir} -xdev -type d -print0 | sort -zr) 195 unmount ${subDir} || return $?
201 fi 196 done <<<$(find ${dir} -xdev -type d -print0 | sort -zr)
202 197 fi
203 rm -rfv --one-file-system -- ${dir} 198
199 rm -rfv --one-file-system -- ${dir}
200 dir=""
201 fi
204 } 202 }
205 203
206 local tmpdir="" 204 local tmpdir=""
207 205
208 while getopts ':t:a:s:Sd:ir:wqg:p:' arg; do 206 while getopts ':t:a:d:ir:wg:p:m' arg; do
209 case $arg in 207 case $arg in
210 "t") tmpdir="=${OPTARG}" ;; 208 "t") tmpdir="=${OPTARG}" ;;
211 "?"|":") printf "Invalid option: %s\n" $arg >&2; exit 2 ;; 209 "?"|":") printf "Invalid option: %s\n" $arg >&2; exit 2 ;;
@@ -213,6 +211,8 @@ tmpdir() {
213 done 211 done
214 212
215 ( 213 (
214 set -o localoptions -o localtraps
215 trap 'return 1' INT TERM
216 trap cleanup EXIT 216 trap cleanup EXIT
217 217
218 218
@@ -234,16 +234,6 @@ public-ip() {
234 curl -s -H 'Accept: application/json' $@ ifconfig.co | jq -r '.ip' 234 curl -s -H 'Accept: application/json' $@ ifconfig.co | jq -r '.ip'
235} 235}
236 236
237nix-ghci() {
238 pkgExpr=""
239 if [[ ${#@} -gt 0 ]]; then
240 pkgExpr="${1}"
241 shift
242 fi
243
244 nix-shell -p "with (import <nixpkgs> {}); pkgs.haskellPackages.ghcWithPackages (p: with p; [${pkgExpr}])" --run "ghci ${@}"
245}
246
247swap() { 237swap() {
248 f1=${1} 238 f1=${1}
249 f2=${2} 239 f2=${2}
@@ -271,14 +261,6 @@ l() {
271 ls --long --binary --git --time-style=iso --header $@ 261 ls --long --binary --git --time-style=iso --header $@
272} 262}
273 263
274re() {
275 systemctl --restart $@
276}
277
278ure() {
279 systemctl --user --restart $@
280}
281
282ssh-installer() { 264ssh-installer() {
283 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentityFile=~/.ssh/gkleen@sif.midgard.yggdrasil $@ 265 ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o IdentityFile=~/.ssh/gkleen@sif.midgard.yggdrasil $@
284} 266}
@@ -306,20 +288,7 @@ done < <(find ~/projects ~/uni -regextype posix-extended -maxdepth 2 -type d -re
306 sed -zr 's|(.*/([0-9]{2}[ws])/(.+))|\1 \2 \3|' | \ 288 sed -zr 's|(.*/([0-9]{2}[ws])/(.+))|\1 \2 \3|' | \
307 sort -z -r -k2 | sort -z -s -k3 | uniq -z -f 2) 289 sort -z -r -k2 | sort -z -s -k3 | uniq -z -f 2)
308 290
309alias '..'='cd ..'
310alias rzadm=$'tmpdir -i sh -c \'mkdir adm; sshfs gkleen@mgmt01:/adm adm\'' 291alias rzadm=$'tmpdir -i sh -c \'mkdir adm; sshfs gkleen@mgmt01:/adm adm\''
311alias mathcloud=$'tmpdir -i rclone mount --daemon mathcloud:// .' 292alias mathcloud=$'tmpdir -i rclone mount --daemon mathcloud:// .'
312alias -g L='| less'
313alias -g S='&> /dev/null'
314alias -g G='| grep'
315alias -g B='&> /dev/null &'
316alias -g BB='&> /dev/null &!'
317 293
318export DEFAULT_USER=gkleen 294export DEFAULT_USER=gkleen
319
320bindkey -e
321bindkey ';5C' emacs-forward-word
322bindkey ';5D' emacs-backward-word
323bindkey '^[[1;5C' emacs-forward-word
324bindkey '^[[1;5D' emacs-backward-word
325bindkey '^H' backward-kill-word