summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--accounts/gkleen@sif/autorandr-profiles/bstr.nix21
-rw-r--r--accounts/gkleen@sif/autorandr-profiles/def.nix13
-rw-r--r--accounts/gkleen@sif/backup-patterns26
-rw-r--r--accounts/gkleen@sif/default.nix253
-rw-r--r--accounts/gkleen@sif/dunst-settings.nix67
-rw-r--r--accounts/gkleen@sif/emacs.el155
-rw-r--r--accounts/gkleen@sif/firefox-chrome.css25
-rw-r--r--accounts/gkleen@sif/firefox-content.css12
-rwxr-xr-xaccounts/gkleen@sif/scripts/mute.zsh18
-rw-r--r--accounts/gkleen@sif/ssh-hosts.nix209
-rw-r--r--accounts/gkleen@sif/store.kdbx.lftp6
-rw-r--r--accounts/gkleen@sif/systemd.nix94
-rw-r--r--accounts/gkleen@sif/xmobar/default.nix7
-rw-r--r--accounts/gkleen@sif/xmobar/nixpkgs.nix9
-rw-r--r--accounts/gkleen@sif/xmobar/package.yaml13
-rw-r--r--accounts/gkleen@sif/xmobar/shell.nix28
-rw-r--r--accounts/gkleen@sif/xmobar/stack.nix17
-rw-r--r--accounts/gkleen@sif/xmobar/stack.yaml10
-rw-r--r--accounts/gkleen@sif/xmobar/stack.yaml.lock12
-rw-r--r--accounts/gkleen@sif/xmobar/stackage.nix31
-rw-r--r--accounts/gkleen@sif/xmobar/xmobar-yggdrasil.nix13
-rw-r--r--accounts/gkleen@sif/xmobar/xmobar.hs52
-rw-r--r--accounts/gkleen@sif/xmonad/.gitignore4
-rw-r--r--accounts/gkleen@sif/xmonad/default.nix7
-rw-r--r--accounts/gkleen@sif/xmonad/lib/XMonad/Mpv.hs127
-rw-r--r--accounts/gkleen@sif/xmonad/lib/XMonad/Prompt/MyPass.hs94
-rw-r--r--accounts/gkleen@sif/xmonad/lib/XMonad/Prompt/MyShell.hs105
-rw-r--r--accounts/gkleen@sif/xmonad/lib/XMonad/Prompt/MySsh.hs243
-rw-r--r--accounts/gkleen@sif/xmonad/package.yaml30
-rw-r--r--accounts/gkleen@sif/xmonad/stack.nix17
-rw-r--r--accounts/gkleen@sif/xmonad/stack.yaml10
-rw-r--r--accounts/gkleen@sif/xmonad/xmonad-yggdrasil.nix21
-rw-r--r--accounts/gkleen@sif/xmonad/xmonad.hs902
-rw-r--r--accounts/gkleen@sif/xresources.nix46
-rw-r--r--accounts/gkleen@sif/zshrc410
-rw-r--r--accounts/root@sif.nix18
-rw-r--r--flake.lock18
-rw-r--r--hosts/sif/default.nix330
-rw-r--r--hosts/sif/hw.nix35
-rw-r--r--hosts/sif/mail/default.nix66
-rw-r--r--hosts/sif/mail/secrets.yaml33
-rw-r--r--hosts/sif/wacom.conf15
-rw-r--r--modules/borgbackup/btrfs-snapshots.nix52
-rw-r--r--modules/borgbackup/default.nix206
-rw-r--r--modules/borgbackup/lvm-snapshots.nix133
-rw-r--r--modules/borgbackup/repokeys/borg_munin__borg.yaml33
-rw-r--r--modules/kill-user.nix13
-rw-r--r--modules/tinc-networkmanager.nix36
-rw-r--r--modules/uucp.nix391
-rw-r--r--modules/yggdrasil/default.nix49
-rw-r--r--modules/yggdrasil/hosts/sif/default.nix13
-rw-r--r--modules/yggdrasil/hosts/sif/private-keys.yaml34
-rw-r--r--modules/yggdrasil/hosts/ymir.nix19
-rw-r--r--overlays/nerdfonts.nix5
-rw-r--r--overlays/nvidia-kernel-5.7.nix19
-rw-r--r--overlays/persistent-nix-shell/default.nix19
-rw-r--r--overlays/persistent-nix-shell/persistent-nix-shell20
-rw-r--r--overlays/pidgin.nix15
-rw-r--r--overlays/urxvt/52-osc.pl41
-rw-r--r--overlays/urxvt/default.nix21
-rw-r--r--overlays/v4l2loopback.nix37
-rw-r--r--overlays/worktime/default.nix19
-rwxr-xr-xoverlays/worktime/worktime.py430
-rw-r--r--system-profiles/default-locale.nix7
-rw-r--r--system-profiles/initrd-all-crypto-modules.nix7
-rw-r--r--system-profiles/openssh/default.nix36
-rw-r--r--system-profiles/openssh/host-keys/sif.yaml34
-rw-r--r--system-profiles/openssh/known-hosts/sif.nix16
-rw-r--r--system-profiles/openssh/known-hosts/ymir.nix16
-rw-r--r--system-profiles/sudo.nix39
-rw-r--r--user-profiles/direnv.nix9
-rw-r--r--user-profiles/mpv/default.nix83
-rw-r--r--user-profiles/tmux/default.nix26
-rw-r--r--user-profiles/tmux/tmux.conf25
-rw-r--r--user-profiles/utils.nix23
-rw-r--r--user-profiles/zsh/default.nix31
-rw-r--r--user-profiles/zsh/p10k.zsh1578
-rw-r--r--user-profiles/zsh/zshrc26
-rw-r--r--users/gkleen/authorized-keys/gkleen-sif.pub1
-rw-r--r--users/gkleen/default.nix48
-rw-r--r--users/root.nix52
81 files changed, 7275 insertions, 9 deletions
diff --git a/accounts/gkleen@sif/autorandr-profiles/bstr.nix b/accounts/gkleen@sif/autorandr-profiles/bstr.nix
new file mode 100644
index 00000000..527f8321
--- /dev/null
+++ b/accounts/gkleen@sif/autorandr-profiles/bstr.nix
@@ -0,0 +1,21 @@
1{
2 fingerprint = {
3 "eDP-1-1" = "00ffffffffffff004c83414100000000131d0104b5221378029491ae513eb7240b505400000001010101010101010101010101010101f0d40040f17018803020440058c21000001bf0d40040f17018803020440058c21000001b0000000f00ff093cff093c2c800000000000000000fe0041544e413536575230382d3020011502030f00e3058000e6060501736d0700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ab";
4 "DP-1.3" = "00ffffffffffff000469a3289bdd00000b190104a53e22783a1cb5a3574fa0270d5054bfef00d1c0814081809500b300714f81c0010122cc0050f0703e80181035006d552100001a04740030f2705a80b0588a006d552100001a000000fd001e5018a03c041100f0f838f03c000000fc0041535553205042323837510a2001a8020327714f0102031112130414051f900e0f1d1e23091707830100006a030c0010000078200000565e00a0a0a02950302035006d552100001ee26800a0a0402e60302036006d552100001a011d00bc52d01e20b82855406d552100001e8c0ad090204031200c4055006d55210000180000000000000000000000000000000064";
5 };
6 config = {
7 "DP-1.3" = {
8 enable = true;
9 primary = true;
10 position = "3840x0";
11 rate = "60";
12 mode = "3840x2160";
13 };
14 eDP-1-1 = {
15 enable = true;
16 primary = false;
17 position = "0x0";
18 mode = "3840x2160";
19 };
20 };
21}
diff --git a/accounts/gkleen@sif/autorandr-profiles/def.nix b/accounts/gkleen@sif/autorandr-profiles/def.nix
new file mode 100644
index 00000000..304b4afe
--- /dev/null
+++ b/accounts/gkleen@sif/autorandr-profiles/def.nix
@@ -0,0 +1,13 @@
1{
2 fingerprint = {
3 eDP-1-1 = "00ffffffffffff004c83414100000000131d0104b5221378029491ae513eb7240b505400000001010101010101010101010101010101f0d40040f17018803020440058c21000001bf0d40040f17018803020440058c21000001b0000000f00ff093cff093c2c800000000000000000fe0041544e413536575230382d3020011502030f00e3058000e6060501736d0700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ab";
4 };
5 config = {
6 eDP-1-1 = {
7 enable = true;
8 primary = true;
9 position = "0x0";
10 mode = "3840x2160";
11 };
12 };
13}
diff --git a/accounts/gkleen@sif/backup-patterns b/accounts/gkleen@sif/backup-patterns
new file mode 100644
index 00000000..4436887b
--- /dev/null
+++ b/accounts/gkleen@sif/backup-patterns
@@ -0,0 +1,26 @@
1R .
2
3- pp:.cache
4- pp:.antigen
5+ pf:.cabal/config
6- pp:.cabal
7+ pf:.stack/config.yaml
8- pp:.stack
9- pp:.nox
10- pp:.local/share/Steam
11- sh:**/.stack-work*
12- sh:**/.~lock.*
13- sh:**/.gup
14+ pf:.config/pulse/default.pa
15- pp:.config/pulse
16- pp:.config/Zulip
17- pp:.config/discord
18- pp:.zplug/log
19- pp:.zplug/cache
20- pp:.compose-cache
21- pp:.undo-tree
22- pp:.saves
23- pp:.mozilla
24- pp:Downloads/tmp
25- pp:secret
26- pp:mail \ No newline at end of file
diff --git a/accounts/gkleen@sif/default.nix b/accounts/gkleen@sif/default.nix
new file mode 100644
index 00000000..800cacb5
--- /dev/null
+++ b/accounts/gkleen@sif/default.nix
@@ -0,0 +1,253 @@
1{ flake, userName, pkgs, customUtils, lib, config, ... }@inputs:
2let
3 cfg = config.home-manager.users.${userName};
4 xmonad = import ./xmonad pkgs.haskellPackages;
5 emacsclientDesktopItem = pkgs.makeDesktopItem {
6 name = "emacsclient";
7 genericName = "Text Editor";
8 desktopName = "emacsclient";
9 icon = "emacs";
10 mimeType = "text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;";
11 exec = "${config.home-manager.users.${userName}.programs.emacs.package}/bin/emacsclient -a \"\" %F";
12 };
13 emacsScratch = pkgs.stdenv.mkDerivation rec {
14 pname = "scratch";
15 version = "0077334cc299aa7885f804d88f52cdb1b35caf71";
16
17 src = pkgs.fetchFromGitHub {
18 owner = "ffevotte";
19 repo = "scratch.el";
20 rev = version;
21 sha256 = "sha256-FUkKJ+1COGzgllzzv51yUIjMZI6slOFVExdwWl2ZEBA=";
22 };
23
24 phases = [ "installPhase" ];
25
26 installPhase = ''
27 mkdir -p $out/share/emacs/site-lisp
28 cp $src/scratch.el $out/share/emacs/site-lisp/default.el
29 '';
30 };
31 muteScript = pkgs.stdenv.mkDerivation {
32 name = "mute";
33 src = ./scripts/mute.zsh;
34
35 buildInputs = with pkgs; [ makeWrapper ];
36
37 phases = [ "installPhase" ];
38
39 installPhase = ''
40 mkdir -p $out/bin
41 install -m 0755 $src $out/bin/mute
42 wrapProgram $out/bin/mute \
43 --prefix PATH : ${pkgs.zsh}/bin \
44 --prefix PATH : ${pkgs.findutils}/bin \
45 --prefix PATH : ${pkgs.util-linux}/bin \
46 --prefix PATH : ${pkgs.coreutils}/bin \
47 --prefix PATH : ${pkgs.pulseaudio}/bin
48 '';
49 };
50in {
51 imports = with flake.nixosModules.userProfiles.${userName}; [
52 mpv
53 ];
54
55 home-manager.users.${userName} = {
56 programs = {
57 ssh = {
58 matchBlocks = import ./ssh-hosts.nix; # customUtils.recImport { dir = ./ssh-hosts; };
59 extraConfig = ''
60 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"
61 ProxyJump gate2
62
63 Host *
64 '';
65 };
66
67 emacs = {
68 enable = true;
69 extraPackages = epkgs: with epkgs; [
70 evil evil-dvorak undo-tree magit haskell-mode
71 nix-mode yaml-mode json-mode shakespeare-mode
72 smart-mode-line highlight-parentheses highlight-symbol
73 notmuch ag sass-mode lua-mode fira-code-mode use-package
74 use-package-ensure-system-package git-gutter emacsScratch
75 ];
76 };
77 firefox = {
78 enable = true;
79 profiles.default = {
80 settings = {
81 "layout.css.devPixelsPerPx" = "1.75";
82 "browser.tabs.drawInTitlebar" = false;
83 "toolkit.legacyUserProfileCustomizations.stylesheets" = true;
84 "dom.security.https_only_mode" = true;
85 };
86 };
87 };
88
89 urxvt = {
90 enable = true;
91 package = pkgs.rxvt_unicode-with-plugins;
92 fonts = [ "xft:FiraCode Nerd Font Mono:style=Regular:pixelsize=21" ];
93 scroll = {
94 lines = 0;
95 bar.enable = false;
96 };
97 extraConfig = {
98 urgentOnBell = false;
99 print-pipe = "cat >/dev/null";
100 perl-ext-common = "52-osc,url-select";
101 "url-select.launcher" = "firefox";
102 "url-select.underline" = true;
103 };
104 keybindings = {
105 "M-u" = "perl:url-select:select_next";
106 };
107 };
108
109 zathura = {
110 enable = true;
111 package = pkgs.zathura.override { useMupdf = false; };
112 };
113
114 mpv.config = {
115 demuxer-max-bytes = 1073741824;
116 demuxer-max-back-bytes = 268435456;
117 };
118
119 autorandr = {
120 enable = true;
121 hooks.postswitch = {
122 # "restart-compton" = "${pkgs.systemd}/bin/systemctl --user try-restart picom";
123 "restart-trays" = ''
124 ${pkgs.coreutils}/bin/sleep 5
125 ${pkgs.systemd}/bin/systemctl --user try-restart trayer xmobar
126 '';
127 };
128 profiles = customUtils.recImport { dir = ./autorandr-profiles; };
129 };
130
131 zsh.initExtra = "source ${./zshrc}";
132 zsh.dirHashes = {
133 u2w = "$HOME/projects/uni2work";
134 docs = "$HOME/documents";
135 dl = "$HOME/Downloads";
136 flk = "$HOME/config/nixos-flakes";
137 fsk-timi = "$HOME/projects/21s/fsk-timi";
138 };
139
140 obs-studio = {
141 enable = true;
142 plugins = with pkgs; [obs-v4l2sink];
143 };
144 };
145
146 services = {
147 dunst = {
148 settings = import ./dunst-settings.nix inputs;
149 iconTheme = cfg.gtk.iconTheme;
150 enable = true;
151 };
152 emacs.enable = true;
153 gpg-agent = {
154 enable = true;
155 enableSshSupport = true;
156 extraConfig = ''
157 pinentry-program ${pkgs.pinentry-gtk2}/bin/pinentry
158 grab
159 '';
160 };
161 pasystray.enable = true;
162 udiskie = {
163 enable = true;
164 automount = false;
165 };
166 unclutter = {
167 enable = true;
168 timeout = 5;
169 };
170 network-manager-applet.enable = true;
171 blueman-applet.enable = true;
172
173 sxhkd = {
174 enable = true;
175 keybindings = {
176 "button8" = "${muteScript}/bin/mute unmute";
177 "@button8" = "${muteScript}/bin/mute mute";
178 "button9" = "${pkgs.pulseaudio}/bin/pacmd set-sink-mute @DEFAULT_SINK@ 1";
179 "@button9" = "${pkgs.pulseaudio}/bin/pacmd set-sink-mute @DEFAULT_SINK@ 0";
180 };
181 };
182 };
183
184 gtk = {
185 enable = true;
186 font.name = "DejaVu Sans 6";
187 theme = {
188 package = pkgs.equilux-theme;
189 name = "Equilux-compact";
190 };
191 iconTheme = {
192 package = pkgs.paper-icon-theme;
193 name = "Paper";
194 };
195 };
196
197 xsession = {
198 enable = true;
199
200 windowManager.command = "${xmonad}/bin/xmonad";
201
202 initExtra = let
203 lockScript = pkgs.writeScript "lock" ''
204 #!${pkgs.stdenv.shell}
205 ${pkgs.playerctl}/bin/playerctl -a pause
206 exec ${pkgs.xsecurelock}/bin/xsecurelock
207 '';
208 in ''
209 ${pkgs.coreutils}/bin/env XSECURELOCK_WANT_FIRST_KEYPRESS=1 XSECURELOCK_DIM_ALPHA=1 ${pkgs.xss-lock}/bin/xss-lock -l -n ${pkgs.xsecurelock}/libexec/xsecurelock/dimmer -- ${lockScript} &
210 ${pkgs.xorg.xinput}/bin/xinput disable 'Synaptics TM3512-010'
211 ${pkgs.xorg.xset}/bin/xset s 590 10
212 '';
213 };
214
215 xresources.properties = import ./xresources.nix;
216
217 home = {
218 packages = with pkgs; [
219 fira-code powerline-fonts nerdfonts pavucontrol keepassxc
220 youtube-dl sxiv xclip mumble pulseaudio-ctl libnotify synergy
221 xorg.xbacklight screen-message pidgin-with-plugins
222 google-play-music-desktop-player qt5ct playerctl evince
223 thunderbird zulip zoom-us steam steam-run wireshark skype
224 virt-manager rclone cached-nix-shell xournal discord xmonad
225 worktime fira-code-symbols emacsclientDesktopItem libreoffice
226 xournalpp
227 ];
228
229 file = {
230 ".emacs".source = ./emacs.el;
231 ".backup-munin".source = ./backup-patterns;
232 ".mozilla/firefox/default/chrome/userChrome.css".source = ./firefox-chrome.css;
233 ".mozilla/firefox/default/chrome/userContent.css".source = ./firefox-content.css;
234 };
235
236 sessionVariables = {
237 GDK_SCALE = 96.0 / 282.0;
238 QT_AUTO_SCREEN_SCALE_FACTOR = 1;
239 QT_QPA_PLATFORMTHEME = "qt5ct";
240 };
241
242 extraProfileCommands = ''
243 export XDG_DATA_DIRS="${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/${pkgs.gsettings-desktop-schemas.name}:${pkgs.gtk3}/share/gsettings-schemas/${pkgs.gtk3.name}''${XDG_DATA_DIRS:+:''${XDG_DATA_DIRS}}"
244 '';
245
246 stateVersion = "20.03";
247 };
248
249 fonts.fontconfig.enable = true;
250
251 systemd.user = import ./systemd.nix inputs;
252 };
253}
diff --git a/accounts/gkleen@sif/dunst-settings.nix b/accounts/gkleen@sif/dunst-settings.nix
new file mode 100644
index 00000000..8abdfc5a
--- /dev/null
+++ b/accounts/gkleen@sif/dunst-settings.nix
@@ -0,0 +1,67 @@
1{ pkgs, ... }:
2{
3 global = {
4 font = "Monospace 6";
5 markup = "full";
6 format = "<i>%s</i> %p\\n%b";
7 alignment = "left";
8 geometry = "1216x10-32+64";
9 shrink = true;
10 monitor = 0;
11 follow = "none";
12 padding = 6;
13 horizontal_padding = 6;
14 separator_height = 1;
15 separator_color = "frame";
16 idle_threshold = 0;
17
18 transparency = 10;
19
20 frame_width = 1;
21 frame_color = "#999999";
22
23 word_wrap = true;
24 show_age_threshold = 15;
25 show_indicators = false;
26 icon_position = "right";
27 sort = false;
28 sticky_history = false;
29
30 dmenu = "${pkgs.dmenu}/bin/dmenu";
31 };
32 shortcuts = {
33 close = "ctrl+space";
34 close_all = "ctrl+shift+space";
35 history = "ctrl+comma";
36 context = "ctrl+period";
37 };
38 urgency_low = {
39 background = "#000000";
40 foreground = "#999999";
41 timeout = 5;
42 };
43 urgency_normal = {
44 background = "#000000";
45 foreground = "#ffffff";
46 timeout = 15;
47 };
48 urgency_critical = {
49 background = "#900000";
50 foreground = "#ffffff";
51 timeout = 0;
52 };
53 pulseaudio-ctl = {
54 summary = "Volume *";
55 body = "Current is *";
56 set_stack_tag = "volume";
57 history_ignore = true;
58 };
59 mail = {
60 appname = "notmuch";
61 timeout = 0;
62 };
63 zulip = {
64 appname = "Zulip";
65 timeout = 0;
66 };
67}
diff --git a/accounts/gkleen@sif/emacs.el b/accounts/gkleen@sif/emacs.el
new file mode 100644
index 00000000..c8356bf2
--- /dev/null
+++ b/accounts/gkleen@sif/emacs.el
@@ -0,0 +1,155 @@
1(menu-bar-mode -1)
2(scroll-bar-mode -1)
3(tool-bar-mode -1)
4
5(setq inhibit-startup-message t)
6(defalias 'yes-or-no-p 'y-or-n-p)
7
8(set-face-attribute 'default nil :font "FiraCode Nerd Font Mono" :height 49)
9
10(require 'package)
11(setq package-archives nil)
12(package-initialize)
13(require 'use-package)
14(use-package use-package-ensure-system-package :ensure t)
15
16(require 'evil)
17(evil-mode 1)
18
19(global-subword-mode)
20(global-undo-tree-mode)
21(global-fira-code-mode)
22
23(evil-set-undo-system 'undo-tree)
24
25(global-set-key (kbd "RET") 'newline-and-indent)
26(global-set-key (kbd "M-g") 'magit-status)
27(global-set-key (kbd "M-?") 'vc-git-grep)
28
29(require 'git-gutter)
30(global-git-gutter-mode t)
31(custom-set-variables '(git-gutter:update-interval 2))
32(custom-set-variables '(git-gutter:hide-gutter t))
33
34;; (require 'scratch)
35(global-set-key (kbd "C-x B") 'scratch-create)
36(setq initial-major-mode 'scratch-mode)
37(setq initial-scratch-message "")
38
39(global-set-key (kbd "C-x K") 'kill-current-buffer)
40
41(setq backup-directory-alist `(("." . "~/.saves")))
42(setq undo-tree-history-directory-alist `(("." . "~/.undo")))
43(setq delete-old-versions t
44 kept-new-versions 6
45 kept-old-versions 2
46 version-control t)
47
48(setq undo-tree-visualizer-timestamps t
49 undo-tree-visualizer-diff t
50 ;; 10X bump of the undo limits to avoid issues with premature
51 ;; Emacs GC which truncages the undo history very aggresively
52 undo-limit 800000
53 undo-strong-limit 12000000
54 undo-outer-limit 120000000)
55
56(add-hook 'haskell-mode-hook 'haskell-indentation-mode)
57(add-hook 'haskell-mode-hook 'subword-mode)
58(add-hook 'haskell-mode-hook 'highlight-symbol-mode)
59(add-hook 'haskell-mode-hook 'highlight-paretheses-mode)
60
61(add-hook 'js-mode-hook 'highlight-symbol-mode)
62(add-hook 'js-mode-hook 'highlight-parentheses-mode)
63(defun my-js-mode-hook ()
64 "Custom `js-mode' behaviours."
65 (setq js-indent-level 2)
66 )
67(add-hook 'js-mode-hook 'my-js-mode-hook)
68
69(setq undo-tree-auto-save-history t)
70
71(defvar expand-file-name-custom-tilde-alist '())
72(defun my/add-to-tilde-alist (hash)
73 (let* ((tilde:dir (split-string hash "="))
74 (tilde (car tilde:dir))
75 (dir (cadr tilde:dir)))
76 (push (cons tilde dir) expand-file-name-custom-tilde-alist)))
77(mapc #'my/add-to-tilde-alist
78 (split-string (with-output-to-string
79 (call-process "zsh" nil standard-output nil "-ic" "hash -d"))
80 "\n" t))
81
82(defadvice expand-file-name (before expand-file-name-custom-tilde
83 (name &optional default-directory)
84 activate compile)
85 "User-defined expansions for ~NAME in file names."
86 (save-match-data
87 (when (string-match "\\`\\(\\(.*/\\)?~\\([^:/]+\\)\\)/" name)
88 (let ((replacement (assoc (match-string 3 name) expand-file-name-custom-tilde-alist)))
89 (when replacement
90 (setq name (replace-match (cdr replacement) t t name 1)))))))
91
92(setq notmuch-address-internal-completion '(received nil))
93(setq notmuch-always-prompt-for-sender t)
94(setq notmuch-command "notmuch-ssh")
95(setq notmuch-crypto-process-mime t)
96(setq notmuch-draft-tags '("+draft" "-inbox"))
97(setq notmuch-fcc-dirs nil)
98(setq notmuch-hello-sections '(notmuch-hello-insert-header notmuch-hello-insert-saved-searches))
99(setq notmuch-hello-thousands-separator " ")
100(setq notmuch-identities '("gkleen@yggdrasil.li" "g@141.li" "kleen@cip.ifi.lmu.de" "Gregor.Kleen@stud.ifi.lmu.de" "G.Kleen@campus.lmu.de" "G.Kleen@lmu.de" "gregor.kleen@ifi.lmu.de" "uni2work@ifi.lmu.de" "gregor@kleen.li"))
101(setq notmuch-message-headers '("Subject" "To" "Cc" "Date"))
102(setq notmuch-message-replied-tags '("+replied" "-unread" "-inbox"))
103(setq notmuch-saved-searches
104 (quote
105 ((:name "inbox" :query "tag:inbox" :key "i")
106 (:name "unread" :query "tag:unread AND tag:inbox" :key "u")
107 (:name "drafts" :query "tag:draft" :key "d")
108 (:name "all mail" :query "date:month.." :key "a" :count-query "*")
109 (:name "sent" :query "is:sent" :key "s" :count-query "is:sent")
110 )))
111(setq notmuch-search-oldest-first nil)
112(setq notmuch-show-all-tags-list t)
113(setq notmuch-show-logo nil)
114
115(setq send-mail-function 'sendmail-send-it)
116(setq mail-envelope-from 'header)
117(setq mail-specify-envelope-from 't)
118(setq mail-default-headers nil)
119(setq message-default-headers "")
120(setq message-default-mail-headers "")
121(setq message-sendmail-envelope-from 'header)
122
123(setq highlight-symbol-idle-delay 0)
124
125(setq indent-tabs-mode nil)
126
127(setq ido-enable-flex-matching t)
128(setq ido-everywhere t)
129(ido-mode 1)
130
131(setq mail-host-address "sif.midgard.yggdrasil")
132(setq user-full-name "Gregor Kleen")
133
134(defun tell-emacsclients-for-buffer-to-die ()
135 "Sends error exit command to every client for the current buffer."
136 (interactive)
137 (dolist (proc server-buffer-clients)
138 (server-send-string proc "-error die")))
139
140(defun kill-buffer-with-special-emacsclient-handling ()
141 "Wrapper around kill-buffer that ensures tell-emacsclients-for-buffer-to-die is on the hooks"
142 (interactive)
143 (add-hook 'kill-buffer-hook 'tell-emacsclients-for-buffer-to-die nil t)
144 (kill-buffer))
145
146;; (global-set-key (kbd "C-x k") 'kill-buffer)
147
148(defun install-emacsclient-wrapped-kill-buffer ()
149 "Installs wrapped kill-buffer with special emacsclient handling.
150Best not to install it unconditionally because the server is not
151necessarily running."
152 (interactive)
153 (global-set-key (kbd "C-x k") 'kill-buffer-with-special-emacsclient-handling))
154
155(add-hook 'server-switch-hook 'install-emacsclient-wrapped-kill-buffer)
diff --git a/accounts/gkleen@sif/firefox-chrome.css b/accounts/gkleen@sif/firefox-chrome.css
new file mode 100644
index 00000000..2d359771
--- /dev/null
+++ b/accounts/gkleen@sif/firefox-chrome.css
@@ -0,0 +1,25 @@
1@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
2
3* {
4 font-size:12px;
5}
6
7#sidebar {
8 min-width:20em !important;
9 max-width:20em !important;
10 border-right:1px solid rgba(30,30,30) !important;
11}
12
13#sidebar-header:nth-last-child(2) > *:last-child {
14 visibility: collapse;
15}
16
17#sidebar-splitter {
18 visibility: collapse;
19}
20
21#toolbar-menubar[inactive="true"] + #TabsToolbar {
22 visibility: collapse !important;
23}
24
25#sidebar-box[sidebarcommand="tabcenter-reborn_ariasuni-sidebar-action"] #sidebar-header { visibility: collapse !important; }
diff --git a/accounts/gkleen@sif/firefox-content.css b/accounts/gkleen@sif/firefox-content.css
new file mode 100644
index 00000000..3ac53a9d
--- /dev/null
+++ b/accounts/gkleen@sif/firefox-content.css
@@ -0,0 +1,12 @@
1@-moz-document url("about:blank") {
2 html {
3 background-color: rgb(40,40,40);
4 }
5}
6
7@-moz-document url(about:newtab) {
8 body, #newtab-customize-overlay {
9 background-color: rgb(40,40,40) !important;
10 color: rgb(166,166,166) !important;
11 }
12}
diff --git a/accounts/gkleen@sif/scripts/mute.zsh b/accounts/gkleen@sif/scripts/mute.zsh
new file mode 100755
index 00000000..1b30ad67
--- /dev/null
+++ b/accounts/gkleen@sif/scripts/mute.zsh
@@ -0,0 +1,18 @@
1#!/usr/bin/env zsh
2
3lockFile=~/.mute.flock
4
5case $1 in
6 mute)
7 (
8 flock -n 9 || exit 1
9 sleep 0.2
10 pacmd set-source-mute @DEFAULT_SOURCE@ 1
11 ) 9<>${lockFile} &
12 ;;
13 unmute)
14 set -o pipefail
15 while fuser ${lockFile} 2>/dev/null | cut -d ':' -f 2- | xargs -r -- kill; do sleep 0.001; done
16 pacmd set-source-mute @DEFAULT_SOURCE@ 0
17 ;;
18esac
diff --git a/accounts/gkleen@sif/ssh-hosts.nix b/accounts/gkleen@sif/ssh-hosts.nix
new file mode 100644
index 00000000..0db4e342
--- /dev/null
+++ b/accounts/gkleen@sif/ssh-hosts.nix
@@ -0,0 +1,209 @@
1{
2 "git.ymir" =
3 { hostname = "ymir.yggdrasil.li";
4 user = "gitolite";
5 identityFile = "~/.ssh/gkleen@sif.midgard.yggdrasil";
6 };
7 "git.yggdrasil.li" =
8 { user = "gitolite";
9 identityFile = "~/.ssh/gkleen@sif.midgard.yggdrasil";
10 };
11 "git.rheperire.org" =
12 { user = "gitolite";
13 identityFile = "~/.ssh/gkleen@sif.midgard.yggdrasil";
14 };
15 "borg.munin" =
16 { hostname = "u120515.your-storagebox.de";
17 user = "u120515";
18 identityFile = "~/.ssh/borg.munin";
19 port = 23;
20 };
21 "munin" =
22 { hostname = "u120515.your-storagebox.de";
23 user = "u120515";
24 identityFile = "~/.ssh/munin";
25 };
26 "ymir" =
27 { hostname = "ymir.yggdrasil.li";
28 identityFile = "~/.ssh/gkleen@sif.midgard.yggdrasil";
29 };
30 "odin" =
31 { hostname = "odin.asgard.yggdrasil";
32 identityFile = "~/.ssh/gkleen@sif.midgard.yggdrasil";
33 };
34 "init.odin" =
35 { hostname = "odin.asgard.yggdrasil";
36 user = "root";
37 identityFile = "~/.ssh/rsa.gkleen@hel.midgard.yggdrasil";
38 extraOptions = {
39 StrictHostKeyChecking = "off";
40 };
41 };
42 "heimdallr" =
43 { hostname = "heimdallr.asgard.yggdrasil";
44 user = "root";
45 identityFile = "~/.ssh/gkleen@sif.midgard.yggdrasil";
46 };
47 "gitlab2.rz.ifi.lmu.de" =
48 { user = "git";
49 identityFile = "~/.ssh/gkleen@gitlab2.rz.ifi.lmu.de";
50 };
51 "gitlab2.cip.ifi.lmu.de" =
52 { user = "git";
53 identityFile = "~/.ssh/gkleen@sif.midgard.yggdrasil";
54 };
55 "hel".hostname = "hel.midgard.yggdrasil";
56 "blackbeard" =
57 { hostname = "blackbeard.tcs.ifi.lmu.de";
58 user = "pi";
59 identityFile = "~/.ssh/blackbeard";
60 };
61 "github.com" =
62 { user = "git";
63 identityFile = "~/.ssh/gkleen@github.com";
64 };
65 "ullr.playat.ch" =
66 { hostname = "ullr.playat.ch";
67 user = "minecraft";
68 identityFile = "~/.ssh/minecraft@ullr.playat.ch";
69 };
70 "ullr" =
71 { hostname = "185.170.112.70";
72 identityFile = "~/.ssh/gkleen@sif.midgard.yggdrasil";
73 };
74 "testworx" =
75 { hostname = "testworx.tcs.ifi.lmu.de";
76 user = "root";
77 port = 30363;
78 identityFile = "~/.ssh/testworx";
79 };
80 "remote.cip.ifi.lmu.de" =
81 { user = "kleen";
82 identityFile = "~/.ssh/gkleen@sif.midgard.yggdrasil";
83 };
84 "uniworx3" =
85 { hostname = "uniworx3.ifi.lmu.de";
86 user = "root";
87 identityFile = "~/.ssh/uni2work";
88 };
89 "uniworx4" =
90 { hostname = "uniworx4.ifi.lmu.de";
91 user = "root";
92 identityFile = "~/.ssh/uni2work";
93 };
94 "uni2workgw" =
95 { hostname = "uni2workgw.ifi.lmu.de";
96 user = "root";
97 identityFile = "~/.ssh/uni2work";
98 };
99 "uniworxdb2" =
100 { hostname = "uniworxdb2";
101 proxyJump = "uniworx4";
102 user = "root";
103 identityFile = "~/.ssh/uni2work";
104 };
105 "uniworx5" =
106 { hostname = "uniworx5.ifi.lmu.de";
107 user = "root";
108 identityFile = "~/.ssh/uni2work";
109 };
110 "gate2" =
111 { hostname = "gate2.tcs.ifi.lmu.de";
112 user = "gkleen";
113 identityFile = "~/.ssh/tcs";
114 serverAliveInterval = 0;
115 };
116 "proxy.gate2" =
117 { hostname = "gate2.tcs.ifi.lmu.de";
118 user = "gkleen";
119 identityFile = "~/.ssh/proxy.gkleen@tcs.ifi.lmu.de";
120 dynamicForwards = [ { port = 8118; } ];
121 serverAliveInterval = 0;
122 extraOptions = {
123 ExitOnForwardFailure = "yes";
124 };
125 };
126 "jump.gate2" =
127 { hostname = "gate2.tcs.ifi.lmu.de";
128 user = "gkleen";
129 identityFile = "~/.ssh/proxy.gkleen@tcs.ifi.lmu.de";
130 serverAliveInterval = 0;
131 extraOptions = {
132 ExitOnForwardFailure = "yes";
133 };
134 };
135 "gate" =
136 { hostname = "gate.tcs.ifi.lmu.de";
137 user = "gkleen";
138 identityFile = "~/.ssh/tcs";
139 };
140 "proxy.gate" =
141 { hostname = "gate.tcs.ifi.lmu.de";
142 user = "gkleen";
143 identityFile = "~/.ssh/proxy.gkleen@tcs.ifi.lmu.de";
144 dynamicForwards = [ { port = 8118; } ];
145 extraOptions = {
146 ExitOnForwardFailure = "yes";
147 };
148 };
149 "jump.gate" =
150 { hostname = "gate.tcs.ifi.lmu.de";
151 user = "gkleen";
152 identityFile = "~/.ssh/proxy.gkleen@tcs.ifi.lmu.de";
153 extraOptions = {
154 ExitOnForwardFailure = "yes";
155 };
156 };
157 "oregon" =
158 { hostname = "oregon.tcs.ifi.lmu.de";
159 user = "root";
160 identityFile = "~/.ssh/tcs";
161 };
162 "proxy.oregon" =
163 { hostname = "oregon.tcs.ifi.lmu.de";
164 user = "root";
165 identityFile = "~/.ssh/tcs";
166 dynamicForwards = [ { port = 8113; } ];
167 extraOptions = {
168 ExitOnForwardFailure = "yes";
169 };
170 };
171 "witbank" =
172 { hostname = "witbank.tcs.ifi.lmu.de";
173 user = "uni2work";
174 identityFile = "~/.ssh/letz";
175 };
176 "git.odin" =
177 { hostname = "odin.asgard.yggdrasil";
178 user = "gitolite";
179 };
180 "notmuch.odin" =
181 { hostname = "odin.asgard.yggdrasil";
182 identityFile = "~/.ssh/notmuch.odin.asgard.yggdrasil";
183 };
184 "status.odin" =
185 { hostname = "odin.asgard.yggdrasil";
186 identityFile = "~/.ssh/status.odin.asgard.yggdrasil";
187 extraOptions.ControlPath = "~/.ssh/status-%r@%n:%p";
188 };
189 "moden" =
190 { hostname = "oristano.tcs.ifi.lmu.de";
191 user = "gkleen";
192 port = 30363;
193 identityFile = "~/.ssh/gkleen@oristano.tcs.ifi.lmu.de";
194 };
195 "ubuntu1804" =
196 { hostname = "192.168.122.30";
197 identityFile = "~/.ssh/gkleen@sif.midgard.yggdrasil";
198 forwardAgent = true;
199 };
200 "gitlab.haskell.org" =
201 { hostname = "gitlab.haskell.org";
202 identityFile = "~/.ssh/gkleen@gitlab.haskell.org";
203 };
204 "gitlab.lrz.de" =
205 { hostname = "gitlab.lrz.de";
206 user = "git";
207 identityFile = "~/.ssh/gkleen@gitlab.lrz.de";
208 };
209}
diff --git a/accounts/gkleen@sif/store.kdbx.lftp b/accounts/gkleen@sif/store.kdbx.lftp
new file mode 100644
index 00000000..4447aded
--- /dev/null
+++ b/accounts/gkleen@sif/store.kdbx.lftp
@@ -0,0 +1,6 @@
1open ftp://gkleen.keepass@yggdrasil.li/
2
3lcd /home/gkleen
4
5mirror -v --only-newer -f store.kdbx
6mirror -v --reverse --only-newer -f store.kdbx \ No newline at end of file
diff --git a/accounts/gkleen@sif/systemd.nix b/accounts/gkleen@sif/systemd.nix
new file mode 100644
index 00000000..a5b71417
--- /dev/null
+++ b/accounts/gkleen@sif/systemd.nix
@@ -0,0 +1,94 @@
1{ pkgs, config, userName, ... }:
2let
3 xmobar = import ./xmobar pkgs.haskellPackages;
4 cfg = config.home-manager.users.${userName};
5in {
6 services = {
7 sync-keepass = {
8 Service = {
9 Type = "oneshot";
10 WorkingDirectory = "~";
11 ExecStart = "${pkgs.lftp}/bin/lftp -f ${./store.kdbx.lftp}";
12 };
13 };
14 urxvtd = {
15 Service = {
16 Type = "simple";
17 WorkingDirectory = "~";
18 ExecStart = "${cfg.programs.urxvt.package}/bin/urxvtd";
19 Restart = "always";
20 };
21 Unit = {
22 After = ["graphical-session.target"];
23 };
24 Install = {
25 WantedBy = ["graphical-session.target"];
26 };
27 };
28 emacs = {
29 Unit = {
30 After = ["graphical-session-pre.target"];
31 };
32 };
33 trayer = {
34 Service = {
35 Type = "simple";
36 WorkingDirectory = "~";
37 ExecStart = "${pkgs.trayer}/bin/trayer --edge top --align right --SetDockType true --SetPartialStrut true --expand true --width 8 --tint 0x000000 --alpha 0 --transparent true --height 32 --monitor primary";
38 Restart = "always";
39 };
40 Install = {
41 WantedBy = ["graphical-session.target"];
42 };
43 };
44 xmobar = {
45 Service = {
46 Type = "simple";
47 WorkingDirectory = "~";
48 ExecStart = "${xmobar}/bin/xmobar";
49 Restart = "always";
50 Environment = "PATH=${pkgs.worktime}/bin:${pkgs.openssh}/bin";
51
52 };
53 Install = {
54 WantedBy = ["graphical-session.target"];
55 };
56 };
57 dunst = {
58 Service = {
59 Restart = "always";
60 };
61 Install = {
62 WantedBy = ["graphical-session.target"];
63 };
64 };
65 xiccd = {
66 Service = {
67 Type = "simple";
68 WorkingDirectory = "~";
69 ExecStart = "${pkgs.xiccd}/bin/xiccd";
70 Restart = "always";
71 };
72 };
73 };
74 timers = {
75 sync-keepass = {
76 Timer = {
77 OnActiveSec = "1m";
78 OnUnitActiveSec = "1m";
79 };
80
81 Install = {
82 WantedBy = ["default.target"];
83 };
84 };
85 };
86 targets = {
87 graphical-session = {
88 Unit = {
89 BindsTo = ["default.target"];
90 After = ["basic.target"];
91 };
92 };
93 };
94}
diff --git a/accounts/gkleen@sif/xmobar/default.nix b/accounts/gkleen@sif/xmobar/default.nix
new file mode 100644
index 00000000..fcac5e60
--- /dev/null
+++ b/accounts/gkleen@sif/xmobar/default.nix
@@ -0,0 +1,7 @@
1argumentPackages@{ ... }:
2
3let
4 # defaultPackages = (import ./stackage.nix {});
5 # haskellPackages = defaultPackages // argumentPackages;
6 haskellPackages = argumentPackages;
7in haskellPackages.callPackage ./xmobar-yggdrasil.nix {}
diff --git a/accounts/gkleen@sif/xmobar/nixpkgs.nix b/accounts/gkleen@sif/xmobar/nixpkgs.nix
new file mode 100644
index 00000000..783ede00
--- /dev/null
+++ b/accounts/gkleen@sif/xmobar/nixpkgs.nix
@@ -0,0 +1,9 @@
1{ nixpkgs ? import <nixpkgs>
2}:
3
4import ((nixpkgs {}).fetchFromGitHub {
5 owner = "NixOS";
6 repo = "nixpkgs";
7 rev = "10e61bf5be57736035ec7a804cb0bf3d083bf2cf";
8 sha256 = "0fplfm2zx4vk7gs8bdcxnvzkdmpx2w0llqwf8475z9dz9cl132rm";
9})
diff --git a/accounts/gkleen@sif/xmobar/package.yaml b/accounts/gkleen@sif/xmobar/package.yaml
new file mode 100644
index 00000000..b638b6ac
--- /dev/null
+++ b/accounts/gkleen@sif/xmobar/package.yaml
@@ -0,0 +1,13 @@
1name: xmobar-yggdrasil
2
3executables:
4 xmobar:
5 dependencies:
6 - base
7 - xmobar
8
9 main: xmobar.hs
10 source-dirs:
11 - .
12
13 ghc-options: -threaded
diff --git a/accounts/gkleen@sif/xmobar/shell.nix b/accounts/gkleen@sif/xmobar/shell.nix
new file mode 100644
index 00000000..16beb322
--- /dev/null
+++ b/accounts/gkleen@sif/xmobar/shell.nix
@@ -0,0 +1,28 @@
1{ nixpkgs ? import ./nixpkgs.nix {} }:
2
3let
4 inherit (nixpkgs {}) pkgs;
5 haskellPackages = import ./stackage.nix { inherit nixpkgs; };
6
7 drv = haskellPackages.callPackage ./xmobar-yggdrasil.nix {};
8 override = oldAttrs: {
9 nativeBuildInputs = oldAttrs.nativeBuildInputs ++ (with pkgs; []) ++ (with haskellPackages; [ stack cabal-install cabal2nix ]);
10 shellHook = ''
11 export PROMPT_INFO="${oldAttrs.name}"
12
13 if [ -n "$ZSH_VERSION" ]; then
14 autoload -U +X compinit && compinit
15 autoload -U +X bashcompinit && bashcompinit
16 fi
17 eval "$(stack --bash-completion-script stack)"
18
19 ${oldAttrs.shellHook}
20 '';
21 };
22
23 dummy = pkgs.stdenv.mkDerivation {
24 name = "interactive-xmobar-environment";
25 shellHook = "";
26 };
27in pkgs.lib.overrideDerivation dummy override
28 #pkgs.lib.overrideDerivation drv.env override
diff --git a/accounts/gkleen@sif/xmobar/stack.nix b/accounts/gkleen@sif/xmobar/stack.nix
new file mode 100644
index 00000000..17a49e04
--- /dev/null
+++ b/accounts/gkleen@sif/xmobar/stack.nix
@@ -0,0 +1,17 @@
1{ ghc, nixpkgs ? import ./nixpkgs.nix {} }:
2
3let
4 haskellPackages = import ./stackage.nix { inherit nixpkgs; };
5 inherit (nixpkgs {}) pkgs;
6in pkgs.haskell.lib.buildStackProject {
7 inherit ghc;
8 inherit (haskellPackages) stack;
9 name = "stackenv";
10 buildInputs = (with pkgs;
11 [ xorg.libX11 xorg.libXrandr xorg.libXinerama xorg.libXScrnSaver xorg.libXext xorg.libXft
12 cairo
13 glib
14 ]) ++ (with haskellPackages;
15 [
16 ]);
17}
diff --git a/accounts/gkleen@sif/xmobar/stack.yaml b/accounts/gkleen@sif/xmobar/stack.yaml
new file mode 100644
index 00000000..b8ed1147
--- /dev/null
+++ b/accounts/gkleen@sif/xmobar/stack.yaml
@@ -0,0 +1,10 @@
1nix:
2 enable: true
3 shell-file: stack.nix
4
5resolver: lts-13.21
6
7packages:
8 - .
9
10extra-deps: []
diff --git a/accounts/gkleen@sif/xmobar/stack.yaml.lock b/accounts/gkleen@sif/xmobar/stack.yaml.lock
new file mode 100644
index 00000000..fcc2f5f3
--- /dev/null
+++ b/accounts/gkleen@sif/xmobar/stack.yaml.lock
@@ -0,0 +1,12 @@
1# This file was autogenerated by Stack.
2# You should not edit this file by hand.
3# For more information, please see the documentation at:
4# https://docs.haskellstack.org/en/stable/lock_files
5
6packages: []
7snapshots:
8- completed:
9 size: 498180
10 url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/13/21.yaml
11 sha256: eff2de19a6d4691ccbf6edc1fba858f1918683047dce0f09adede874bbd2a8f3
12 original: lts-13.21
diff --git a/accounts/gkleen@sif/xmobar/stackage.nix b/accounts/gkleen@sif/xmobar/stackage.nix
new file mode 100644
index 00000000..c162ca2c
--- /dev/null
+++ b/accounts/gkleen@sif/xmobar/stackage.nix
@@ -0,0 +1,31 @@
1{ nixpkgs ? import ./nixpkgs.nix {}
2, snapshot ? "lts-13.21"
3}:
4
5let
6 stackage = import (fetchTarball {
7 url = "https://stackage.serokell.io/zb36jsy3r5h4ydz0pnp00g9vk94dvv03-stackage/default.nix.tar.gz";
8 sha256 = "0h6f80gds0ds77y51hhiadh2h2k8njqq8n0gayp729ana9m9agma";
9 });
10
11 overlays =
12 [ stackage."${snapshot}"
13 (self: super: {
14 haskell = super.haskell // {
15 packages = super.haskell.packages // {
16 "${snapshot}" = super.haskell.packages."${snapshot}".override {
17 overrides = hself: hsuper: {
18 zip-archive = self.haskell.lib.overrideCabal hsuper.zip-archive (old: {
19 testToolDepends = old.testToolDepends ++ (with self; [ unzip which ]);
20 });
21 alex = self.haskell.lib.dontCheck hsuper.alex;
22 };
23 };
24 };
25 };
26 }
27 )
28 ];
29
30 inherit (nixpkgs { inherit overlays; }) pkgs;
31in pkgs.haskell.packages."${snapshot}"
diff --git a/accounts/gkleen@sif/xmobar/xmobar-yggdrasil.nix b/accounts/gkleen@sif/xmobar/xmobar-yggdrasil.nix
new file mode 100644
index 00000000..852fb8e5
--- /dev/null
+++ b/accounts/gkleen@sif/xmobar/xmobar-yggdrasil.nix
@@ -0,0 +1,13 @@
1{ mkDerivation, base, hpack, lib, xmobar }:
2mkDerivation {
3 pname = "xmobar-yggdrasil";
4 version = "0.0.0";
5 src = ./.;
6 isLibrary = false;
7 isExecutable = true;
8 libraryToolDepends = [ hpack ];
9 executableHaskellDepends = [ base xmobar ];
10 preConfigure = "hpack";
11 license = "unknown";
12 hydraPlatforms = lib.platforms.none;
13}
diff --git a/accounts/gkleen@sif/xmobar/xmobar.hs b/accounts/gkleen@sif/xmobar/xmobar.hs
new file mode 100644
index 00000000..74ce7347
--- /dev/null
+++ b/accounts/gkleen@sif/xmobar/xmobar.hs
@@ -0,0 +1,52 @@
1import Xmobar
2
3import Data.List (intercalate)
4
5
6main :: IO ()
7main = xmobar config
8 where
9 config = defaultConfig
10 { font = "xft:FiraCode Nerd Font Mono:style=Regular:pixelsize=21"
11 , position = OnScreen 0 $ TopP 0 307
12 , bgColor = "black"
13 , fgColor = "#808080"
14 , overrideRedirect = False
15 , template =
16 let left = intercalate " | "
17 [ "%XMonadWorkspaces%"
18 , "%XMonadLayout%"
19 , "%XMonadTitle%"
20 ]
21 right = intercalate " | "
22 [ {- "%status%"
23 , -} "%battery%"
24 , "%kbd%"
25 , "%worktime%"
26 , "%worktime-today%"
27 , "%time%"
28 , "%date%"
29 ]
30 in left <> "}{" <> right
31 , commands =
32 [ Run $ NamedXPropertyLog "_XMONAD_WORKSPACES" "XMonadWorkspaces"
33 , Run $ NamedXPropertyLog "_XMONAD_LAYOUT" "XMonadLayout"
34 , Run $ NamedXPropertyLog "_XMONAD_TITLE" "XMonadTitle"
35 , Run $ Date "%H:%M" "time" 50
36 , Run $ Date "%a %b %_d" "date" 50
37 , Run $ Com "worktime" [] "worktime" 1500
38 , Run $ Com "worktime" ["today"] "worktime-today" 1500
39 , Run $ Com "ssh" ["status.odin"] "status" 600
40 , Run $ Kbd [("us(dvp)", "dvp")]
41 , Run $ Battery
42 [ "--template", "<watts> <left> (<timeleft>) AC <acstatus>"
43 , "--suffix", "On"
44 , "--Low", "10"
45 , "--High", "80"
46 , "--low", "darkred"
47 , "--normal", "darkorange"
48 , "--high", "darkgreen"
49 , "-p", "3"
50 ] 50
51 ]
52 }
diff --git a/accounts/gkleen@sif/xmonad/.gitignore b/accounts/gkleen@sif/xmonad/.gitignore
new file mode 100644
index 00000000..c11891cd
--- /dev/null
+++ b/accounts/gkleen@sif/xmonad/.gitignore
@@ -0,0 +1,4 @@
1**/#*#
2**/.stack-work/
3/stack.yaml.lock
4/*.cabal
diff --git a/accounts/gkleen@sif/xmonad/default.nix b/accounts/gkleen@sif/xmonad/default.nix
new file mode 100644
index 00000000..8790c12f
--- /dev/null
+++ b/accounts/gkleen@sif/xmonad/default.nix
@@ -0,0 +1,7 @@
1argumentPackages@{ ... }:
2
3let
4 # defaultPackages = (import ./stackage.nix {});
5 # haskellPackages = defaultPackages // argumentPackages;
6 haskellPackages = argumentPackages;
7in haskellPackages.callPackage ./xmonad-yggdrasil.nix {}
diff --git a/accounts/gkleen@sif/xmonad/lib/XMonad/Mpv.hs b/accounts/gkleen@sif/xmonad/lib/XMonad/Mpv.hs
new file mode 100644
index 00000000..e6accdcc
--- /dev/null
+++ b/accounts/gkleen@sif/xmonad/lib/XMonad/Mpv.hs
@@ -0,0 +1,127 @@
1{-# LANGUAGE DeriveGeneric, OverloadedLists, OverloadedStrings, ViewPatterns, ExistentialQuantification, MultiWayIf #-}
2
3module XMonad.Mpv
4 ( MpvCommand(..), MpvResponse(..), MpvException(..)
5 , mpv
6 , mpvDir
7 , mpvAll, mpvOne
8 , mpvResponse
9 ) where
10
11import Data.Aeson
12
13import Data.Monoid
14
15import Network.Socket hiding (recv)
16import Network.Socket.ByteString
17
18import qualified Data.ByteString as BS
19import qualified Data.ByteString.Char8 as CBS
20import qualified Data.ByteString.Lazy as LBS
21
22import GHC.Generics (Generic)
23import Data.Typeable (Typeable)
24import Data.String (IsString(..))
25
26import Control.Exception
27
28import System.IO.Temp (getCanonicalTemporaryDirectory)
29
30import Control.Monad
31import Control.Exception (bracket)
32import Control.Monad.IO.Class (MonadIO(..))
33
34import System.FilePath
35import System.Directory (getDirectoryContents)
36
37import Data.List
38import Data.Either
39import Data.Maybe
40
41import Debug.Trace
42
43
44data MpvCommand
45 = forall a. ToJSON a => MpvSetProperty String a
46 | MpvGetProperty String
47data MpvResponse
48 = MpvError String
49 | MpvSuccess (Maybe Value)
50 deriving (Read, Show, Generic, Eq)
51data MpvException = MpvException String
52 | MpvNoValue
53 | MpvNoParse String
54 deriving (Generic, Typeable, Read, Show)
55instance Exception MpvException
56
57
58instance ToJSON MpvCommand where
59 toJSON (MpvSetProperty name val) = Array ["set_property", fromString name, toJSON val]
60 toJSON (MpvGetProperty name) = Array ["get_property", fromString name]
61
62instance FromJSON MpvResponse where
63 parseJSON = withObject "response object" $ \obj -> do
64 mval <- obj .:? "data"
65 err <- obj .: "error"
66
67 let ret
68 | err == "success" = MpvSuccess mval
69 | otherwise = MpvError err
70
71 return ret
72
73mpvSocket :: FilePath -> (Socket -> IO a) -> IO a
74mpvSocket sockPath = withSocketsDo . bracket mkSock close
75 where
76 mkSock = do
77 sock <- socket AF_UNIX Stream defaultProtocol
78 connect sock $ SockAddrUnix (traceId sockPath)
79 return sock
80
81mpvResponse :: FromJSON v => MpvResponse -> IO v
82mpvResponse (MpvError str) = throwIO $ MpvException str
83mpvResponse (MpvSuccess Nothing) = throwIO MpvNoValue
84mpvResponse (MpvSuccess (Just v)) = case fromJSON v of
85 Success v' -> return v'
86 Error str -> throwIO $ MpvNoParse str
87
88mpv :: FilePath -> MpvCommand -> IO MpvResponse
89mpv sockPath cmd = mpvSocket sockPath $ \sock -> do
90 let message = (`BS.append` "\n") . LBS.toStrict . encode $ Object [("command", toJSON cmd)]
91 traceIO $ show message
92 sendAll sock message
93 let recvAll = do
94 prefix <- recv sock 4096
95 if
96 | (prefix', rest) <- CBS.break (== '\n') prefix
97 , not (BS.null rest) -> return prefix'
98 | BS.null prefix -> return prefix
99 | otherwise -> BS.append prefix <$> recvAll
100 response <- recvAll
101 traceIO $ show response
102 either (ioError . userError) return . traceShowId $ eitherDecodeStrict' response
103
104mpvDir :: Exception e => FilePath -> (FilePath -> [(FilePath, Either e MpvResponse)] -> Maybe MpvCommand) -> IO [(FilePath, Either e MpvResponse)]
105mpvDir dir step = do
106 socks <- filter (".sock" `isSuffixOf`) <$> getDirectoryContents dir
107 go [] socks
108 where
109 go acc [] = return acc
110 go acc (sock:socks)
111 | Just cmd <- step sock acc = do
112 res <- try $ mpv (dir </> sock) cmd
113 go ((sock, res) : acc) socks
114 | otherwise =
115 go acc socks
116
117mpvAll :: FilePath -> MpvCommand -> IO [MpvResponse]
118mpvAll dir cmd = do
119 results <- map snd <$> (mpvDir dir (\_ _ -> Just cmd) :: IO [(FilePath, Either SomeException MpvResponse)])
120 mapM (either throwIO return) results
121
122mpvOne :: FilePath -> MpvCommand -> IO (Maybe MpvResponse)
123mpvOne dir cmd = listToMaybe . snd . partitionEithers . map snd <$> (mpvDir dir step :: IO [(FilePath, Either SomeException MpvResponse)])
124 where
125 step _ results
126 | any (isRight . snd) results = Nothing
127 | otherwise = Just cmd
diff --git a/accounts/gkleen@sif/xmonad/lib/XMonad/Prompt/MyPass.hs b/accounts/gkleen@sif/xmonad/lib/XMonad/Prompt/MyPass.hs
new file mode 100644
index 00000000..1caefae5
--- /dev/null
+++ b/accounts/gkleen@sif/xmonad/lib/XMonad/Prompt/MyPass.hs
@@ -0,0 +1,94 @@
1module XMonad.Prompt.MyPass
2 (
3 -- * Usages
4 -- $usages
5 mkPassPrompt
6 ) where
7
8import Control.Monad (liftM)
9import XMonad.Core
10import XMonad.Prompt ( XPrompt
11 , showXPrompt
12 , commandToComplete
13 , nextCompletion
14 , getNextCompletion
15 , XPConfig
16 , mkXPrompt
17 , searchPredicate)
18import System.Directory (getHomeDirectory)
19import System.FilePath (takeExtension, dropExtension, combine)
20import System.Posix.Env (getEnv)
21import XMonad.Util.Run (runProcessWithInput)
22
23-- $usages
24-- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@:
25--
26-- > import XMonad.Prompt.Pass
27--
28-- Then add a keybinding for 'passPrompt', 'passGeneratePrompt' or 'passRemovePrompt':
29--
30-- > , ((modMask x , xK_p) , passPrompt xpconfig)
31-- > , ((modMask x .|. controlMask, xK_p) , passGeneratePrompt xpconfig)
32-- > , ((modMask x .|. controlMask .|. shiftMask, xK_p), passRemovePrompt xpconfig)
33--
34-- For detailed instructions on:
35--
36-- - editing your key bindings, see "XMonad.Doc.Extending#Editing_key_bindings".
37--
38-- - how to setup the password storage, see <http://git.zx2c4.com/password-store/about/>
39--
40
41type Predicate = String -> String -> Bool
42
43getPassCompl :: [String] -> Predicate -> String -> IO [String]
44getPassCompl compls p s
45 | length s <= minL
46 , all ((> minL) . length) compls = return []
47 | otherwise = do return $ filter (p s) compls
48 where
49 minL = 3
50
51type PromptLabel = String
52
53data Pass = Pass PromptLabel
54
55instance XPrompt Pass where
56 showXPrompt (Pass prompt) = prompt ++ ": "
57 commandToComplete _ c = c
58 nextCompletion _ = getNextCompletion
59
60-- | Default password store folder in $HOME/.password-store
61--
62passwordStoreFolderDefault :: String -> String
63passwordStoreFolderDefault home = combine home ".password-store"
64
65-- | Compute the password store's location.
66-- Use the PASSWORD_STORE_DIR environment variable to set the password store.
67-- If empty, return the password store located in user's home.
68--
69passwordStoreFolder :: IO String
70passwordStoreFolder =
71 getEnv "PASSWORD_STORE_DIR" >>= computePasswordStoreDir
72 where computePasswordStoreDir Nothing = liftM passwordStoreFolderDefault getHomeDirectory
73 computePasswordStoreDir (Just storeDir) = return storeDir
74
75-- | A pass prompt factory
76--
77mkPassPrompt :: PromptLabel -> (String -> X ()) -> XPConfig -> X ()
78mkPassPrompt promptLabel passwordFunction xpconfig = do
79 passwords <- io (passwordStoreFolder >>= getPasswords)
80 mkXPrompt (Pass promptLabel) xpconfig (getPassCompl passwords $ searchPredicate xpconfig) passwordFunction
81
82-- | Retrieve the list of passwords from the password storage 'passwordStoreDir
83getPasswords :: FilePath -> IO [String]
84getPasswords passwordStoreDir = do
85 files <- runProcessWithInput "find" [
86 passwordStoreDir,
87 "-type", "f",
88 "-name", "*.gpg",
89 "-printf", "%P\n"] []
90 return $ map removeGpgExtension $ lines files
91
92removeGpgExtension :: String -> String
93removeGpgExtension file | takeExtension file == ".gpg" = dropExtension file
94 | otherwise = file
diff --git a/accounts/gkleen@sif/xmonad/lib/XMonad/Prompt/MyShell.hs b/accounts/gkleen@sif/xmonad/lib/XMonad/Prompt/MyShell.hs
new file mode 100644
index 00000000..c268f87d
--- /dev/null
+++ b/accounts/gkleen@sif/xmonad/lib/XMonad/Prompt/MyShell.hs
@@ -0,0 +1,105 @@
1module XMonad.Prompt.MyShell
2 ( Shell (..)
3 , shellPrompt
4 , prompt
5 , safePrompt
6 , unsafePrompt
7 , getCommands
8 , getShellCompl
9 , split
10 ) where
11
12import Codec.Binary.UTF8.String (encodeString)
13import Control.Exception as E
14import Control.Monad (forM)
15import Data.List (isPrefixOf)
16import System.Directory (doesDirectoryExist, getDirectoryContents)
17import System.Environment (getEnv)
18import System.Posix.Files (getFileStatus, isDirectory)
19
20import XMonad hiding (config)
21import XMonad.Prompt
22import XMonad.Util.Run
23
24econst :: Monad m => a -> IOException -> m a
25econst = const . return
26
27data Shell = Shell String
28
29instance XPrompt Shell where
30 showXPrompt (Shell q) = q
31 completionToCommand _ = escape
32
33shellPrompt :: String -> XPConfig -> X ()
34shellPrompt q c = do
35 cmds <- io getCommands
36 mkXPrompt (Shell q) c (getShellCompl cmds) spawn
37
38{- $spawns
39 See safe and unsafeSpawn in "XMonad.Util.Run".
40 prompt is an alias for safePrompt;
41 safePrompt and unsafePrompt work on the same principles, but will use
42 XPrompt to interactively query the user for input; the appearance is
43 set by passing an XPConfig as the second argument. The first argument
44 is the program to be run with the interactive input.
45 You would use these like this:
46
47 > , ((modm, xK_b), safePrompt "firefox" greenXPConfig)
48 > , ((modm .|. shiftMask, xK_c), prompt ("xterm" ++ " -e") greenXPConfig)
49
50 Note that you want to use safePrompt for Firefox input, as Firefox
51 wants URLs, and unsafePrompt for the XTerm example because this allows
52 you to easily start a terminal executing an arbitrary command, like
53 'top'. -}
54
55prompt, unsafePrompt, safePrompt :: String -> FilePath -> XPConfig -> X ()
56prompt = unsafePrompt
57safePrompt q c config = mkXPrompt (Shell q) config (getShellCompl [c]) run
58 where run = safeSpawn c . return
59unsafePrompt q c config = mkXPrompt (Shell q) config (getShellCompl [c]) run
60 where run a = unsafeSpawn $ c ++ " " ++ a
61
62getShellCompl :: [String] -> String -> IO [String]
63getShellCompl cmds s | s == "" || last s == ' ' = return []
64 | otherwise = do
65 f <- fmap lines $ runProcessWithInput "bash" [] ("compgen -A file -- "
66 ++ s ++ "\n")
67 files <- case f of
68 [x] -> do fs <- getFileStatus (encodeString x)
69 if isDirectory fs then return [x ++ "/"]
70 else return [x]
71 _ -> return f
72 return . uniqSort $ files ++ commandCompletionFunction cmds s
73
74commandCompletionFunction :: [String] -> String -> [String]
75commandCompletionFunction cmds str | '/' `elem` str = []
76 | otherwise = filter (isPrefixOf str) cmds
77
78getCommands :: IO [String]
79getCommands = do
80 p <- getEnv "PATH" `E.catch` econst []
81 let ds = filter (/= "") $ split ':' p
82 es <- forM ds $ \d -> do
83 exists <- doesDirectoryExist d
84 if exists
85 then getDirectoryContents d
86 else return []
87 return . uniqSort . filter ((/= '.') . head) . concat $ es
88
89split :: Eq a => a -> [a] -> [[a]]
90split _ [] = []
91split e l =
92 f : split e (rest ls)
93 where
94 (f,ls) = span (/=e) l
95 rest s | s == [] = []
96 | otherwise = tail s
97
98escape :: String -> String
99escape [] = ""
100escape (x:xs)
101 | isSpecialChar x = '\\' : x : escape xs
102 | otherwise = x : escape xs
103
104isSpecialChar :: Char -> Bool
105isSpecialChar = flip elem " &\\@\"'#?$*()[]{};"
diff --git a/accounts/gkleen@sif/xmonad/lib/XMonad/Prompt/MySsh.hs b/accounts/gkleen@sif/xmonad/lib/XMonad/Prompt/MySsh.hs
new file mode 100644
index 00000000..c85d0f92
--- /dev/null
+++ b/accounts/gkleen@sif/xmonad/lib/XMonad/Prompt/MySsh.hs
@@ -0,0 +1,243 @@
1module XMonad.Prompt.MySsh
2 ( -- * Usage
3 -- $usage
4 sshPrompt,
5 Ssh,
6 Override (..),
7 mkOverride,
8 Conn (..),
9 moshCmd,
10 moshCmd',
11 sshCmd,
12 inTmux,
13 withEnv
14 ) where
15
16import XMonad
17import XMonad.Util.Run
18import XMonad.Prompt
19
20import System.Directory
21import System.Environment
22import qualified Control.Exception as E
23
24import Control.Monad
25import Data.Maybe
26
27import Text.Parsec.String
28import Text.Parsec
29import Data.Char (isSpace)
30
31econst :: Monad m => a -> E.IOException -> m a
32econst = const . return
33
34-- $usage
35-- 1. In your @~\/.xmonad\/xmonad.hs@:
36--
37-- > import XMonad.Prompt
38-- > import XMonad.Prompt.Ssh
39--
40-- 2. In your keybindings add something like:
41--
42-- > , ((modm .|. controlMask, xK_s), sshPrompt defaultXPConfig)
43--
44-- Keep in mind, that if you want to use the completion you have to
45-- disable the "HashKnownHosts" option in your ssh_config
46--
47-- For detailed instruction on editing the key binding see
48-- "XMonad.Doc.Extending#Editing_key_bindings".
49
50data Override = Override
51 { oUser :: Maybe String
52 , oHost :: String
53 , oPort :: Maybe Int
54 , oCommand :: Conn -> String
55 }
56
57mkOverride = Override { oUser = Nothing, oHost = "", oPort = Nothing, oCommand = sshCmd }
58sshCmd c = concat
59 [ "ssh -t "
60 , if isJust $ cUser c then (fromJust $ cUser c) ++ "@" else ""
61 , cHost c
62 , if isJust $ cPort c then " -p " ++ (show $ fromJust $ cPort c) else ""
63 , " -- "
64 , cCommand c
65 ]
66moshCmd c = concat
67 [ "mosh "
68 , if isJust $ cUser c then (fromJust $ cUser c) ++ "@" else ""
69 , cHost c
70 , if isJust $ cPort c then " --ssh=\"ssh -p " ++ (show $ fromJust $ cPort c) ++ "\"" else ""
71 , " -- "
72 , cCommand c
73 ]
74moshCmd' p c = concat
75 [ "mosh "
76 , "--server=" ++ p ++ " "
77 , if isJust $ cUser c then (fromJust $ cUser c) ++ "@" else ""
78 , cHost c
79 , if isJust $ cPort c then " --ssh=\"ssh -p " ++ (show $ fromJust $ cPort c) ++ "\"" else ""
80 , " -- "
81 , cCommand c
82 ]
83inTmux c
84 | null $ cCommand c = c { cCommand = "tmux new-session" }
85 | otherwise = c { cCommand = "tmux new-session \"" ++ (cCommand c) ++ "\"" }
86withEnv :: [(String, String)] -> Conn -> Conn
87withEnv envs c = c { cCommand = "env" ++ (concat $ map (\(n, v) -> ' ' : (n ++ "=" ++ v)) envs) ++ " " ++ (cCommand c) }
88
89data Conn = Conn
90 { cUser :: Maybe String
91 , cHost :: String
92 , cPort :: Maybe Int
93 , cCommand :: String
94 } deriving (Eq, Show, Read)
95
96data Ssh = Ssh
97
98instance XPrompt Ssh where
99 showXPrompt Ssh = "SSH to: "
100 commandToComplete _ c = c
101 nextCompletion _ = getNextCompletion
102
103toConn :: String -> Maybe Conn
104toConn = toConn' . parse connParser "(unknown)"
105toConn' :: Either ParseError Conn -> Maybe Conn
106toConn' (Left _) = Nothing
107toConn' (Right a) = Just a
108
109connParser :: Parser Conn
110connParser = do
111 spaces
112 user' <- optionMaybe $ try $ do
113 str <- many1 $ satisfy (\c -> (not $ isSpace c) && (c /= '@'))
114 char '@'
115 return str
116 host' <- many1 $ satisfy (not . isSpace)
117 port' <- optionMaybe $ try $ do
118 space
119 string "-p"
120 spaces
121 int <- many1 digit
122 (space >> return ()) <|> eof
123 return $ (read int :: Int)
124 spaces
125 command' <- many anyChar
126 eof
127 return $ Conn
128 { cHost = host'
129 , cUser = user'
130 , cPort = port'
131 , cCommand = command'
132 }
133
134sshPrompt :: [Override] -> XPConfig -> X ()
135sshPrompt o c = do
136 sc <- io sshComplList
137 mkXPrompt Ssh c (mkComplFunFromList sc) $ ssh o
138
139ssh :: [Override] -> String -> X ()
140ssh overrides str = do
141 let cmd = applyOverrides overrides str
142 liftIO $ putStr "SSH Command: "
143 liftIO $ putStrLn cmd
144 runInTerm "" cmd
145
146applyOverrides :: [Override] -> String -> String
147applyOverrides [] str = "ssh " ++ str
148applyOverrides (o:os) str = case (applyOverride o str) of
149 Just str -> str
150 Nothing -> applyOverrides os str
151
152applyOverride :: Override -> String -> Maybe String
153applyOverride o str = let
154 conn = toConn str
155 in
156 if isNothing conn then Nothing else
157 case (fromJust conn) `matches` o of
158 True -> Just $ (oCommand o) (fromJust conn)
159 False -> Nothing
160
161matches :: Conn -> Override -> Bool
162a `matches` b = and
163 [ justBool (cUser a) (oUser b) (==)
164 , (cHost a) == (oHost b)
165 , justBool (cPort a) (oPort b) (==)
166 ]
167
168justBool :: Eq a => Maybe a -> Maybe a -> (a -> a -> Bool) -> Bool
169justBool Nothing _ _ = True
170justBool _ Nothing _ = True
171justBool (Just a) (Just b) match = a `match` b
172
173sshComplList :: IO [String]
174sshComplList = uniqSort `fmap` liftM2 (++) sshComplListLocal sshComplListGlobal
175
176sshComplListLocal :: IO [String]
177sshComplListLocal = do
178 h <- getEnv "HOME"
179 s1 <- sshComplListFile $ h ++ "/.ssh/known_hosts"
180 s2 <- sshComplListConf $ h ++ "/.ssh/config"
181 return $ s1 ++ s2
182
183sshComplListGlobal :: IO [String]
184sshComplListGlobal = do
185 env <- getEnv "SSH_KNOWN_HOSTS" `E.catch` econst "/nonexistent"
186 fs <- mapM fileExists [ env
187 , "/usr/local/etc/ssh/ssh_known_hosts"
188 , "/usr/local/etc/ssh_known_hosts"
189 , "/etc/ssh/ssh_known_hosts"
190 , "/etc/ssh_known_hosts"
191 ]
192 case catMaybes fs of
193 [] -> return []
194 (f:_) -> sshComplListFile' f
195
196sshComplListFile :: String -> IO [String]
197sshComplListFile kh = do
198 f <- doesFileExist kh
199 if f then sshComplListFile' kh
200 else return []
201
202sshComplListFile' :: String -> IO [String]
203sshComplListFile' kh = do
204 l <- readFile kh
205 return $ map (getWithPort . takeWhile (/= ',') . concat . take 1 . words)
206 $ filter nonComment
207 $ lines l
208
209sshComplListConf :: String -> IO [String]
210sshComplListConf kh = do
211 f <- doesFileExist kh
212 if f then sshComplListConf' kh
213 else return []
214
215sshComplListConf' :: String -> IO [String]
216sshComplListConf' kh = do
217 l <- readFile kh
218 return $ map (!!1)
219 $ filter isHost
220 $ map words
221 $ lines l
222 where
223 isHost ws = take 1 ws == ["Host"] && length ws > 1
224
225fileExists :: String -> IO (Maybe String)
226fileExists kh = do
227 f <- doesFileExist kh
228 if f then return $ Just kh
229 else return Nothing
230
231nonComment :: String -> Bool
232nonComment [] = False
233nonComment ('#':_) = False
234nonComment ('|':_) = False -- hashed, undecodeable
235nonComment _ = True
236
237getWithPort :: String -> String
238getWithPort ('[':str) = host ++ " -p " ++ port
239 where (host,p) = break (==']') str
240 port = case p of
241 ']':':':x -> x
242 _ -> "22"
243getWithPort str = str
diff --git a/accounts/gkleen@sif/xmonad/package.yaml b/accounts/gkleen@sif/xmonad/package.yaml
new file mode 100644
index 00000000..48de1a53
--- /dev/null
+++ b/accounts/gkleen@sif/xmonad/package.yaml
@@ -0,0 +1,30 @@
1name: xmonad-yggdrasil
2
3executables:
4 xmonad:
5 dependencies:
6 - base
7 - xmonad
8 - xmonad-contrib
9 - aeson
10 - bytestring
11 - text
12 - temporary
13 - filepath
14 - directory
15 - network
16 - unix
17 - utf8-string
18 - parsec
19 - process
20 - mtl
21 - X11
22 - transformers
23 - containers
24 - hostname
25 - libnotify
26
27 main: xmonad.hs
28 source-dirs:
29 - .
30 - lib
diff --git a/accounts/gkleen@sif/xmonad/stack.nix b/accounts/gkleen@sif/xmonad/stack.nix
new file mode 100644
index 00000000..17a49e04
--- /dev/null
+++ b/accounts/gkleen@sif/xmonad/stack.nix
@@ -0,0 +1,17 @@
1{ ghc, nixpkgs ? import ./nixpkgs.nix {} }:
2
3let
4 haskellPackages = import ./stackage.nix { inherit nixpkgs; };
5 inherit (nixpkgs {}) pkgs;
6in pkgs.haskell.lib.buildStackProject {
7 inherit ghc;
8 inherit (haskellPackages) stack;
9 name = "stackenv";
10 buildInputs = (with pkgs;
11 [ xorg.libX11 xorg.libXrandr xorg.libXinerama xorg.libXScrnSaver xorg.libXext xorg.libXft
12 cairo
13 glib
14 ]) ++ (with haskellPackages;
15 [
16 ]);
17}
diff --git a/accounts/gkleen@sif/xmonad/stack.yaml b/accounts/gkleen@sif/xmonad/stack.yaml
new file mode 100644
index 00000000..b8ed1147
--- /dev/null
+++ b/accounts/gkleen@sif/xmonad/stack.yaml
@@ -0,0 +1,10 @@
1nix:
2 enable: true
3 shell-file: stack.nix
4
5resolver: lts-13.21
6
7packages:
8 - .
9
10extra-deps: []
diff --git a/accounts/gkleen@sif/xmonad/xmonad-yggdrasil.nix b/accounts/gkleen@sif/xmonad/xmonad-yggdrasil.nix
new file mode 100644
index 00000000..d3d72310
--- /dev/null
+++ b/accounts/gkleen@sif/xmonad/xmonad-yggdrasil.nix
@@ -0,0 +1,21 @@
1{ mkDerivation, aeson, base, bytestring, containers, directory
2, filepath, hostname, hpack, mtl, network, parsec, process, lib
3, temporary, transformers, unix, utf8-string, X11, xmonad
4, xmonad-contrib, libnotify
5}:
6mkDerivation {
7 pname = "xmonad-yggdrasil";
8 version = "0.0.0";
9 src = ./.;
10 isLibrary = false;
11 isExecutable = true;
12 libraryToolDepends = [ hpack ];
13 executableHaskellDepends = [
14 aeson base bytestring containers directory filepath hostname mtl
15 network parsec process temporary transformers unix utf8-string X11
16 xmonad xmonad-contrib libnotify
17 ];
18 preConfigure = "hpack";
19 license = "unknown";
20 hydraPlatforms = lib.platforms.none;
21}
diff --git a/accounts/gkleen@sif/xmonad/xmonad.hs b/accounts/gkleen@sif/xmonad/xmonad.hs
new file mode 100644
index 00000000..8282ed3f
--- /dev/null
+++ b/accounts/gkleen@sif/xmonad/xmonad.hs
@@ -0,0 +1,902 @@
1{-# LANGUAGE TupleSections, ViewPatterns, OverloadedStrings, FlexibleInstances, UndecidableInstances, MultiWayIf #-}
2
3import XMonad
4import XMonad.Hooks.DynamicLog
5import XMonad.Hooks.ManageDocks
6import XMonad.Util.Run
7import XMonad.Util.Loggers
8import XMonad.Util.EZConfig(additionalKeys)
9import System.IO
10import System.IO.Error
11import System.Environment
12import Data.Map (Map)
13import qualified Data.Map as Map
14import qualified XMonad.StackSet as W
15import System.Exit
16import Control.Monad.State (get)
17-- import XMonad.Layout.Spiral
18import Data.Ratio
19import Data.List
20import Data.Char
21import Data.Maybe (fromMaybe, listToMaybe, maybeToList, catMaybes, isJust)
22import XMonad.Layout.Tabbed
23import XMonad.Prompt
24import XMonad.Prompt.Input
25import XMonad.Util.Scratchpad
26import XMonad.Util.NamedScratchpad
27import Control.Monad (sequence, liftM, liftM2, join, void)
28import XMonad.Util.WorkspaceCompare
29import XMonad.Layout.NoBorders
30import XMonad.Layout.PerWorkspace
31import XMonad.Layout.SimplestFloat
32import XMonad.Layout.Renamed
33import XMonad.Layout.Reflect
34import XMonad.Layout.OnHost
35import XMonad.Layout.Combo
36import XMonad.Layout.ComboP
37import XMonad.Layout.Column
38import XMonad.Layout.TwoPane
39import XMonad.Layout.IfMax
40import XMonad.Layout.LayoutBuilder
41import XMonad.Layout.WindowNavigation
42import XMonad.Layout.Dwindle
43import XMonad.Layout.TrackFloating
44import System.Process
45import System.Directory (removeFile)
46import System.Posix.Files
47import System.FilePath ((</>))
48import Control.Concurrent
49import System.Posix.Process (getProcessID)
50import System.IO.Error
51import System.IO
52import XMonad.Hooks.ManageHelpers hiding (CW)
53import XMonad.Hooks.UrgencyHook as U
54import XMonad.Hooks.EwmhDesktops
55import XMonad.StackSet (RationalRect (..))
56import Control.Monad (when, filterM, (<=<))
57import Graphics.X11.ExtraTypes.XF86
58import XMonad.Util.Cursor
59import XMonad.Actions.Warp
60import XMonad.Actions.FloatKeys
61import XMonad.Util.SpawnOnce
62import System.Directory
63import System.FilePath
64import XMonad.Actions.CopyWindow
65import XMonad.Hooks.ServerMode
66import XMonad.Actions.Commands
67import XMonad.Actions.CycleWS
68import XMonad.Actions.RotSlaves
69import XMonad.Actions.UpdatePointer
70import XMonad.Prompt.Window
71import Data.IORef
72import Data.Monoid
73import Data.String
74import qualified XMonad.Actions.PhysicalScreens as P
75
76import XMonad.Layout.IM
77
78import XMonad.Prompt.MyShell
79import XMonad.Prompt.MyPass
80import XMonad.Prompt.MySsh
81
82import XMonad.Mpv
83
84import Network.HostName
85
86import Control.Applicative ((<$>))
87
88import Libnotify as Notify hiding (appName)
89import qualified Libnotify as Notify (appName)
90import Libnotify (Notification)
91-- import System.Information.Battery
92
93import Data.Int (Int32)
94
95import System.Posix.Process
96import System.Posix.Signals
97import System.Posix.IO as Posix
98import Control.Exception
99
100import System.IO.Unsafe
101
102import Control.Monad.Trans.Class
103import Control.Monad.Trans.Maybe
104
105import Data.Fixed (Micro)
106
107import qualified Data.Text as Text
108import Data.Ord (comparing)
109import Debug.Trace
110
111instance MonadIO m => IsString (m ()) where
112 fromString = spawn
113
114type KeyMap = Map (ButtonMask, KeySym) (X ())
115
116data Host = Host
117 { hName :: HostName
118 , hManageHook :: ManageHook
119 , hWsp :: Integer -> WorkspaceId
120 , hCoWsp :: String -> Maybe WorkspaceId
121 , hKeysMod :: XConfig Layout -> (KeyMap -> KeyMap)
122 , hScreens :: [P.PhysicalScreen]
123 , hKbLayouts :: [(String, Maybe String)]
124 , hCmds :: X [(String, X ())]
125 , hKeyUpKeys :: XConfig Layout -> KeyMap
126 }
127
128defaultHost = Host { hName = "unkown"
129 , hManageHook = composeOne [manageScratchTerm]
130 , hWsp = show
131 , hCoWsp = const Nothing
132 , hKeysMod = const id
133 , hScreens = [0,1..]
134 , hKbLayouts = [ ("us", Just "dvp")
135 , ("us", Nothing)
136 , ("de", Nothing)
137 ]
138 , hCmds = return []
139 , hKeyUpKeys = const Map.empty
140 }
141
142browser :: String
143browser = "env MOZ_USE_XINPUT2=1 firefox"
144
145hostFromName :: HostName -> Host
146hostFromName h@("vali") = defaultHost { hName = h
147 , hManageHook = composeOne $ catMaybes [ Just manageScratchTerm
148 , assign "web" $ className =? ".dwb-wrapped"
149 , assign "web" $ className =? "Chromium"
150 , assign "work" $ className =? "Emacs"
151 , assign "media" $ className =? "mpv"
152 ]
153 , hWsp = hWsp
154 , hCoWsp = hCoWsp
155 , hKeysMod = \conf -> Map.union $ (Map.fromList $ join $ map (spawnBindings conf) [ (xK_d, ["chromium", "chromium $(xclip -o)"])
156 , (xK_e, ["emacsclient -c"])
157 ])
158 `Map.union`
159 ( Map.fromList [ ((XMonad.modMask conf .|. controlMask, xK_Return), scratchpadSpawnActionCustom $ (XMonad.terminal conf) ++ " -name scratchpad -title scratchpad -e tmux new-session -D -s scratch")
160 ] )
161 , hScreens = hScreens defaultHost
162 }
163 where
164 workspaceNames = Map.fromList [ (2, "web")
165 , (3, "work")
166 , (10, "media")
167 ]
168 hWsp = wspFromMap workspaceNames
169 hCoWsp = coWspFromMap workspaceNames
170 assign wsp test = (\wsp -> test -?> doShift wsp) <$> hCoWsp wsp
171hostFromName h
172 | h `elem` ["hel", "sif"] = defaultHost { hName = h
173 , hManageHook = namedScratchpadManageHook scratchpads <+> composeOne (catMaybes
174 [ assign "mpv" $ className =? "mpv"
175 , assign "mpv" $ (className =? "URxvt" <&&> title =? "irssi")
176 , assign "mpv" $ (className =? "URxvt" <&&> resource =? "presentation")
177 , assign "mpv" $ stringProperty "WM_WINDOW_ROLE" =? "presentation"
178 , assign "read" $ stringProperty "WM_WINDOW_ROLE" =? "presenter"
179 , assign "mpv" $ className =? "factorio"
180 , assign "web" $ className =? "chromium-browser"
181 , assign "web" $ className =? "Google-chrome"
182 , assign "work" $ (appName =? "Devtools" <&&> className =? "Firefox")
183 , assign "work" $ className =? "Postman"
184 , assign "web" $ className =? "Firefox"
185 , assign "comm" $ (className =? "URxvt" <&&> resource =? "comm")
186 , assign "comm" $ (className =? "Emacs" <&&> title =? "Mail")
187 , assign "comm" $ className =? "Zulip"
188 , assign "comm" $ className =? "Discord"
189 , assign "media" $ (className =? "URxvt" <&&> resource =? "media")
190 , assign "media" $ (className =? "URxvt" <&&> title =? "streamlink")
191 , assign "media" $ (className =? "URxvt" <&&> title =? "mpv")
192 , assign "monitor" $ (className =? "URxvt" <&&> fmap ("monitor" `isInfixOf`) title)
193 , assign "monitor" $ className =? "Grafana"
194 , Just $ (className =? "URxvt" <&&> resource =? "htop") -?> centerFloat
195 , Just $ (className =? "Scp-dbus-service.py") -?> centerFloat
196 , Just $ (className =? "URxvt" <&&> resource =? "log") -?> centerFloat
197 , assign "work" $ className =? "URxvt"
198 , assign' ["work", "uni"] $ (className =? "Emacs" <&&> appName /=? "Edit_with_Emacs_FRAME")
199 , assign' ["work", "uni"] $ className =? "jetbrains-idea-ce"
200 , assign "read" $ className =? "llpp"
201 , assign "read" $ className =? "Evince"
202 , assign "read" $ className =? "Zathura"
203 , assign "read" $ className =? "MuPDF"
204 , assign "read" $ className =? "Xournal"
205 , assign "read" $ appName =? "com-trollworks-gcs-app-GCS"
206 , assign "read" $ appName =? "Tux.py"
207 , assign "read" $ className =? "Gnucash"
208 , assign "comm" $ className =? "Skype"
209 , assign "comm" $ className =? "Daily"
210 , assign "comm" $ className =? "Pidgin"
211 , assign "comm" $ className =? "Slack"
212 , Just $ (resource =? "xvkbd") -?> doRectFloat $ RationalRect (1 % 8) (3 % 8) (6 % 8) (4 % 8)
213 , Just $ (stringProperty "_NET_WM_WINDOW_TYPE" =? "_NET_WM_WINDOW_TYPE_DIALOG") -?> doFloat
214 , Just $ (className =? "Dunst") -?> doFloat
215 , Just $ (className =? "Xmessage") -?> doCenterFloat
216 , Just $ (className =? "Nm-openconnect-auth-dialog") -?> centerFloat
217 , Just $ (className =? "Pinentry") -?> doCenterFloat
218 , Just $ (className =? "pinentry") -?> doCenterFloat
219 , Just $ (appName =? "Edit_with_Emacs_FRAME") -?> centerFloat
220 , Just $ (stringProperty "WM_WINDOW_ROLE" =? "GtkFileChooseDialog") -?> centerFloatSmall
221 , Just $ (className =? "Nvidia-settings") -?> doCenterFloat
222 , Just $ fmap ("Minetest" `isInfixOf`) title -?> doIgnore
223 , Just $ fmap ("Automachef" `isInfixOf`) title -?> doIgnore
224 , assign "call" $ className =? "zoom"
225 ])
226 , hWsp = hWsp
227 , hCoWsp = hCoWsp
228 , hKeysMod = \conf -> Map.union $ (Map.fromList $ join $ map (spawnBindings conf) [ (xK_e, ["emacsclient -c"])
229 , (xK_d, [fromString browser, fromString $ browser ++ " $(xclip -o)", fromString $ "notmuch-links"])
230 , (xK_f, ["urxvtc -name comm -title Feeds -e mosh odin -- tmux new-session -ADs comm"])
231 , (xK_c, [ inputPrompt xPConfig "dc" ?+ dc ])
232 , (xK_g, ["pidgin"])
233 , (xK_s, ["skype"])
234 -- , (xK_p, [mkPassPrompt "Type password" pwType xPConfig, mkPassPrompt "Show password" pwShow xPConfig, mkPassPrompt "Copy password" pwClip xPConfig])
235 , (xK_w, ["sudo rewacom"])
236 , (xK_y, [ "tmux new-window -dt media /var/media/link.hs $(xclip -o)"
237 , "tmux new-window -dt media /var/media/download.hs $(xclip -o)"
238 , "urxvtc -name media -e tmuxp load /var/media"
239 ])
240 , (xK_l, [ "tmux new-window -dt media mpv $(xclip -o)"
241 , "tmux new-window -dt media streamlink --retry-open 10 $(xclip -o)"
242 ])
243 , (xK_m, [ "emacsclient -c -F \"'(title . \\\"Mail\\\")\" -e '(notmuch)'"
244 , "emacsclient -c -F \"'(title . \\\"Mail\\\")\" -e '(notmuch-mua-new-mail)'"
245 , "emacsclient -c -F \"'(title . \\\"Mail\\\")\" -e \"(browse-url-mail \"$(xclip -o)\")\""
246 ])
247 , (xK_Return, ["keynav start,windowzoom", "keynav start"])
248 , (xK_t, [inputPrompt xPConfig "fuzzytime timer" ?+ fuzzytime, fuzzytime "unset", work_fuzzytime])
249 , (xK_a, [inputPrompt xPConfig "adjmix" ?+ adjmix])
250 , (xK_s, [ inputPromptWithCompl xPConfig "start synergy" synergyCompl ?+ synergyStart
251 , inputPromptWithCompl xPConfig "stop synergy" synergyCompl ?+ synergyStop
252 ])
253 , (xK_h, [ "urxvtc -name htop -e htop"
254 , "urxvtc -name log -e journalctl -xef"
255 ])
256 , (xK_x, [ "autorandr -c"
257 , "autorandr -fl def"
258 ])
259 , (xK_z, [ "zulip -- --force-device-scale-factor=2"
260 ])
261 ])
262 `Map.union`
263 ( Map.fromList [ ((XMonad.modMask conf .|. controlMask, xK_Return), namedScratchpadAction scratchpads "term")
264 , ((XMonad.modMask conf .|. controlMask, xK_a), namedScratchpadAction scratchpads "pavucontrol")
265 , ((XMonad.modMask conf .|. controlMask, xK_w), namedScratchpadAction scratchpads "alarms")
266 , ((XMonad.modMask conf .|. controlMask, xK_b), namedScratchpadAction scratchpads "blueman")
267 , ((XMonad.modMask conf .|. controlMask, xK_p), namedScratchpadAction scratchpads "keepassxc")
268 , ((XMonad.modMask conf .|. controlMask, xK_t), namedScratchpadAction scratchpads "toggl")
269 , ((XMonad.modMask conf .|. controlMask, xK_e), namedScratchpadAction scratchpads "emacs")
270 , ((XMonad.modMask conf .|. controlMask, xK_m), namedScratchpadAction scratchpads "calendar")
271 , ((XMonad.modMask conf .|. controlMask, xK_f), namedScratchpadAction scratchpads "music")
272 , ((XMonad.modMask conf .|. mod1Mask, xK_Up), rotate U)
273 , ((XMonad.modMask conf .|. mod1Mask, xK_Down), rotate D)
274 , ((XMonad.modMask conf .|. mod1Mask, xK_Left), rotate L)
275 , ((XMonad.modMask conf .|. mod1Mask, xK_Right), rotate R)
276 -- , ((XMonad.modMask conf .|. shiftMask, xK_a), startMute "hel")
277 ] )
278 , hKeyUpKeys = \conf -> Map.fromList [ -- ((XMonad.modMask conf .|. shiftMask, xK_a), stopMute "hel")
279 ]
280 , hScreens = hScreens defaultHost
281 , hCmds = return [ ("prev-workspace", prevWS)
282 , ("next-workspace", nextWS)
283 , ("prev-window", rotAllDown)
284 , ("next-window", rotAllUp)
285 , ("banish", banishScreen LowerRight)
286 , ("update-gpg-tty", safeSpawn "gpg-connect-agent" ["UPDATESTARTUPTTY", "/bye"])
287 , ("rescreen", rescreen)
288 , ("repanel", do
289 spawn "nm-applet"
290 spawn "blueman-applet"
291 spawn "pasystray"
292 spawn "kdeconnect-indicator"
293 spawn "dunst -print"
294 spawn "udiskie"
295 spawn "autocutsel -s PRIMARY"
296 spawn "autocutsel -s CLIPBOARD"
297 )
298 , ("pause", mediaMpv $ MpvSetProperty "pause" True)
299 , ("unpause", mediaMpv $ MpvSetProperty "pause" False)
300 , ("exit", io $ exitWith ExitSuccess)
301 ]
302 }
303 where
304 withGdkScale act = void . xfork $ setEnv "GDK_SCALE" "2" >> act
305 workspaceNames = Map.fromList [ (1, "comm")
306 , (2, "web")
307 , (3, "work")
308 , (4, "read")
309 , (5, "monitor")
310 , (6, "uni")
311 , (8, "call")
312 , (9, "media")
313 , (10, "mpv")
314 ]
315 scratchpads = [ NS "term" "urxvtc -name scratchpad -title scratchpad -e tmux new-session -AD -s scratch" (resource =? "scratchpad") centerFloat
316 , NS "pavucontrol" "pavucontrol" (resource =? "pavucontrol") centerFloat
317 , NS "alarms" "alarm-clock-applet" (className =? "Alarm-clock-applet" <&&> title =? "Alarms") centerFloat
318 , NS "blueman" "blueman-manager" (className =? ".blueman-manager-wrapped") centerFloat
319 , NS "keepassxc" "keepassxc" (className =? "KeePassXC") centerFloat
320 , NS "toggl" "toggldesktop" (className =? "Toggl Desktop") centerFloat
321 , NS "calendar" "minetime -- --force-device-scale-factor=1.6" (className =? "MineTime") centerFloat
322 , NS "emacs" "emacsclient -c -F \"'(title . \\\"Scratchpad\\\")\"" (className =? "Emacs" <&&> title =? "Scratchpad") centerFloat
323 , NS "music" "google-play-music-desktop-player --force-device-scale-factor=1.6" (className =? "Google Play Music Desktop Player") centerFloat
324 ]
325 centerFloat = customFloating $ RationalRect (1 % 16) (1 % 16) (7 % 8) (7 % 8)
326 centerFloatSmall = customFloating $ RationalRect (1 % 4) (1 % 4) (1 % 2) (1 % 2)
327 hWsp = wspFromMap workspaceNames
328 hCoWsp = coWspFromMap workspaceNames
329 assign wsp test = (\wsp -> test -?> doShift wsp) <$> hCoWsp wsp
330 assign' :: [String] -> Query Bool -> Maybe MaybeManageHook
331 assign' wsps test = do
332 wsIds <- mapM hCoWsp wsps
333 return $ test -?> go wsIds
334 where
335 go :: [WorkspaceId] -> ManageHook
336 go wsps = do
337 visWsps <- liftX $ (\wset -> W.tag . W.workspace <$> W.current wset : W.visible wset) <$> gets windowset
338 case (filter (`elem` visWsps) wsps, wsps) of
339 (wsp : _, _) -> doShift wsp
340 (_, wsp : _) -> doShift wsp
341 ([], []) -> return mempty
342 rotate rot = do
343 safeSpawn "xrandr" ["--output", "eDP-1", "--rotate", xrandrDir]
344 mapM_ rotTouch touchscreens
345 where
346 xrandrDir = case rot of
347 U -> "normal"
348 L -> "left"
349 R -> "right"
350 D -> "inverted"
351 matrix = case rot of
352 U -> [ [ 1, 0, 0]
353 , [ 0, 1, 0]
354 , [ 0, 0, 1]
355 ]
356 L -> [ [ 0, -1, 1]
357 , [ 1, 0, 0]
358 , [ 0, 0, 1]
359 ]
360 R -> [ [ 0, 1, 0]
361 , [-1, 0, 1]
362 , [ 0, 0, 1]
363 ]
364 D -> [ [-1, 0, 1]
365 , [ 0, -1, 1]
366 , [ 0, 0, 1]
367 ]
368 touchscreens = [ "Wacom Co.,Ltd. Pen and multitouch sensor Finger touch"
369 , "Wacom Co.,Ltd. Pen and multitouch sensor Pen stylus"
370 , "Wacom Co.,Ltd. Pen and multitouch sensor Pen eraser"
371 ]
372 rotTouch screen = do
373 safeSpawn "xinput" $ ["set-prop", screen, "Coordinate Transformation Matrix"] ++ map (\n -> show n ++ ",") (concat matrix)
374 safeSpawn "xinput" ["map-to-output", screen, "eDP-1"]
375 withPw f label = io . void . forkProcess $ do
376 uninstallSignalHandlers
377 void $ createSession
378 (dropWhileEnd isSpace -> pw) <- readCreateProcess (proc "pass" ["show", label]) ""
379 void $ f pw
380 pwType :: String -> X ()
381 pwType = withPw $ readCreateProcess (proc "xdotool" ["type", "--clearmodifiers", "--file", "-"])
382 pwClip label = safeSpawn "pass" ["show", "--clip", label]
383 pwShow :: String -> X ()
384 pwShow = withPw $ \pw -> do
385 xmessage <- fromMaybe "xmessage" <$> liftIO (lookupEnv "XMONAD_XMESSAGE")
386 readCreateProcess (proc xmessage ["-file", "-"]) pw
387 fuzzytime str = safeSpawn "fuzzytime" $ "timer" : words str
388 work_fuzzytime = io . void . forkProcess $ do
389 readCreateProcess (proc "worktime" []) "" >>= safeSpawn "fuzzytime" . ("timer" : ) . pure
390 adjmix str = safeSpawn "adjmix" $ words str
391 dc expr = void . xfork $ do
392 result <- readProcess "dc" [] $ expr ++ "f"
393 let
394 (first : rest) = filter (not . null) $ lines result
395 notification = Notify.summary first <> Notify.body (unlines rest) <> Notify.timeout Infinite <> Notify.urgency Normal <> Notify.appName "dc"
396 void $ Notify.display notification
397 synergyCompl = mkComplFunFromList' ["mathw86"]
398 synergyStart host = safeSpawn "systemctl" ["--user", "start", "synergy-rtunnel@" ++ host ++ ".service"]
399 synergyStop host = safeSpawn "systemctl" ["--user", "stop", "synergy-rtunnel@" ++ host ++ ".service"]
400
401hostFromName _ = defaultHost
402
403-- muteRef :: IORef (Maybe (String, Notification))
404-- {-# NOINLINE muteRef #-}
405-- muteRef = unsafePerformIO $ newIORef Nothing
406
407-- startMute, stopMute :: String -> X ()
408-- startMute sink = liftIO $ do
409-- muted <- isJust <$> readIORef muteRef
410-- when (not muted) $ do
411-- let
412-- notification = Notify.summary "Muted" <> Notify.timeout Infinite <> Notify.urgency Normal
413-- level = "0.0dB"
414-- -- level <- runProcessWithInput "ssh" ["bragi", "cat", "/dev/shm/mix/" ++ sink ++ "/level"] ""
415-- -- callProcess "ssh" ["bragi", "adjmix", "-t", sink, "-o", "0"]
416-- hPutStrLn stderr "Mute"
417-- writeIORef muteRef . Just . (level, ) =<< Notify.display notification
418-- stopMute sink = liftIO $ do
419-- let
420-- unmute (Just (level, notification)) = do
421-- hPutStrLn stderr "Unmute"
422-- -- callProcess "ssh" ["bragi", "adjmix", "-t", sink, "-o", level]
423-- Notify.close notification
424-- unmute Nothing = return ()
425-- muted <- isJust <$> readIORef muteRef
426-- when muted . join . atomicModifyIORef muteRef $ (Nothing, ) . unmute
427
428wspFromMap workspaceNames = \i -> case Map.lookup i workspaceNames of
429 Just str -> show i ++ " " ++ str
430 Nothing -> show i
431
432coWspFromMap workspaceNames = \str -> case filter ((== str) . snd) $ Map.toList workspaceNames of
433 [] -> Nothing
434 [(i, _)] -> Just $ wspFromMap workspaceNames i
435 _ -> Nothing
436
437spawnModifiers = [0, controlMask, shiftMask .|. controlMask]
438spawnBindings :: XConfig layout -> (KeySym, [X ()]) -> [((KeyMask, KeySym), X ())]
439spawnBindings conf (k, cmds) = zipWith (\m cmd -> ((modm .|. mod1Mask .|. m, k), cmd)) spawnModifiers cmds
440 where
441 modm = XMonad.modMask conf
442
443manageScratchTerm = (resource =? "scratchpad" <||> resource =? "keysetup") -?> doRectFloat $ RationalRect (1 % 16) (1 % 16) (7 % 8) (7 % 8)
444
445tabbedLayout t = renamed [Replace "Tabbed"] $ reflectHoriz $ t CustomShrink $ tabbedTheme
446tabbedLayoutHoriz t = renamed [Replace "Tabbed Horiz"] $ reflectVert $ t CustomShrink $ tabbedTheme
447tabbedTheme = def
448 { activeColor = "black"
449 , inactiveColor = "black"
450 , urgentColor = "black"
451 , activeBorderColor = "grey"
452 , inactiveBorderColor = "#202020"
453 , urgentBorderColor = "#bb0000"
454 , activeTextColor = "grey"
455 , inactiveTextColor = "grey"
456 , urgentTextColor = "grey"
457 , decoHeight = 32
458 , fontName = "xft:Fira Mono for Powerline:style=Medium:pixelsize=22.5"
459 }
460
461main :: IO ()
462main = do
463 arguments <- either (const []) id <$> tryIOError getArgs
464 case arguments of
465 ["--command", s] -> do
466 d <- openDisplay ""
467 rw <- rootWindow d $ defaultScreen d
468 a <- internAtom d "XMONAD_COMMAND" False
469 m <- internAtom d s False
470 allocaXEvent $ \e -> do
471 setEventType e clientMessage
472 setClientMessageEvent e rw a 32 m currentTime
473 sendEvent d rw False structureNotifyMask e
474 sync d False
475 _ -> do
476 -- batteryMon <- xfork $ monitorBattery Nothing Nothing
477 hostname <- getHostName
478 let
479 host = hostFromName hostname
480 setEnv "HOST" hostname
481 let myConfig = withHostUrgency . ewmh $ docks def
482 { manageHook = hManageHook host
483 , terminal = "urxvtc"
484 , layoutHook = smartBorders . avoidStruts $ windowNavigation layout'
485 , logHook = do
486 dynamicLogString xmobarPP' >>= writeProps
487 updatePointer (99 % 100, 98 % 100) (0, 0)
488 , modMask = mod4Mask
489 , keys = \conf -> hKeysMod host conf $ myKeys' conf host
490 , workspaces = take (length numKeys) $ map wsp [1..]
491 , startupHook = setDefaultCursor xC_left_ptr
492 , normalBorderColor = "#202020"
493 , focusedBorderColor = "grey"
494 , handleEventHook = fullscreenEventHook <+> (serverModeEventHookCmd' $ hCmds host) <+> keyUpEventHook
495 }
496 writeProps str = do
497 let encodeCChar = map $ fromIntegral . fromEnum
498 atoms = [ "_XMONAD_WORKSPACES"
499 , "_XMONAD_LAYOUT"
500 , "_XMONAD_TITLE"
501 ]
502 (flip mapM_) (zip atoms (lines str)) $ \(atom', content) -> do
503 ustring <- getAtom "UTF8_STRING"
504 atom <- getAtom atom'
505 withDisplay $ \dpy -> io $ do
506 root <- rootWindow dpy $ defaultScreen dpy
507 changeProperty8 dpy root atom ustring propModeReplace $ encodeCChar content
508 sync dpy True
509 wsp = hWsp host
510 -- We can´t define per-host layout modifiers because we lack dependent types
511 layout' = onHost "skadhi" ( onWorkspace (wsp 1) (Full ||| withIM (1%5) (Title "Buddy List") tabbedLayout') $
512 onWorkspace (wsp 10) Full $
513 onWorkspace (wsp 2) (Full ||| tabbedLayout') $
514 onWorkspace (wsp 5) tabbedLayout' $
515 onWorkspace (wsp 8) (withIM (1%5) (Title "Friends") tabbedLayout') $
516 defaultLayouts
517 ) $
518 onHost "vali" ( onWorkspace (wsp 2) (Full ||| tabbedLayout' ||| combineTwo (TwoPane 0.01 0.57) Full tabbedLayout') $
519 onWorkspace (wsp 3) workLayouts $
520 defaultLayouts
521 ) $
522 onHost "hel" ( onWorkspace (wsp 1) (withIM (1 % 8) (Title "Buddy List") $ trackFloating tabbedLayout') $
523 onWorkspace (wsp 2) (tabbedLayout''' ||| Dwindle R CW 1 (5 % 100)) $
524 onWorkspace (wsp 3) workLayouts $
525 onWorkspace (wsp 6) workLayouts $
526 onWorkspace (wsp 4) (tabbedLayout' ||| tabbedLayoutHoriz' ||| Dwindle R CW 1 (5 % 100)) $
527 onWorkspace (wsp 5) (tabbedLayout''' ||| Dwindle R CW 1 (5 % 100)) $
528 onWorkspace (wsp 10) (tabbedLayout''' ||| combineTwoP (TwoPane (1 % 100) (3 % 4)) tabbedLayout''' tabbedLayout''' (ClassName "mpv") ||| Dwindle R CW 1 (5 % 100)) $
529 defaultLayouts
530 ) $
531 onHost "sif" ( onWorkspace (wsp 1) (withIM (1 % 8) (Title "Buddy List") $ trackFloating tabbedLayout') $
532 onWorkspace (wsp 2) (tabbedLayout''' ||| Dwindle R CW 1 (5 % 100)) $
533 onWorkspace (wsp 3) workLayouts $
534 onWorkspace (wsp 6) workLayouts $
535 onWorkspace (wsp 4) (tabbedLayout' ||| tabbedLayoutHoriz' ||| Dwindle R CW 1 (5 % 100)) $
536 onWorkspace (wsp 5) (tabbedLayout''' ||| Dwindle R CW 1 (5 % 100)) $
537 onWorkspace (wsp 8) tabbedLayout''' $
538 onWorkspace (wsp 10) (tabbedLayout''' ||| combineTwoP (TwoPane (1 % 100) (3 % 4)) tabbedLayout''' tabbedLayout''' (ClassName "mpv") ||| Dwindle R CW 1 (5 % 100)) $
539 defaultLayouts
540 ) $
541 defaultLayouts
542 -- tabbedLayout''' = renamed [Replace "Tabbed'"] $ IfMax 1 (noBorders Full) (tabbedLayout tabbedBottomAlways)
543 tabbedLayout''' = tabbedLayout tabbedBottom
544 tabbedLayout' = tabbedLayout tabbedBottomAlways
545 tabbedLayoutHoriz' = tabbedLayoutHoriz tabbedLeftAlways
546 defaultLayouts = {- spiralWithDir East CW (1 % 2) -} Dwindle R CW 1 (5 % 100) ||| tabbedLayout' ||| Full
547 -- workLayouts = {- spiralWithDir East CW (1 % 2) -} Dwindle R CW (2 % 1) (5 % 100) ||| tabbedLayout' ||| Full
548 workLayouts = tabbedLayout' ||| (renamed [Replace "Combined"] $ combineTwoP (TwoPane (1 % 100) (1891 % 2560)) tabbedLayout''' (Column 1.6) (ClassName "Postman" `Or` ClassName "Emacs" `Or` ClassName "jetbrains-idea-ce" `Or` (Resource "Devtools" `And` ClassName "Firefox"))) ||| Full ||| Dwindle R CW 1 (5 % 100)
549 sqrtTwo = approxRational (sqrt 2) (1 / 2560)
550 xmobarPP' = xmobarPP { ppTitle = shorten 80
551 , ppSort = (liftM2 (.)) getSortByIndex $ return scratchpadFilterOutWorkspace
552 , ppUrgent = wrap "(" ")" . xmobarColor "#800000" ""
553 , ppHiddenNoWindows = xmobarColor "#202020" "" . wrap "(" ")"
554 , ppVisible = wrap "(" ")" . xmobarColor "#808000" ""
555 , ppCurrent = wrap "(" ")" . xmobarColor "#008000" ""
556 , ppHidden = wrap "(" ")"
557 , ppWsSep = " "
558 , ppSep = "\n"
559 }
560 withHostUrgency = case hostname of
561 "hel" -> withUrgencyHookC urgencyHook' $ urgencyConfig { suppressWhen = U.Never, remindWhen = Dont }
562 "sif" -> withUrgencyHookC urgencyHook' $ urgencyConfig { suppressWhen = U.Never, remindWhen = Dont }
563 _ -> id
564 urgencyHook' window = do
565 runQuery ((resource =? "comm" <||> resource =? "Pidgin" <||> className =? "Gajim" <||> className =? "Skype") --> safeSpawn "thinklight" ["Blink", "100"]) window
566 urgencyHook (BorderUrgencyHook { urgencyBorderColor = "#bb0000" }) window
567 shutdown :: SomeException -> IO a
568 shutdown e = do
569 let pids = [ -- batteryMon
570 ]
571 mapM_ (signalProcess sigTERM) pids
572 mapM_ (getProcessStatus False False) pids
573 throw e
574 keyUpEventHook :: Event -> X All
575 keyUpEventHook event = handle event >> return (All True)
576 where
577 handle (KeyEvent { ev_event_type = t, ev_state = m, ev_keycode = code })
578 | t == keyRelease = withDisplay $ \dpy -> do
579 s <- io $ keycodeToKeysym dpy code 0
580 mClean <- cleanMask m
581 ks <- asks $ hKeyUpKeys host . config
582 userCodeDef () $ whenJust (Map.lookup (mClean, s) ks) id
583 | otherwise = return ()
584 handle _ = return ()
585 handle shutdown $ launch myConfig
586
587secs :: Int -> Int
588secs = (* 1000000)
589
590-- monitorBattery :: Maybe BatteryContext -> Maybe Notification -> IO ()
591-- monitorBattery Nothing n = do
592-- ctx <- batteryContextNew
593-- case ctx of
594-- Nothing -> threadDelay (secs 10) >> monitorBattery Nothing n
595-- Just _ -> monitorBattery ctx n
596-- monitorBattery ctx@(Just ctx') n = do
597-- batInfo <- getBatteryInfo ctx'
598-- case batInfo of
599-- Nothing -> threadDelay (secs 1) >> monitorBattery ctx n
600-- Just batInfo -> do
601-- let n'
602-- | batteryState batInfo == BatteryStateDischarging
603-- , timeLeft <= 1200
604-- , timeLeft > 0 = Just $ summary "Discharging" <> hint "value" percentage <> urgency u <> body (duz timeLeft ++ "left")
605-- | otherwise = Nothing
606-- u
607-- | timeLeft <= 600 = Critical
608-- | timeLeft <= 1800 = Normal
609-- | otherwise = Low
610-- timeLeft = batteryTimeToEmpty batInfo
611-- percentage :: Int32
612-- percentage = round $ batteryPercentage batInfo
613-- ts = [("s", 60), ("m", 60), ("h", 24), ("d", 365), ("y", 1)]
614-- duz ms = ss
615-- where (ss, _) = foldl (\(ss, x) (s, y) -> ((if rem x y > 0 then show (rem x y) ++ s ++ " " else "") ++ ss , quot x y)) ("", ms) ts
616-- case n' of
617-- Just n' -> Notify.display (maybe mempty reuse n <> Notify.appName "monitorBattery" <> n') >>= (\n -> threadDelay (secs 2) >> monitorBattery ctx (Just n))
618-- Nothing -> threadDelay (secs 30) >> monitorBattery ctx n
619
620disableTouchpad, disableTrackpoint, enableTrackpoint, enableTouchpad :: X ()
621enableTouchpad = safeSpawn "xinput" ["enable", "SynPS/2 Synaptics TouchPad"]
622disableTouchpad = safeSpawn "xinput" ["disable", "SynPS/2 Synaptics TouchPad"]
623enableTrackpoint = safeSpawn "xinput" ["enable", "TPPS/2 IBM TrackPoint"]
624disableTrackpoint = safeSpawn "xinput" ["disable", "TPPS/2 IBM TrackPoint"]
625
626isDisabled :: String -> X Bool
627isDisabled str = do
628 out <- runProcessWithInput "xinput" ["list", str] ""
629 return $ "disabled" `isInfixOf` out
630
631
632spawnKeychain :: X ()
633spawnKeychain = do
634 home <- liftIO getHomeDirectory
635 let keys = (map ((home </>) . (".ssh/" ++)) ["id", "id-rsa"]) ++ ["6B13AA67"]
636 liftIO (maybe (return ()) (setEnv "SSH_ASKPASS") =<< findAskpass)
637 safeSpawn "keychain" . (["--agents", "gpg,ssh"] ++)=<< liftIO (filterM doesFileExist keys)
638 where
639 findAskpass = filter `liftM` readFile "/etc/zshrc"
640 filter = listToMaybe . catMaybes . map (stripPrefix "export SSH_ASKPASS=") . lines
641
642assimilateKeychain :: X ()
643assimilateKeychain = liftIO $ assimilateKeychain' >> return ()
644assimilateKeychain' = tryIOError $ do
645 -- pid <- getProcessID
646 -- tmpDir <- lookupEnv "TMPDIR"
647 -- let tmpDir' = fromMaybe "/tmp" tmpDir
648 -- tmpFile = tmpDir' </> "xmonad-keychain" ++ (show pid) ++ ".env"
649 env <- runProcessWithInput "sh" ["-c", "eval $(keychain --eval --noask --agents gpg,ssh); env"] "" -- > " ++ tmpFile] ""
650 -- env <- readFile tmpFile
651 let envVars = Map.fromList $ map (\(k, v) -> (k, tail' v)) $ map (span (/= '=')) $ envLines
652 envVars' = Map.filterWithKey (\k _ -> k `elem` transfer) envVars
653 transfer = ["SSH_AUTH_SOCK", "SSH_AGENT_PID", "GPG_AGENT_INFO"]
654 envLines = filter (elem '=') $ lines env :: [String]
655 sequence $ map (\(k, c) -> setEnv k c) $ Map.toList envVars'
656 -- removeFile tmpFile
657 where
658 tail' [] = []
659 tail' (x:xs) = xs
660
661
662numKeys = [xK_parenleft, xK_parenright, xK_braceright, xK_plus, xK_braceleft, xK_bracketright, xK_bracketleft, xK_exclam, xK_equal, xK_asterisk]
663
664instance Shrinker CustomShrink where
665 shrinkIt _ "" = [""]
666 shrinkIt s cs
667 | length cs >= 4 = cs : shrinkIt s ((reverse . drop 4 . reverse $ cs) ++ "...")
668 | otherwise = cs : shrinkIt s (init cs)
669
670xPConfig :: XPConfig
671xPConfig = def
672 { font = "xft:Fira Mono for Powerline:style=Medium:pixelsize=22.5"
673 , height = 32
674 , bgColor = "black"
675 , fgColor = "grey"
676 , fgHLight = "green"
677 , bgHLight = "black"
678 , borderColor = "grey"
679 , searchPredicate = (\needle haystack -> all (`isInfixOf` map toLower haystack) . map (map toLower) $ words needle)
680 , position = Top
681 }
682
683sshOverrides = map (\h -> mkOverride { oHost = h, oCommand = moshCmd . inTmux } )
684 [
685 "odin", "odin.asgard.yggdrasil"
686 , "ymir", "ymir.yggdrasil.li", "ymir.niflheim.yggdrasil"
687 , "surtr", "yggdrasil.li", "surtr.yggdrasil.li", "praseodym.org", "surtr.praseodym.org", "surtr.141.li", "141.li"
688 , "vindler", "vindler.alfheim.yggdrasil"
689 , "ullr"
690 , "heimdallr", "heimdallr.asgard.yggdrasil"
691 , "testworx"
692 ]
693 ++
694 map (\h -> mkOverride { oHost = h, oCommand = moshCmd' "/run/current-system/sw/bin/mosh-server" . withEnv [("TERM", "xterm")] . inTmux} )
695 [ "bragi", "bragi.asgard.yggdrasil"
696 ]
697 ++
698 map (\h -> mkOverride { oHost = h, oCommand = sshCmd . withEnv [("TERM", "xterm")] . inTmux } )
699 [ "remote.cip.ifi.lmu.de"
700 , "uniworx3", "uniworx4", "uniworxdb"
701 ]
702
703backlight :: (Rational -> Rational) -> X ()
704backlight f = void . xfork . liftIO $ do
705 [ _device
706 , _class
707 , read . Text.unpack -> currentBright
708 , _currentPercentage
709 , read . Text.unpack -> maximumBright
710 ] <- Text.splitOn "," . Text.pack <$> readProcess "brightnessctl" ["-m"] ""
711 let current = currentBright % maximumBright
712 new' = f current * fromIntegral maximumBright
713 new :: Integer
714 new | floor new' < 0 = 0
715 | ceiling new' > maximumBright = maximumBright
716 | new' >= maximumBright % 2 = ceiling new'
717 | otherwise = floor new'
718 callProcess "brightnessctl" ["-m", "s", show new]
719
720cycleThrough :: [Rational] -> (Rational -> Rational)
721cycleThrough opts current = fromMaybe currentOpt $ listToMaybe next'
722 where currentOpt = minimumBy (comparing $ abs . subtract current) opts
723 (_, _ : next') = break (== currentOpt) opts
724
725cycleKbLayout :: [(String, Maybe String)] -> X ()
726cycleKbLayout [] = return ()
727cycleKbLayout layouts = liftIO $ do
728 next <- (getNext . extract) `liftM` runProcessWithInput "setxkbmap" ["-query"] ""
729 let
730 args = case next of
731 (l, Just v) -> [l, v]
732 (l, Nothing) -> [l]
733 safeSpawn "setxkbmap" args
734 where
735 extract :: String -> Maybe (String, Maybe String)
736 extract str = listToMaybe $ do
737 ["layout:", l] <- str'
738 [(l, Just v) | ["variant:", v] <- str'] ++ pure (l, Nothing)
739 where
740 str' = map words $ lines str
741 getNext :: Maybe (String, Maybe String) -> (String, Maybe String)
742 getNext = maybe (head layouts) getNext'
743 getNext' x = case elemIndex x layouts of
744 Nothing -> getNext Nothing
745 Just i -> layouts !! ((i + 1) `mod` length layouts)
746
747mpvAll' :: MpvCommand -> IO [MpvResponse]
748mpvAll' = mpvAll "/var/media/.mpv-ipc"
749
750mpvOne' :: MpvCommand -> IO (Maybe MpvResponse)
751mpvOne' = mpvOne "/var/media/.mpv-ipc"
752
753mediaMpv :: MpvCommand -> X ()
754mediaMpv cmd = void . xfork $ print =<< mpvAll' cmd
755
756mediaMpvTogglePause :: X ()
757mediaMpvTogglePause = void . xfork $ do
758 paused <- mapM mpvResponse <=< mpvAll' $ MpvGetProperty "pause"
759 if
760 | and paused -> print <=< mpvAll' $ MpvSetProperty "pause" False
761 | otherwise -> print <=< mpvOne' $ MpvSetProperty "pause" True
762
763myKeys' conf host = Map.fromList $
764 -- launch a terminal
765 [ ((modm, xK_Return), spawn $ (XMonad.terminal conf) ++ " -e tmux")
766 , ((modm .|. shiftMask, xK_Return), spawn $ XMonad.terminal conf)
767
768 -- launch dmenu
769 --, ((modm, xK_d ), spawn "exe=`dmenu_path | dmenu` && eval \"exec $exe\"")
770 , ((modm, xK_d ), shellPrompt "Run: " xPConfig)
771 , ((modm .|. shiftMask, xK_d ), prompt "Run in Terminal: " ("urxvtc" ++ " -e") xPConfig)
772 , ((modm, xK_at ), sshPrompt sshOverrides xPConfig)
773
774 -- close focused window
775 , ((modm .|. shiftMask, xK_q ), kill)
776 , ((modm .|. controlMask .|. shiftMask, xK_q ), spawn "xkill")
777
778 -- Rotate through the available layout algorithms
779 , ((modm, xK_space ), sendMessage NextLayout)
780
781 -- Reset the layouts on the current workspace to default
782 , ((modm .|. controlMask, xK_r ), (setLayout $ XMonad.layoutHook conf) >> refresh)
783
784 -- Resize viewed windows to the correct size
785 , ((modm, xK_r ), refresh)
786
787 -- Move focus to the next window
788 , ((modm, xK_t ), windows W.focusDown)
789
790 -- Move focus to the previous window
791 , ((modm, xK_n ), windows W.focusUp )
792
793 -- Move focus to the master window
794 , ((modm, xK_m ), windows W.focusMaster )
795
796 -- Swap the focused window and the master window
797 , ((modm .|. shiftMask, xK_m ), windows W.swapMaster)
798
799 -- Swap the focused window with the next window
800 , ((modm .|. shiftMask, xK_t ), windows W.swapDown )
801
802 -- Swap the focused window with the previous window
803 , ((modm .|. shiftMask, xK_n ), windows W.swapUp )
804
805 -- Swap the focused window with the previous window
806 , ((modm .|. shiftMask .|. controlMask, xK_m), sendMessage SwapWindow)
807
808 , ((modm, xK_Right), sendMessage $ Go R)
809 , ((modm, xK_Left ), sendMessage $ Go L)
810 , ((modm, xK_Up ), sendMessage $ Go U)
811 , ((modm, xK_Down ), sendMessage $ Go D)
812 , ((modm .|. shiftMask , xK_Right), sendMessage $ Move R)
813 , ((modm .|. shiftMask , xK_Left ), sendMessage $ Move L)
814 , ((modm .|. shiftMask , xK_Up ), sendMessage $ Move U)
815 , ((modm .|. shiftMask , xK_Down ), sendMessage $ Move D)
816 -- , ((modm .|. controlMask, xK_Right), withFocused $ keysMoveWindow (10, 0))
817 -- , ((modm .|. controlMask, xK_Left ), withFocused $ keysMoveWindow (-10, 0))
818 -- , ((modm .|. controlMask, xK_Up ), withFocused $ keysMoveWindow (0, -10))
819 -- , ((modm .|. controlMask, xK_Down ), withFocused $ keysMoveWindow (0, 10))
820 -- Shrink the master area
821 , ((modm, xK_h ), sendMessage Shrink)
822
823 -- Expand the master area
824 , ((modm, xK_s ), sendMessage Expand)
825
826 -- Push window back into tiling
827 , ((modm .|. shiftMask, xK_space ), withFocused $ windows . W.sink)
828 , ((modm, xK_BackSpace), focusUrgent)
829 , ((modm .|. shiftMask, xK_BackSpace), clearUrgents)
830
831 -- Increment the number of windows in the master area
832 , ((modm , xK_comma ), sendMessage (IncMasterN 1))
833
834 -- Deincrement the number of windows in the master area
835 , ((modm , xK_period), sendMessage (IncMasterN (-1)))
836
837 , ((0, xF86XK_AudioRaiseVolume), safeSpawn "pulseaudio-ctl" ["up", "2"])
838 , ((0, xF86XK_AudioLowerVolume), safeSpawn "pulseaudio-ctl" ["down", "2"])
839 , ((0, xF86XK_AudioMute), safeSpawn "pulseaudio-ctl" ["mute"])
840 , ((0, xF86XK_AudioPause), mediaMpv $ MpvSetProperty "pause" False)
841 , ((0, {-xF86XK_AudioMicMute-} 269025202), safeSpawn "pulseaudio-ctl" ["mute-input"])
842 , ((0, xF86XK_AudioPlay), mediaMpvTogglePause)
843 , ((modm .|. mod1Mask, xK_space), mediaMpvTogglePause)
844
845 , ((0, xF86XK_MonBrightnessDown), backlight (subtract 5))
846 , ((0, xF86XK_MonBrightnessUp), backlight (+ 5))
847
848 , ((modm , xK_Escape), cycleKbLayout (hKbLayouts host))
849 , ((modm .|. controlMask, xK_Escape), safeSpawn "setxkbmap" $ fst (head $ hKbLayouts host) : maybeToList (snd . head $ hKbLayouts host))
850
851 -- Toggle the status bar gap
852 -- Use this binding with avoidStruts from Hooks.ManageDocks.
853 -- See also the statusBar function from Hooks.DynamicLog.
854 --
855 , ((modm , xK_b ), sendMessage ToggleStruts)
856
857 , ((modm .|. shiftMask, xK_p ), safeSpawn "playerctl" ["-a", "pause"])
858
859 -- Quit xmonad
860 , ((modm .|. shiftMask, xK_e ), io (exitWith ExitSuccess))
861
862 -- Restart xmonad
863 -- , ((modm .|. shiftMask .|. controlMask, xK_r ), void . xfork $ recompile False >>= flip when (safeSpawn "xmonad" ["--restart"]))
864 , ((modm .|. shiftMask, xK_r ), void . liftIO $ executeFile "xmonad" True [] Nothing)
865 , ((modm .|. shiftMask, xK_l ), void . xfork $ do
866 sessId <- getEnv "XDG_SESSION_ID"
867 safeSpawn "loginctl" ["lock-session", sessId]
868 )
869 , ((modm .|. shiftMask, xK_s ), safeSpawn "systemctl" ["suspend"])
870 , ((modm .|. shiftMask, xK_h ), safeSpawn "systemctl" ["hibernate"])
871 , ((modm .|. shiftMask, xK_b ), backlight $ cycleThrough [1, 3 % 4, 1 % 2, 1 % 4, 1 % 10, 1 % 100, 0]
872 )
873 , ((modm .|. shiftMask .|. controlMask, xK_b), backlight $ cycleThrough [0, 1 % 100, 1 % 10, 1 % 4, 1 % 2, 3 % 4, 1]
874 )
875 , ((modm, xK_v ), windows copyToAll) -- @@ Make focused window always visible
876 , ((modm .|. shiftMask, xK_v ), killAllOtherCopies) -- @@ Toggle window state back
877 , ((modm .|. shiftMask, xK_g ), windowPrompt xPConfig Goto wsWindows)
878 , ((modm .|. shiftMask .|. controlMask, xK_g ), windowPrompt xPConfig Bring allWindows)
879 ]
880 ++
881
882 --
883 -- mod-[1..9], Switch to workspace N
884 --
885 -- mod-[1..9], Switch to workspace N
886 -- mod-shift-[1..9], Move client to workspace N
887 --
888 [((m .|. modm, k), windows $ f i)
889 | (i, k) <- zip (XMonad.workspaces conf) $ numKeys
890 , (f, m) <- [(W.greedyView, 0), (W.shift, shiftMask)]
891 ]
892 ++
893 [((m .|. modm .|. controlMask, k), void . runMaybeT $
894 MaybeT (P.getScreen def i) >>= MaybeT . screenWorkspace >>= lift . windows . f
895 )
896 | (i, k) <- zip (hScreens host) [xK_g, xK_c, xK_r, xK_l]
897 , (f, m) <- [(W.view, 0), (W.shift, shiftMask)]
898 ]
899 where
900 modm = XMonad.modMask conf
901
902
diff --git a/accounts/gkleen@sif/xresources.nix b/accounts/gkleen@sif/xresources.nix
new file mode 100644
index 00000000..3bd9af2c
--- /dev/null
+++ b/accounts/gkleen@sif/xresources.nix
@@ -0,0 +1,46 @@
1{
2 "Xft.dpi" = 282;
3 "Xft.autohint" = false;
4 "Xft.lcdfilter" = "lcddefault";
5 "Xft.hintstyle" = "hintfull";
6 "Xft.hinting" = true;
7 "Xft.antialias" = true;
8 "Xft.rgba" = "rgb";
9
10 # special
11 "*.foreground" = "#d9d9d9";
12 "*.background" = "#000000";
13 "*.cursorColor" = "#d9d9d9";
14
15 # black
16 "*.color0" = "#000000";
17 "*.color8" = "#757a80";
18
19 # red
20 "*.color1" = "#bf4949";
21 "*.color9" = "#e66e6e";
22
23 # green
24 "*.color2" = "#9fb346";
25 "*.color10" = "#cbd676";
26
27 # yellow
28 "*.color3" = "#e69650";
29 "*.color11" = "#ffa74f";
30
31 # blue
32 "*.color4" = "#759fbf";
33 "*.color12" = "#98b8d9";
34
35 # magenta
36 "*.color5" = "#9b79a6";
37 "*.color13" = "#ceadd9";
38
39 # cyan
40 "*.color6" = "#79a69b";
41 "*.color14" = "#a3d9ce";
42
43 # white
44 "*.color7" = "#d9d9d9";
45 "*.color15" = "#ffffff";
46} \ No newline at end of file
diff --git a/accounts/gkleen@sif/zshrc b/accounts/gkleen@sif/zshrc
new file mode 100644
index 00000000..d4d75073
--- /dev/null
+++ b/accounts/gkleen@sif/zshrc
@@ -0,0 +1,410 @@
1filebin() {
2 basePath=/srv/www/files
3 ssh ymir find /srv/www/files -type f -printf "$'%T@ %TY-%Tm-%TdT%TH:%TM %P\\\\0'" | sort -zn | cut -z -d ' ' -f 2- \
4 | while IFS= read -r -d $'\0' l; do
5 IFS=' ' read -r t p <<<"${l}"
6 printf "%s https://f.141.li/%s\n" "${t}" "${p}"
7 done
8}
9
10push2bin() {
11 if [[ ${#@} -eq 1 && ! -r ${1} ]]; then
12 uux -p 'ymir!push2bin' $(echo -n "${1:t}" | tr -c $'[:alnum:]+-=.' '_')
13 else
14 for f (${@}); do
15 uux -p 'ymir!push2bin' $(echo -n "${f:t}" | tr -c $'[:alnum:]+-=.' '_') <${f}
16 done
17 fi
18}
19
20genmail() {
21 local baseName=""
22 local target=""
23 if [[ ${#@} -ge 1 ]]; then
24 target=${1}
25 shift
26 fi
27
28 if [[ ${#@} -ge 1 ]]; then
29 baseName=$(pwgen ${@})
30 else
31 baseName=$(pwgen -v -A -0 16 1)
32 fi
33 baseName=$(tr -cd $'[:alnum:]!#$%&*+-/=?^_{|}~.' <<<${baseName})
34 address=${baseName}@141.li
35 insertAddr() {
36 echo "${baseName} gkleen" | ssh ymir tee -a /srv/mail/spm 1>/dev/null \
37 }
38
39 printf "%s\n" ${address}
40 read -q 'cont?Continue [y/N]? ' || return
41
42 insertAddr
43}
44
45s() {
46 dir=$(pwd)
47 [[ ${#@} -ge 1 ]] && dir=$1
48
49 shellFile=$(findNix ${@})
50 [[ ${#@} -ge 1 ]] && shift
51
52 typeset -a cmd
53 if [[ -d ${dir}/.nix-gc-roots ]]; then
54 cmd=(persistent-nix-shell ${shellFile} ${S_EXTRA_ARGS} ${@})
55 else
56 cmd=(nix-shell ${shellFile} ${S_EXTRA_ARGS} ${@})
57 fi
58
59 if [[ -n "${S_SYSTEMD}" ]]; then
60 systemd-run --user --slice=development.slice --collect -E PATH=${PATH} -p WorkingDirectory=${dir} -- ${cmd}
61 else
62 (
63 cd ${dir}
64
65 exec ${cmd}
66 )
67 fi
68}
69
70sz() {
71 typeset -a S_EXTRA_ARGS
72 S_EXTRA_ARGS=(--run "env __ETC_ZSHENV_SOURCED=1 zsh") s ${@}
73}
74st() {
75 typeset -a S_EXTRA_ARGS
76 S_EXTRA_ARGS=(--run "tmux new-session env __ETC_ZSHENV_SOURCED=1 zsh") s ${@}
77}
78stt() {
79 typeset -a S_EXTRA_ARGS
80 S_SYSTEMD=true S_EXTRA_ARGS=(--run "urxvt -e tmux -S .tmux.sock new-session env __ETC_ZSHENV_SOURCED=1 zsh") s ${@}
81}
82se() {
83 typeset -a S_EXTRA_ARGS
84 S_SYSTEMD=true S_EXTRA_ARGS=(--run "emacs") s ${@}
85}
86
87findNix() {
88 if [[ $#@ -eq 0 ]]; then
89 findNix $(pwd)
90 elif [[ -f "$1" ]]; then
91 print ${1:a}
92 elif [[ -d "$1" && -f "$1"/shell.nix ]]; then
93 print ${1:a}/shell.nix
94 elif [[ -d "$1" && -f "$1"/default.nix ]]; then
95 print ${1:a}/default.nix
96 elif [[ -d "$1" && "$1" != "/" ]]; then
97 findNix ${1:h}
98 else
99 printf "Traversed directories to ‘/’ and found no shell specification\n" >&2
100 return 1
101 fi
102}
103
104dir() {
105 curlArchive=false
106 templateArchive=""
107 repoUrl=""
108 nixShell=""
109 findNix=false
110 dir=""
111 forceShell=false
112 wormhole=false
113 gitWorktree=""
114 notmuchMsg=""
115 quickserve=false
116
117 while getopts ':t:a:s:Sd:ir:wqg:n:' arg; do
118 case $arg in
119 "t") ;;
120 "a")
121 if [[ ${OPTARG} =~ "://" ]]; then
122 templateArchive=${OPTARG}
123 curlArchive=true
124 else
125 templateArchive=${OPTARG:a}
126 fi
127 ;;
128 "s") nixShell=${OPTARG:a} ;;
129 "S") findNix=true ;;
130 "d") dir=${OPTARG} ;;
131 "i") forceShell=true ;;
132 "r") repoUrl=${OPTARG} ;;
133 "w") wormhole=true ;;
134 "g") gitWorktree=${OPTARG} ;;
135 "n") notmuchMsg=${OPTARG} ;;
136 "q") quickserve=true ;;
137 *) printf "Invalid option: %s\n" $arg >&2; exit 2 ;;
138 esac
139 done
140
141 shift $((OPTIND - 1))
142
143 if [[ -z ${dir} && ${#@} -ge 1 ]]; then
144 dir=${1}
145 shift
146 fi
147
148 [[ -n ${dir} ]] || return 2;
149
150 if [[ ! -e ${dir} ]]; then
151 if [[ -z "${gitWorktree}" ]]; then
152 mkdir -vp ${dir}
153 else
154 git -C ${gitWorktree} worktree add ${dir}
155 fi
156 else
157 gitWorktree=""
158 fi
159
160 (
161 cd ${dir}
162 export dir;
163
164 ${findNix} && { nixShell=$(findNix) || return $? }
165
166 [[ -n ${repoUrl} ]] && git clone -- ${repoUrl} .
167
168 if [[ -n ${templateArchive} ]]; then
169 (
170 archiveFile=""
171 cleanup() {
172 [[ -n "${archiveFile}" ]] && rm -fv ${archiveFile}
173 }
174 trap cleanup EXIT
175
176 if ${curlArchive}; then
177 archiveFile=$(mktemp -t "archive.XXXXXXXXXX.${templateArchive:t}")
178
179 curl -L -o ${archiveFile} ${templateArchive}
180
181 templateArchive=${archiveFile}
182 fi
183
184 case $(file --brief --mime-type ${templateArchive}) in
185 application/zip) unzip ${templateArchive} ;;
186 *) tar -xvaf ${templateArchive} ;;
187 esac
188 )
189 fi
190
191
192 if [[ -n ${notmuchMsg} ]]; then
193 getMimeTypes() {
194 nix-shell -p mailcap --run "find \${buildInputs} -path '*/etc/mime.types' | head -n 1 | xargs -- cat"
195 }
196
197 typeset -a messages
198 messages=(${(z)$(notmuch search --output=messages ${notmuchMsg})})
199
200 for message (${messages}); do
201 typeset -A notmuchAtts
202 notmuchAtts=()
203
204 while IFS= read -r -d $'\n' line; do
205 [[ ${line} =~ '(attachment|part)\{ ID: ([0-9]+)' ]] || continue
206 attId=${match[2]}
207
208 [[ ${line} =~ 'Content-type: multipart/' ]] && continue
209
210 fName="part_${attId}"
211 [[ ${line} =~ 'Filename: (([^,]|,[^ ])+)' ]] && fName=${match[1]}
212
213 if [[ ${#messages} -gt 1 ]]; then
214 fName="${message}/${fName}"
215 fi
216
217 fExt="${fName:e}"
218 [[ -n "${fExt}" ]] && fName="${fName:r}"
219
220 if [[ -z "${fExt}" && ${line} =~ 'Content-type: (([^,]|,[^ ])+)$' ]]; then
221 fExt=$(getMimeTypes | grep ${match[1]}$'\t' | head -n 1 | awk '{ print $2; }')
222 fi
223
224 mkdir -p ${fName:h}
225 if [[ -n "${fExt}" ]]; then
226 fName=$(mktemp -p . "${fName}.XXXXXX.${fExt}")
227 else
228 fName=$(mktemp -p . "${fName}.XXXXXX")
229 fi
230
231 notmuchAtts[${attId}]=${fName}
232 done <<(notmuch show --decrypt=false -- ${message} | tr -d $'\f')
233
234 for attId fName in ${(kv)notmuchAtts}; do
235 [[ -d ${fName:h} ]] || mkdir -p ${fName:h}
236 printf "#%d → ‘%s’\n" "${attId}" "${fName}" >&2
237
238 notmuch show --decrypt=false --part=${attId} -- ${message} | pv -W -D 2 -i 0.1 >${fName}
239 done
240 done
241 fi
242
243
244 ${wormhole} && wormhole receive
245
246 if ${quickserve}; then
247 quickserve --root . --upload . --show-hidden --tar gz
248 fi
249
250
251 if [[ ${#@} -eq 0 ]] || ${forceShell}; then
252 if [[ ${#@} -gt 0 ]]; then
253 if [[ -z ${nixShell} ]]; then
254 ${@}
255 else
256 nix-shell ${nixShell} --run "${@}"
257 fi
258 fi
259
260 cd $(pwd) # Needed for mounting to work
261
262 isSingleDir() {
263 typeset -a contents
264 contents=(*(N) .*(N))
265
266 if [[ ${#contents} -eq 1 && -d ${contents[1]} ]]; then
267 print ${contents[1]}
268 return 0
269 else
270 return 1
271 fi
272 }
273 while d=$(isSingleDir); do cd ${d}; done
274
275
276 if [[ -z ${nixShell} ]]; then
277 exec -- zsh
278 else
279 exec -- nix-shell ${nixShell} --run zsh
280 fi
281 else
282 if [[ -z ${nixShell} ]]; then
283 exec -- ${@}
284 else
285 exec -- nix-shell ${nixShell} --run "${@}"
286 fi
287 fi
288 )
289}
290
291tmpdir() {
292 cleanup()
293 {
294 cd /
295 unmount() {
296 printf "Unmounting %s\n" ${1} >&2
297 fusermount -u ${1} || umount ${1} || sudo umount ${1}
298 }
299
300 if mountpoint -q -- ${dir}; then
301 unmount ${dir} || return $?
302 else
303 while read -d $'\0' subDir; do
304 mountpoint -q -- ${subDir} || continue
305 unmount ${subDir} || return $?
306 done <<<$(find ${dir} -xdev -type d -print0 | sort -zr)
307 fi
308
309 rm -rfv --one-file-system -- ${dir}
310 }
311
312 local tmpdir=""
313
314 while getopts ':t:a:s:Sd:ir:wqg:n:' arg; do
315 case $arg in
316 "t") tmpdir="=${OPTARG}" ;;
317 "?"|":") printf "Invalid option: %s\n" $arg >&2; exit 2 ;;
318 esac
319 done
320
321 (
322 trap cleanup EXIT
323
324
325 local dir=$(mktemp -ud --tmpdir${tmpdir} ${0}.XXXXXXXXXX || return $?)
326
327 dir -d ${dir} ${@}
328 )
329}
330
331inhibit-sleep() {
332 if systemctl --user is-active prevent-suspend.service 1>/dev/null; then
333 echo "Allowing suspend"
334 systemctl --user stop prevent-suspend.service
335 else
336 echo "Inhibiting suspend"
337 systemctl --user start prevent-suspend.service
338 fi
339}
340
341qr() {
342 qrencode -l M -o - -t ANSIUTF8 $@
343}
344
345clock() {
346 tty-clock -sSbc -C 7 -d 0 -a 100000000
347}
348
349public-ip() {
350 curl -s -H 'Accept: application/json' $@ ifconfig.co | jq -r '.ip'
351}
352
353nix-ghci() {
354 pkgExpr=""
355 if [[ ${#@} -gt 0 ]]; then
356 pkgExpr="${1}"
357 shift
358 fi
359
360 nix-shell -p "with (import <nixpkgs> {}); pkgs.haskellPackages.ghcWithPackages (p: with p; [${pkgExpr}])" --run "ghci ${@}"
361}
362
363swap() {
364 f1=${1}
365 f2=${2}
366
367 if [[ -z "${f1}" || ! -e "${f1}" ]]; then
368 printf "‘%s’ does not exist\n" "${f1}" >&2
369 return 2
370 fi
371 if [[ -z "${f2}" || ! -e "${f2}" ]]; then
372 printf "‘%s’ does not exist\n" "${f2}" >&2
373 return 2
374 fi
375
376 tmpfile=$(mktemp --dry-run --tmpdir=${f1:h} .swap.XXXXXXXXXX)
377 mv -v ${f1} ${tmpfile}
378 mv -v ${f2} ${f1}
379 mv -v ${tmpfile} ${f2}
380}
381
382l() {
383 exa --binary --git --time-style=iso --long --all --header --group-directories-first --colour=always $@ | less --mouse -FR
384}
385
386re() {
387 systemctl --restart $@
388}
389
390ure() {
391 systemctl --user --restart $@
392}
393
394u2wdb() {
395 ssh -t postgres@uniworxdb2 psql uni2work
396}
397
398alias '..'='cd ..'
399alias -g L='| less'
400alias -g S='&> /dev/null'
401alias -g G='| grep'
402alias -g B='&> /dev/null &'
403alias -g BB='&> /dev/null &!'
404
405export DEFAULT_USER=gkleen
406export EDITOR=emacsclient
407
408bindkey -e
409bindkey ';5C' emacs-forward-word
410bindkey ';5D' emacs-backward-word \ No newline at end of file
diff --git a/accounts/root@sif.nix b/accounts/root@sif.nix
new file mode 100644
index 00000000..979463ba
--- /dev/null
+++ b/accounts/root@sif.nix
@@ -0,0 +1,18 @@
1{ userName, ... }:
2{
3 home-manager.users.${userName} = {
4 programs.ssh.matchBlocks = {
5 "git.yggdrasil.li" = {
6 user = "gitolite";
7 identityFile = "~/.ssh/sysconf";
8 };
9 "borg.munin" = {
10 hostname = "u120515.your-storagebox.de";
11 user = "u120515";
12 identityFile = "~/.ssh/borg.munin";
13 identitiesOnly = true;
14 port = 23;
15 };
16 };
17 };
18}
diff --git a/flake.lock b/flake.lock
index 2a0a02da..d5e943cd 100644
--- a/flake.lock
+++ b/flake.lock
@@ -7,11 +7,11 @@
7 ] 7 ]
8 }, 8 },
9 "locked": { 9 "locked": {
10 "lastModified": 1618469593, 10 "lastModified": 1618041827,
11 "narHash": "sha256-fNdt+Q3irnT3pId7PKSSVeR8/9inBrAEg4gpItoRowU=", 11 "narHash": "sha256-17Fzc8rmT6HOarCp+8k9RhmgMorNtzTk/rzew+FqHrA=",
12 "owner": "nix-community", 12 "owner": "nix-community",
13 "repo": "home-manager", 13 "repo": "home-manager",
14 "rev": "ebbbd4f2b50703409543941e7445138dc1e7392e", 14 "rev": "f567ea8228e0ce718871d7346e444dd15ad702e5",
15 "type": "github" 15 "type": "github"
16 }, 16 },
17 "original": { 17 "original": {
@@ -23,11 +23,11 @@
23 }, 23 },
24 "nixpkgs": { 24 "nixpkgs": {
25 "locked": { 25 "locked": {
26 "lastModified": 1618681372, 26 "lastModified": 1618153411,
27 "narHash": "sha256-fjiabnBl20D/jc3bU3K16jP21bf5l2fG1rHij0LoHVk=", 27 "narHash": "sha256-xevpO/rTac+Fcf/8KmGGAxdoMN9WoosZTo9tAM8rHKY=",
28 "owner": "NixOS", 28 "owner": "NixOS",
29 "repo": "nixpkgs", 29 "repo": "nixpkgs",
30 "rev": "c27aea5e879748c79acf3b6681194fcf184e3eba", 30 "rev": "20443c348225791ca7ecb2787f403f4be56ab223",
31 "type": "github" 31 "type": "github"
32 }, 32 },
33 "original": { 33 "original": {
@@ -51,11 +51,11 @@
51 ] 51 ]
52 }, 52 },
53 "locked": { 53 "locked": {
54 "lastModified": 1618226608, 54 "lastModified": 1617608551,
55 "narHash": "sha256-Hq/lcu48RhE3U6gYYkuT7v6hLHeu1PrasiaBiGkCX+w=", 55 "narHash": "sha256-5KMomBp38ujNcz5NBmVaQSpi7k29cc+b+tBPmjGoEJw=",
56 "owner": "Mic92", 56 "owner": "Mic92",
57 "repo": "sops-nix", 57 "repo": "sops-nix",
58 "rev": "ade2f5c17184df9d4c42d36da18a6ed903b4c02d", 58 "rev": "5e0ea90c782d6cfae13cae0af131a687e44717e9",
59 "type": "github" 59 "type": "github"
60 }, 60 },
61 "original": { 61 "original": {
diff --git a/hosts/sif/default.nix b/hosts/sif/default.nix
new file mode 100644
index 00000000..daa37ad9
--- /dev/null
+++ b/hosts/sif/default.nix
@@ -0,0 +1,330 @@
1{ flake, pkgs, customUtils, lib, config, path, ... }:
2{
3 imports = with flake.nixosModules.systemProfiles; [
4 ./hw.nix
5 ./mail
6 initrd-all-crypto-modules default-locale openssh
7 ];
8
9 config = {
10 nixpkgs = {
11 system = "x86_64-linux";
12 config = {
13 allowUnfree = true;
14 };
15 };
16
17 boot = {
18 initrd = {
19 luks.devices = {
20 nvm0.device = "/dev/disk/by-uuid/fe641e81-0812-4181-a5f6-382ebba509bb";
21 nvm1.device = "/dev/disk/by-uuid/43df1ba8-1728-4193-8855-920a82d4494a";
22 };
23 availableKernelModules = [ "drbg" "nvme" "xhci_pci" "usb_storage" "sd_mod" "rtsx_pci_sdmmc" ];
24 kernelModules = [ "dm-raid" "dm-integrity" "dm-snapshot" "dm-thin-pool" ];
25 };
26
27 blacklistedKernelModules = [ "nouveau" ];
28
29 # Use the systemd-boot EFI boot loader.
30 loader = {
31 systemd-boot.enable = true;
32 efi.canTouchEfiVariables = true;
33 timeout = null;
34 };
35
36 plymouth.enable = true;
37
38 kernelPackages = pkgs.linuxPackages_latest;
39 kernelParams = [ "i915.fastboot=1" "intel_pstate=no_hwp" "acpi_backlight=vendor" "thinkpad-acpi.brightness_enable=1" "quiet" ];
40 extraModulePackages = with config.boot.kernelPackages; [ v4l2loopback ];
41 kernelModules = ["v4l2loopback"];
42
43 tmpOnTmpfs = true;
44 };
45
46 networking = {
47 domain = "midgard.yggdrasil";
48 hosts = {
49 "127.0.0.1" = [ "sif.midgard.yggdrasil" "sif" ];
50 "::1" = [ "sif.midgard.yggdrasil" "sif" ];
51 };
52
53 firewall = {
54 enable = true;
55 allowedTCPPorts = [ 22 # ssh
56 8000 # quickserve
57 ];
58 allowedUDPPorts = [ 8554 # gopro webcam
59 ];
60 };
61
62 networkmanager = {
63 enable = true;
64 dhcp = "internal";
65 dns = "dnsmasq";
66 extraConfig = ''
67 [connectivity]
68 uri=https://online.yggdrasil.li
69 '';
70 };
71
72 dhcpcd.enable = false;
73
74 interfaces.yggdrasil = {
75 virtual = true;
76 virtualType = config.services.tinc.networks.yggdrasil.interfaceType;
77 macAddress = "5c:93:21:c3:61:39";
78 };
79 };
80
81 environment.etc."NetworkManager/dnsmasq.d/libvirtd_dnsmasq.conf" = {
82 text = ''
83 server=/sif.libvirt/192.168.122.1
84 '';
85 };
86
87 powerManagement = {
88 enable = true;
89
90 cpuFreqGovernor = "schedutil";
91 };
92
93 environment.systemPackages = with pkgs; [
94 nvtop brightnessctl config.boot.kernelPackages.v4l2loopback s-tui
95 ];
96
97 services = {
98 tinc.yggdrasil.enable = true;
99
100 uucp = {
101 enable = true;
102 nodeName = "sif";
103 remoteNodes = {
104 "ymir" = {
105 publicKeys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG6KNtsCOl5fsZ4rV7udTulGMphJweLBoKapzerWNoLY root@ymir"];
106 hostnames = ["ymir.yggdrasil.li" "ymir.niflheim.yggdrasil"];
107 };
108 };
109
110 defaultCommands = lib.mkForce [];
111 };
112
113 avahi.enable = true;
114
115 fwupd.enable = true;
116
117 fprintd.enable = true;
118
119 blueman.enable = true;
120
121 colord.enable = true;
122
123 vnstat.enable = true;
124
125 logind = {
126 lidSwitch = "suspend";
127 lidSwitchDocked = "lock";
128 lidSwitchExternalPower = "lock";
129 };
130
131 atd = {
132 enable = true;
133 allowEveryone = true;
134 };
135
136 xserver = {
137 enable = true;
138
139 layout = "us";
140 xkbVariant = "dvp";
141 xkbOptions = "compose:caps";
142
143 displayManager.lightdm = {
144 enable = true;
145 greeters.gtk = {
146 clock-format = "%H:%M %a %b %_d";
147 indicators = ["~host" "~spacer" "~clock" "~session" "~power"];
148 theme = {
149 package = pkgs.equilux-theme;
150 name = "Equilux-compact";
151 };
152 iconTheme = {
153 package = pkgs.paper-icon-theme;
154 name = "Paper";
155 };
156 extraConfig = ''
157 background = #000000
158 user-background = false
159 active-monitor = #cursor
160 hide-user-image = true
161
162 [monitor: DP-2]
163 laptop = true
164 '';
165 };
166 };
167
168 displayManager.setupCommands = ''
169 ${pkgs.xorg.xinput}/bin/xinput disable 'SynPS/2 Synaptics TouchPad'
170 '';
171
172 desktopManager.xterm.enable = true;
173 windowManager.twm.enable = true;
174 displayManager.defaultSession = "xterm+twm";
175
176 wacom.enable = true;
177 libinput.enable = true;
178
179 dpi = 282;
180
181 videoDrivers = [ "nvidia" ];
182
183 screenSection = ''
184 Option "metamodes" "nvidia-auto-select +0+0 { ForceCompositionPipeline = On }"
185 '';
186
187 deviceSection = ''
188 Option "AccelMethod" "SNA"
189 Option "TearFree" "True"
190 '';
191
192 exportConfiguration = true;
193 };
194 };
195
196 users = {
197 users.gkleen.extraGroups = [ "media" ];
198 groups.media = {};
199 };
200
201 hardware = {
202 pulseaudio = {
203 enable = true;
204 package = with pkgs; pulseaudioFull;
205 support32Bit = true;
206 };
207
208 bluetooth = {
209 enable = true;
210 settings = {
211 General = {
212 Enable = "Source,Sink,Media,Socket";
213 };
214 };
215 };
216
217 trackpoint = {
218 enable = true;
219 emulateWheel = true;
220 sensitivity = 255;
221 speed = 255;
222 };
223
224 nvidia = {
225 modesetting.enable = true;
226 prime = {
227 nvidiaBusId = "PCI:1:0:0";
228 intelBusId = "PCI:0:2:0";
229 sync.enable = true;
230 };
231 };
232
233 opengl = {
234 enable = true;
235 driSupport32Bit = true;
236 setLdLibraryPath = true;
237 };
238
239 firmware = [ pkgs.firmwareLinuxNonfree ];
240 };
241
242 sound.enable = true;
243
244 nix = {
245 autoOptimiseStore = true;
246 daemonNiceLevel = 10;
247 daemonIONiceLevel = 3;
248 };
249
250 environment.etc."X11/xorg.conf.d/50-wacom.conf".source = lib.mkForce ./wacom.conf;
251
252 systemd.services."ac-plugged" = {
253 description = "Inhibit handling of lid-switch and sleep";
254
255 path = with pkgs; [ systemd coreutils ];
256
257 script = ''
258 exec systemd-inhibit --what=handle-lid-switch --why="AC is connected" --mode=block sleep infinity
259 '';
260
261 serviceConfig = {
262 Type = "simple";
263 };
264 };
265
266 services.udev.extraRules = with pkgs; ''
267 SUBSYSTEM=="power_supply", ENV{POWER_SUPPLY_ONLINE}=="0", RUN+="${systemd}/bin/systemctl --no-block stop ac-plugged.service"
268 SUBSYSTEM=="power_supply", ENV{POWER_SUPPLY_ONLINE}=="1", RUN+="${systemd}/bin/systemctl --no-block start ac-plugged.service"
269 '';
270
271 services.borgbackup = {
272 snapshots = "btrfs";
273 prefix = "yggdrasil.midgard.sif.";
274 targets = {
275 "munin" = {
276 repo = "borg.munin:borg";
277 paths = [ "/home/gkleen" ];
278 prune = {
279 "home" =
280 [ "--keep-within" "24H"
281 "--keep-daily" "31"
282 "--keep-monthly" "12"
283 "--keep-yearly" "-1"
284 ];
285 };
286 keyFile = "/run/secrets/borg-repokey--borg_munin__borg";
287 };
288 };
289 };
290 sops.secrets.borg-repokey--borg_munin__borg = {
291 sopsFile = /. + path + "/modules/borgbackup/repokeys/borg_munin__borg.yaml";
292 key = "key";
293 };
294
295 services.btrfs.autoScrub = {
296 enable = true;
297 fileSystems = [ "/" "/home" ];
298 interval = "weekly";
299 };
300
301 systemd.services."nix-daemon".serviceConfig = {
302 MemoryAccounting = true;
303 MemoryHigh = "50%";
304 MemoryMax = "75%";
305 };
306
307 services.journald.extraConfig = ''
308 SystemMaxUse=100M
309 '';
310
311 services.dbus.packages = with pkgs;
312 [ dbus gnome3.dconf
313 ];
314
315 programs = {
316 light.enable = true;
317 wireshark.enable = true;
318 };
319
320 virtualisation.libvirtd = {
321 enable = true;
322 };
323
324 zramSwap.enable = true;
325
326 services.pcscd.enable = true;
327
328 system.stateVersion = "20.03";
329 };
330}
diff --git a/hosts/sif/hw.nix b/hosts/sif/hw.nix
new file mode 100644
index 00000000..92afb7c9
--- /dev/null
+++ b/hosts/sif/hw.nix
@@ -0,0 +1,35 @@
1{ config, lib, pkgs, ... }:
2
3{
4 fileSystems."/" =
5 { device = "/dev/disk/by-uuid/f094bf06-66f9-40a8-9ab2-2b54d05223d2";
6 fsType = "btrfs";
7 };
8
9 fileSystems."/boot" =
10 { device = "/dev/disk/by-uuid/B3A2-D029";
11 fsType = "vfat";
12 };
13
14 fileSystems."/home" =
15 { device = "/dev/disk/by-uuid/9e932072-3c56-4a9c-8da7-3163d2a8bf28";
16 fsType = "btrfs";
17 };
18
19 fileSystems."/var/media" =
20 { device = "/dev/disk/by-uuid/437eca70-d017-4d52-a1fa-2f4c7a87f096";
21 fsType = "btrfs";
22 };
23
24 swapDevices =
25 [ { device = "/dev/disk/by-uuid/50f3f856-cc17-4614-846a-34a14d5006ec"; }
26 ];
27
28 nix.maxJobs = 12;
29 # High-DPI console
30 console.font = "${pkgs.terminus_font}/share/consolefonts/ter-u28n.psf.gz";
31
32 hardware.cpu.intel.updateMicrocode = true;
33
34 hardware.enableRedistributableFirmware = true;
35}
diff --git a/hosts/sif/mail/default.nix b/hosts/sif/mail/default.nix
new file mode 100644
index 00000000..29bfb4f1
--- /dev/null
+++ b/hosts/sif/mail/default.nix
@@ -0,0 +1,66 @@
1{ config, pkgs, ... }:
2{
3 services.postfix = {
4 enable = true;
5 enableSmtp = true;
6 enableSubmission = false;
7 setSendmail = true;
8 networksStyle = "host";
9 hostname = "sif.midgard.yggdrasil";
10 destination = [];
11 relayHost = "uucp:ymir";
12 recipientDelimiter = "+";
13 masterConfig = {
14 uucp = {
15 type = "unix";
16 private = true;
17 privileged = true;
18 chroot = false;
19 command = "pipe";
20 args = [ "flags=Fqhu" "user=uucp" ''argv=${config.security.wrapperDir}/uux -z -a $sender - $nexthop!rmail ($recipient)'' ];
21 };
22 };
23 transport = ''
24 odin.asgard.yggdrasil uucp:odin
25 '';
26 config = {
27 always_bcc = "gkleen+sent@odin.asgard.yggdrasil";
28
29 default_transport = "uucp:ymir";
30
31 inet_interfaces = "loopback-only";
32
33 authorized_submit_users = ["!uucp" "static:anyone"];
34 message_size_limit = "0";
35
36 sender_dependent_default_transport_maps = ''regexp:${pkgs.writeText "sender_relay" ''
37 /@(cip|stud)\.ifi\.(lmu|uni-muenchen)\.de$/ smtp:smtp.ifi.lmu.de
38 /@ifi\.(lmu|uni-muenchen)\.de$/ smtp:smtpin1.ifi.lmu.de:587
39 /@(campus\.)?lmu\.de$/ smtp:postout.lrz.de
40 ''}'';
41 sender_bcc_maps = ''regexp:${pkgs.writeText "sender_bcc" ''
42 /^uni2work(-[^@]*)?@ifi\.lmu\.de$/ uni2work@ifi.lmu.de
43 /@ifi\.lmu\.de$/ gregor.kleen@ifi.lmu.de
44 ''}'';
45
46 smtp_sasl_auth_enable = true;
47 smtp_sender_dependent_authentication = true;
48 smtp_sasl_tls_security_options = "noanonymous";
49 smtp_sasl_mechanism_filter = ["plain"];
50 smtp_sasl_password_maps = "regexp:/var/db/postfix/sasl_passwd";
51 smtp_cname_overrides_servername = false;
52 smtp_always_send_ehlo = true;
53 smtp_tls_security_level = "dane";
54
55 smtp_tls_loglevel = "1";
56 smtp_dns_support_level = "dnssec";
57 };
58 };
59
60 sops.secrets.postfix-sasl-passwd = {
61 key = "sasl-passwd";
62 path = "/var/db/postfix/sasl_passwd";
63 owner = "postfix";
64 sopsFile = ./secrets.yaml;
65 };
66}
diff --git a/hosts/sif/mail/secrets.yaml b/hosts/sif/mail/secrets.yaml
new file mode 100644
index 00000000..06a2ad40
--- /dev/null
+++ b/hosts/sif/mail/secrets.yaml
@@ -0,0 +1,33 @@
1sasl-passwd: ENC[AES256_GCM,data:S81uICROGm/E0TC3xJyPXbVLjOO+PsRyJBoWINFZGzeh8F0nXx1ewiiSXtNl9trTbxlSgf5jnBvtbyd75N0OcyqBf0db5tJtvU42DO5I4qFo4R67FzpKzKWMF4AJuFGP1aKkPsPIc41WTfLemKCfbEhVfQj9qEFLR9TC8iqzSZa0bztCuLoKi0vrAO/4JZnzUe3n7FXy+ER6oYK9JoKwaXc9KYdwQC3QYCby2iSq+GvRs7FL4x6/Zr8FzVCXHYMaW/Qg9dCn/g2NnEnOsH0pEASuKRPJKh8x5dtQg9v3jRK6NIDjEkXeuBnSOaeQiAcYc784foIlI7Q=,iv:zCsYZtU51zJR9XqaCvMtc5aGZwSccIrPzhznubEoEjo=,tag:0/v4Cp/0xLrfEX7H953bOA==,type:str]
2sops:
3 kms: []
4 gcp_kms: []
5 azure_kv: []
6 hc_vault: []
7 lastmodified: '2021-01-18T09:46:15Z'
8 mac: ENC[AES256_GCM,data:Idvsviv6CGibT+s7TSYUNmYO6gELqahJq33+k8YQhhwDKC6+s3Wqjq3xDkVjPcgq32GQolzmv20s93vQSHVuTKcH9jpXmIlwVZmZFFV7ejuA3QScOqqNNynh1m1ba/eZCGgIZiSlRuv7wqs7wz2uHN9eY3prsDkG1vxpc7UC18g=,iv:S9S/N3vW2TXcNYsc/w+3pDJT+BOQaAw8vgqYwRUtbU4=,tag:jPRXDzy29ewkq/Nzcayfnw==,type:str]
9 pgp:
10 - created_at: '2021-01-02T19:29:14Z'
11 enc: |
12 -----BEGIN PGP MESSAGE-----
13
14 hF4Dgwm4NZSaLAcSAQdAE/883Tbc7WXuzOxjm5jVrOSbnYe+BEg75ijtZP2L3UMw
15 4mhqzy576jEQLPGrnMpX2zA2MwFAwGnMwC98sQ4vVTp/xgNQ0VHHNM4GnTi6VoUb
16 0l4BLgQrT6p2ul69ADecadWJsGm6roqMHrpNGZeeczDLOBIzrrwN4sL92jQiEPw9
17 Ih+EXJpJ1K4NouU1VRsfQPqJ6y+i295TnEgunlJeYc/MNQgBT4ABiPZgUZXnkhxl
18 =7rOv
19 -----END PGP MESSAGE-----
20 fp: F1AF20B9511B63F681A14E8D51AEFBCD1DEF68F8
21 - created_at: '2021-01-02T19:29:14Z'
22 enc: |
23 -----BEGIN PGP MESSAGE-----
24
25 hF4DXxoViZlp6dISAQdAGifJ6qk40VdF/WKaYa9v97PdSVkPvHZt+j0G8+ZDJSEw
26 8XC1622ElTWRCZ2bjUwMF77DMgMy3rEr8B7Bj6MnEzDd/Af63Np1cO+7juybxqhz
27 0l4BO6uZ+gCvKg45jWX0GE6ZBkoUTvh24djTngHFyIHDnpCxSB6s+jcYR9otco2F
28 ++E2pcoQR4GuOeyYa/8UsW+RzKWpCfskYbSIt4gAXyCt8ua1y5Rw0DEVdw91uJNC
29 =E/qh
30 -----END PGP MESSAGE-----
31 fp: 30D3453B8CD02FE2A3E7C78C0FB536FB87AE8F51
32 unencrypted_suffix: _unencrypted
33 version: 3.6.1
diff --git a/hosts/sif/wacom.conf b/hosts/sif/wacom.conf
new file mode 100644
index 00000000..864409f1
--- /dev/null
+++ b/hosts/sif/wacom.conf
@@ -0,0 +1,15 @@
1Section "InputClass"
2 Identifier "Wacom USB device class"
3 MatchUSBID "056a:*"
4 MatchDevicePath "/dev/input/event*"
5 Driver "wacom"
6EndSection
7
8Section "InputClass"
9 Identifier "calibration"
10 MatchProduct "Wacom USB device class"
11 Option "MinX" "58"
12 Option "MaxX" "30982"
13 Option "MinY" "87"
14 Option "MaxY" "17328"
15EndSection \ No newline at end of file
diff --git a/modules/borgbackup/btrfs-snapshots.nix b/modules/borgbackup/btrfs-snapshots.nix
new file mode 100644
index 00000000..96d2b2ba
--- /dev/null
+++ b/modules/borgbackup/btrfs-snapshots.nix
@@ -0,0 +1,52 @@
1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.btrfs-snapshots;
7
8 snapshotMount = str: "${str}${cfg.mountSuffix}";
9in {
10 options = {
11
12 services.btrfs-snapshots = {
13 enable = mkEnableOption "a systemd unit for btrfs snapshots";
14
15 mountSuffix = mkOption {
16 type = types.str;
17 default = ".snapshot";
18 };
19
20 readOnly = mkOption {
21 type = types.bool;
22 default = true;
23 };
24
25 persist = mkOption {
26 type = types.bool;
27 default = false;
28 };
29 };
30
31 };
32
33
34 config = mkIf cfg.enable {
35 systemd.services."btrfs-snapshot@" = {
36 enable = true;
37
38 unitConfig = {
39 StopWhenUnneeded = !cfg.persist;
40 };
41
42 serviceConfig = with pkgs; {
43 Type = "oneshot";
44 ExecStartPre = "-${btrfs-progs}/bin/btrfs subvolume delete -c ${snapshotMount "%f"}";
45 ExecStart = "${btrfs-progs}/bin/btrfs subvolume snapshot ${optionalString cfg.readOnly "-r"} %f ${snapshotMount "%f"}";
46 RemainAfterExit = true;
47 ExecStop = "${btrfs-progs}/bin/btrfs subvolume delete -c ${snapshotMount "%f"}";
48 };
49 };
50
51 };
52}
diff --git a/modules/borgbackup/default.nix b/modules/borgbackup/default.nix
new file mode 100644
index 00000000..a0419d0e
--- /dev/null
+++ b/modules/borgbackup/default.nix
@@ -0,0 +1,206 @@
1{ config, lib, utils, pkgs, ... }:
2
3with utils;
4with lib;
5
6let
7 cfg = config.services.borgbackup;
8
9 lvmPath = {
10 options = {
11 LV = mkOption {
12 type = types.str;
13 };
14 VG = mkOption {
15 type = types.str;
16 };
17 };
18 };
19
20 pathType = if cfg.snapshots == "lvm" then types.submodule lvmPath else types.path;
21
22 systemdPath = path: escapeSystemdPath (if cfg.snapshots == "lvm" then "${path.VG}-${path.LV}" else path);
23
24 withSuffix = path: path + (if cfg.snapshots == "btrfs" then config.services.btrfs-snapshots.mountSuffix else config.services.lvm-snapshots.mountSuffix);
25
26 mountPoint = if cfg.snapshots == "lvm" then config.services.lvm-snapshots.mountPoint else "";
27
28 targetOptions = {
29 options = {
30 repo = mkOption {
31 type = types.str;
32 };
33
34 paths = mkOption {
35 type = types.listOf pathType;
36 default = [];
37 };
38
39 prune = mkOption {
40 type = types.attrsOf (types.listOf types.str);
41 default = {};
42 };
43
44 interval = mkOption {
45 type = types.str;
46 default = "6h";
47 };
48
49 jitter = mkOption {
50 type = with types; nullOr str;
51 default = "6h";
52 };
53
54 lock = mkOption {
55 type = types.nullOr types.str;
56 default = "backup";
57 };
58
59 network = mkOption {
60 type = types.bool;
61 default = true;
62 };
63
64 lockWait = mkOption {
65 type = types.int;
66 default = 600;
67 };
68
69 keyFile = mkOption {
70 type = types.nullOr types.path;
71 default = null;
72 };
73 };
74 };
75in {
76 disabledModules = [ "services/backup/borgbackup.nix" ];
77
78 options = {
79 services.borgbackup = {
80 snapshots = mkOption {
81 type = types.nullOr (types.enum ["btrfs" "lvm"]);
82 default = null;
83 };
84
85 targets = mkOption {
86 type = types.attrsOf (types.submodule targetOptions);
87 default = {};
88 };
89
90 prefix = mkOption {
91 type = types.str;
92 };
93 };
94 };
95
96 imports =
97 [ ./lvm-snapshots.nix
98 ./btrfs-snapshots.nix
99 ];
100
101 config = mkIf (any (t: t.paths != []) (attrValues cfg.targets)) {
102 services.btrfs-snapshots.enable = mkIf (cfg.snapshots == "btrfs") true;
103
104 services.lvm-snapshots.snapshots = mkIf (cfg.snapshots == "lvm") (listToAttrs (map (path: nameValuePair (path.VG + "-" + path.LV) {
105 inherit (path) LV VG;
106 mountName = withSuffix (path.VG + "-" + path.LV);
107 }) (unique (flatten (mapAttrsToList (target: tCfg: tCfg.paths) cfg.targets)))));
108
109 systemd.targets."timers-borg" = {
110 wantedBy = [ "timers.target" ];
111 };
112
113 systemd.slices."system-borgbackup" = {};
114
115 systemd.timers = (listToAttrs (map ({ target, path, tCfg }: nameValuePair "borgbackup-${target}@${systemdPath path}" {
116 requiredBy = [ "timers-borg.target" ];
117
118 timerConfig = {
119 Persistent = false;
120 OnBootSec = tCfg.interval;
121 OnUnitActiveSec = tCfg.interval;
122 RandomizedDelaySec = mkIf (tCfg.jitter != null) tCfg.jitter;
123 };
124 }) (flatten (mapAttrsToList (target: tCfg: map (path: { inherit target path tCfg; }) tCfg.paths) cfg.targets)))) // (mapAttrs' (target: tCfg: nameValuePair "borgbackup-prune-${target}" {
125 enable = tCfg.prune != {};
126
127 requiredBy = [ "timers-borg.target" ];
128
129 timerConfig = {
130 Persistent = false;
131 OnBootSec = tCfg.interval;
132 OnUnitActiveSec = tCfg.interval;
133 RandomizedDelaySec = mkIf (tCfg.jitter != null) tCfg.jitter;
134 };
135 }) cfg.targets);
136
137 systemd.services = (mapAttrs' (target: tCfg: nameValuePair "borgbackup-${target}@" (let
138 deps = flatten [
139 (optional (cfg.snapshots == "btrfs") "btrfs-snapshot@%i.service")
140 (optional tCfg.network "network-online.target")
141 ];
142 in {
143 bindsTo = deps;
144 after = deps;
145
146 path = with pkgs; [borgbackup] ++ optional (tCfg.lock != null) utillinux;
147
148 script = let
149 borgCmd = ''
150 borg create \
151 --lock-wait ${toString tCfg.lockWait} \
152 --stats \
153 --list \
154 --filter 'AME' \
155 --exclude-caches \
156 --keep-exclude-tags \
157 --patterns-from .backup-${target} \
158 --one-file-system \
159 --compression auto,lzma \
160 ${tCfg.repo}::${cfg.prefix}$1-{utcnow}
161 '';
162 in if tCfg.lock == null then borgCmd else "flock -xo /var/lock/${tCfg.lock} ${borgCmd}";
163 scriptArgs = if cfg.snapshots == "lvm" then "%I" else "%i";
164
165 unitConfig = {
166 AssertPathIsDirectory = mkIf (tCfg.lock != null) "/var/lock";
167 DefaultDependencies = false;
168 RequiresMountsFor = mkIf (cfg.snapshots == "lvm") [ "${mountPoint}/${withSuffix "%I"}" ];
169 };
170
171 serviceConfig = {
172 Type = "oneshot";
173 WorkingDirectory = if (cfg.snapshots == null) then "%I" else (if (cfg.snapshots == "lvm") then "${mountPoint}/${withSuffix "%I"}" else "${withSuffix "%f"}");
174 Nice = 15;
175 IOSchedulingClass = 2;
176 IOSchedulingPriority = 7;
177 SuccessExitStatus = [1 2];
178 Slice = "system-borgbackup.slice";
179 Environment = lib.mkIf (tCfg.keyFile != null) "BORG_KEY_FILE=${tCfg.keyFile}";
180 };
181 })) cfg.targets) // (mapAttrs' (target: tCfg: nameValuePair "borgbackup-prune-${target}" {
182 enable = tCfg.prune != {};
183
184 bindsTo = ["network-online.target"];
185 after = ["network-online.target"];
186
187 path = with pkgs; [borgbackup];
188
189 script = concatStringsSep "\n" (mapAttrsToList (path: args: ''
190 borg prune \
191 --lock-wait ${toString tCfg.lockWait} \
192 --list \
193 --stats \
194 --prefix ${escapeShellArg "${cfg.prefix}${path}"} \
195 ${escapeShellArgs args} \
196 ${tCfg.repo}
197 '') tCfg.prune);
198
199 serviceConfig = {
200 Type = "oneshot";
201 Slice = "system-borgbackup.slice";
202 Environment = lib.mkIf (tCfg.keyFile != null) "BORG_KEY_FILE=${tCfg.keyFile}";
203 };
204 }) cfg.targets);
205 };
206}
diff --git a/modules/borgbackup/lvm-snapshots.nix b/modules/borgbackup/lvm-snapshots.nix
new file mode 100644
index 00000000..9b2a6562
--- /dev/null
+++ b/modules/borgbackup/lvm-snapshots.nix
@@ -0,0 +1,133 @@
1{ config, lib, utils, pkgs, ... }:
2
3with utils;
4with lib;
5
6let
7 cfg = config.services.lvm-snapshots;
8
9 snapshotMount = name: "${cfg.mountPoint}/${if isNull cfg.snapshots."${name}".mountName then name else cfg.snapshots."${name}".mountName}";
10 snapshotName = name: "${name}-${cfg.mountSuffix}";
11
12 snapshotConfig = {
13 options = {
14 LV = mkOption {
15 type = types.str;
16 };
17
18 VG = mkOption {
19 type = types.str;
20 };
21
22 mountName = mkOption {
23 type = types.nullOr types.str;
24 default = null;
25 };
26
27 cowSize = mkOption {
28 type = types.str;
29 default = "-l20%ORIGIN";
30 };
31
32 readOnly = mkOption {
33 type = types.bool;
34 default = true;
35 };
36
37 persist = mkOption {
38 type = types.bool;
39 default = false;
40 };
41 };
42 };
43in {
44 options = {
45
46 services.lvm-snapshots = {
47 snapshots = mkOption {
48 type = types.attrsOf (types.submodule snapshotConfig);
49 default = {};
50 };
51
52 mountPoint = mkOption {
53 type = types.path;
54 default = "/mnt";
55 };
56
57 mountSuffix = mkOption {
58 type = types.str;
59 default = "-snapshot";
60 };
61 };
62 };
63
64
65 config = mkIf (cfg != {}) {
66
67 boot.kernelModules = [ "dm_snapshot" ];
68
69 # system.activationScripts = mapAttrs' (name: scfg: nameValuePair ("lvm-mountpoint" + name) ''
70 # mkdir -p ${snapshotMount name}
71 # '') cfg.snapshots;
72
73 systemd.services = mapAttrs' (name: scfg: nameValuePair ("lvm-snapshot@" + escapeSystemdPath name) {
74 enable = true;
75
76 description = "LVM-snapshot of ${scfg.VG}/${scfg.LV}";
77
78 bindsTo = ["${escapeSystemdPath "/dev/${scfg.VG}/${scfg.LV}"}.device"];
79 after = ["${escapeSystemdPath "/dev/${scfg.VG}/${scfg.LV}"}.device"];
80
81 unitConfig = {
82 StopWhenUnneeded = !scfg.persist;
83 AssertPathIsDirectory = "/var/lock";
84 };
85
86 path = with pkgs; [ devicemapper utillinux ];
87
88 script = ''
89 (
90 flock -xn -E 4 9
91 if [[ "$?" -ne 0 ]]; then
92 exit $?
93 fi
94
95 lvcreate -s ${scfg.cowSize} --name ${snapshotName name} ${scfg.VG}/${scfg.LV}
96
97 sleep infinity &
98 ) 9>/var/lock/lvm-snapshot.${scfg.VG}
99 '';
100
101 preStart = ''
102 lvremove -f ${scfg.VG}/${snapshotName name}
103 '';
104
105 preStop = ''
106 lvremove -f ${scfg.VG}/${snapshotName name}
107 '';
108
109 serviceConfig = with pkgs; {
110 Type = "forking";
111 RestartForceExitStatus = [ "4" ];
112 RestartSec = "5min";
113 };
114 }) cfg.snapshots;
115
116 systemd.mounts = mapAttrsToList (name: scfg: {
117 enable = true;
118
119 unitConfig = {
120 # AssertPathIsDirectory = snapshotMount name;
121 StopWhenUnneeded = !scfg.persist;
122 };
123
124 bindsTo = [ ("lvm-snapshot@" + escapeSystemdPath name + ".service") ];
125 after = [ ("lvm-snapshot@" + escapeSystemdPath name + ".service") ];
126
127 options = concatStringsSep "," ([ "noauto" ] ++ optional scfg.readOnly "ro");
128
129 where = snapshotMount name;
130 what = "/dev/" + scfg.VG + "/" + snapshotName name;
131 }) cfg.snapshots;
132 };
133}
diff --git a/modules/borgbackup/repokeys/borg_munin__borg.yaml b/modules/borgbackup/repokeys/borg_munin__borg.yaml
new file mode 100644
index 00000000..f302fe06
--- /dev/null
+++ b/modules/borgbackup/repokeys/borg_munin__borg.yaml
@@ -0,0 +1,33 @@
1key: ENC[AES256_GCM,data:mxh+Jtxx+HyD246yPwo0vy7vSTz3IG8VmfbxPMwqJRreh9ZwkGnH5aCTDOvWOHIrkmzaRMF3oCi1P8D29+abMUZdt0MuJ3UE6iL8+SXlflR+WACgALM2Df+x9B3BwQM3yeoCiWG+ebr0iQPHM3jqqpkjoRv1CcythxG2deZueur9lzgC2CwG1g3O8Prnl9z0JQGOa+gjic8Zwfn38B1BECeNPrbjzICGBOrSbN/6EnfBDygI2QzseamzK2I6R6jT+QxHvkl+Zi1m2TRB+4o82VgTjPhIReJyT7PrlDnUyrKObhCOlb3v+LiSdp16IPIDVs968kyDzgyi7QPOpGr+5tutWCZrau5xhPDrONKByl/0nVVwEZfRIYATvEXtn5okJru/mglcpeD0I7AtLt+Vfv9CB9pQczvkHo0cDtgudQDf9ADt/nkmqHugm5VfMg9m9aGbKqzXt6pPOMsXSbS43K7wgDaduLZ/PW4Ookx9gTNLtJHnZ64GBorOv4QSrZIZF8pE1FsQdUhmp/YzVhaNBnjCr+Jh77sYjoOwzF77Xy+VP2C/yVIf492P+FcgkSj6XhYYqHffpFW9l/xmUvyQF5gjj2k5T21UvgChhI1HeLPzQ7W9+xuGSMtg58aD/VPe1loCy8zLITNl71bneararRS5vItoZyzMdmIRMLAZD1klPmDNe1yufTpubOXzNYbWUqFUZtwH/mDL5GRZBD9dqs2b3F26c1CUyw==,iv:NJBHesKSZ1zuKk8qHnYKqIwMnFkH+rkQD1bam5XpLXU=,tag:EiYbIFY/r/eTSTJIhYV+GA==,type:str]
2sops:
3 kms: []
4 gcp_kms: []
5 azure_kv: []
6 hc_vault: []
7 lastmodified: '2021-01-02T20:38:48Z'
8 mac: ENC[AES256_GCM,data:3rkFTOk3r2dx3hOqu1u7XIIibTDfqNlRcWY9X2N/LFa/BKojgDt5tcpbphV4HqWvl8nS+fPcVrIElJfQ/QGFEOx68G95BhByntT9+JhSbHJt73dGnCSroZCw5QefdydREGvA5n00Vo9yT9IMvQsQbmpRzo6hcrSSUvagZqmZckA=,iv:F/HllDzyxgulIWZbfz9bFKR+SFg4PoaUYZ5N5hfIzw0=,tag:h2NXmvj/thhBg1rIkwdXXA==,type:str]
9 pgp:
10 - created_at: '2021-01-02T20:38:09Z'
11 enc: |
12 -----BEGIN PGP MESSAGE-----
13
14 hF4Dgwm4NZSaLAcSAQdAwmvyXlr9MyfPfLgkfQkoktKBV2WA2xhZrGL7NeeGfhAw
15 REk+clJ9WgiJ0iceRAONPnEjeiK0J6Fsj+5Ulq8flFGkoj5Pta0pm/9fudKmcPdC
16 0l4BF0G5LSpG1EmY+LmVdSdas16rWgthnojoXPvbbHG6jZs3aDETshdiN8Bdlqsf
17 aVhq2LYzscnYezNcdernR4uojtiFny8qcmdF3tFacr+mkgfgIQr0W9yWFhDH15gm
18 =4TwU
19 -----END PGP MESSAGE-----
20 fp: F1AF20B9511B63F681A14E8D51AEFBCD1DEF68F8
21 - created_at: '2021-01-02T20:38:09Z'
22 enc: |
23 -----BEGIN PGP MESSAGE-----
24
25 hF4DXxoViZlp6dISAQdAruPXj9IsllEN7R5jk4gF7bW0ZirhvX7qsu22/6HbSw8w
26 66RwN3WGjYO1CcVbHKuLqVVaUBCnrR/4XHN0JYUaqjubrSZBTWFKTBFsKSTT0LZq
27 0l4BKcsXrbGpYC5+yQvg0RHJ7LplxpKOmqMY8KGckvGnVf2xg7k6wuWQREFzqwt+
28 lOa3x+xFy9c0JwE8AafyKjb/cgqJiMb96lhsH57BpXJa2E39ImQbXqzDzdx2jEUt
29 =3rxi
30 -----END PGP MESSAGE-----
31 fp: 30D3453B8CD02FE2A3E7C78C0FB536FB87AE8F51
32 unencrypted_suffix: _unencrypted
33 version: 3.6.1
diff --git a/modules/kill-user.nix b/modules/kill-user.nix
new file mode 100644
index 00000000..dd897b36
--- /dev/null
+++ b/modules/kill-user.nix
@@ -0,0 +1,13 @@
1{ lib, pkgs, config, ... }:
2{
3 options = {
4 systemd.kill-user.enable = lib.mkEnableOption "Systemd kill-user@ services";
5 };
6
7 config.systemd.services."kill-user@" = lib.mkIf config.systemd.kill-user.enable {
8 serviceConfig = {
9 Type = "oneshot";
10 ExecStart = "${pkgs.systemd}/bin/loginctl kill-user %I";
11 };
12 };
13}
diff --git a/modules/tinc-networkmanager.nix b/modules/tinc-networkmanager.nix
new file mode 100644
index 00000000..ff03abd2
--- /dev/null
+++ b/modules/tinc-networkmanager.nix
@@ -0,0 +1,36 @@
1{ lib, config, pkgs, ... }:
2let
3 cfg = config.services.tinc;
4in {
5 options = {
6 services.tinc.networks = lib.mkOption {
7 type = lib.types.attrsOf (lib.types.submodule {
8 options.nmDispatch = lib.mkOption {
9 type = lib.types.bool;
10 default = config.networking.networkmanager.enable;
11 description = ''
12 Install a network-manager dispatcher script to automatically
13 connect to all remotes when networking is available
14 '';
15 };
16 });
17 };
18 };
19
20 config = {
21 networking.networkmanager.dispatcherScripts = lib.concatLists (lib.flip lib.mapAttrsToList cfg.networks (network: data: lib.optional data.nmDispatch {
22 type = "basic";
23 source = pkgs.writeScript "connect-${network}.sh" ''
24 #!${pkgs.stdenv.shell}
25
26 shopt -s extglob
27
28 case "''${2}" in
29 (?(vpn-)up)
30 ${data.package}/bin/tinc -n ${network} --pidfile /run/tinc.${network}.pid --batch retry
31 ;;
32 esac
33 '';
34 }));
35 };
36}
diff --git a/modules/uucp.nix b/modules/uucp.nix
new file mode 100644
index 00000000..0334a3db
--- /dev/null
+++ b/modules/uucp.nix
@@ -0,0 +1,391 @@
1{ flake, config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 portSpec = name: node: concatStringsSep "\n" (map (port: ''
7 port ${name}.${port}
8 type pipe
9 protocol ${node.protocols}
10 reliable true
11 command ${pkgs.openssh}/bin/ssh -x -o batchmode=yes ${name}.${port}
12 '') node.hostnames);
13 sysSpec = name: node: ''
14 system ${name}
15 time any
16 chat-seven-bit false
17 chat . ""
18 protocol ${node.protocols}
19 command-path ${concatStringsSep " " cfg.commandPath}
20 commands ${concatStringsSep " " node.commands}
21 ${concatStringsSep "\nalternate\n" (map (port: ''
22 port ${name}.${port}
23 '') node.hostnames)}
24 '';
25 sshConfig = name: node: concatStringsSep "\n" (map (port: ''
26 Host ${name}.${port}
27 Hostname ${port}
28 IdentitiesOnly Yes
29 IdentityFile ${cfg.sshKeyDir}/${name}
30 '') node.hostnames);
31 sshKeyGen = name: node: ''
32 if [[ ! -e ${cfg.sshKeyDir}/${name} ]]; then
33 ${pkgs.openssh}/bin/ssh-keygen ${escapeShellArgs node.generateKey} -f ${cfg.sshKeyDir}/${name}
34 fi
35 '';
36 restrictKey = key: ''
37 restrict,command="${chat}" ${key}
38 '';
39 chat = pkgs.writeScript "chat" ''
40 #!${pkgs.stdenv.shell}
41
42 echo .
43 exec ${config.security.wrapperDir}/uucico
44 '';
45
46 nodeCfg = {
47 options = {
48 commands = mkOption {
49 type = types.listOf types.str;
50 default = cfg.defaultCommands;
51 description = "Commands to allow for this remote";
52 };
53
54 protocols = mkOption {
55 type = types.separatedString "";
56 default = cfg.defaultProtocols;
57 description = "UUCP protocols to use for this remote";
58 };
59
60 publicKeys = mkOption {
61 type = types.listOf types.str;
62 default = [];
63 description = "SSH client public keys for this node";
64 };
65
66 generateKey = mkOption {
67 type = types.listOf types.str;
68 default = [ "-t" "ed25519" "-N" "" ];
69 description = "Arguments to pass to `ssh-keygen` to generate a keypair for communication with this host";
70 };
71
72 hostnames = mkOption {
73 type = types.listOf types.str;
74 default = [];
75 description = "Hostnames to try in order when connecting";
76 };
77 };
78 };
79
80 cfg = config.services.uucp;
81in {
82 options = {
83 services.uucp = {
84 enable = mkOption {
85 type = types.bool;
86 default = false;
87 description = ''
88 If enabled we set up an account accesible via uucp over ssh
89 '';
90 };
91
92 nodeName = mkOption {
93 type = types.str;
94 default = "nixos";
95 description = "uucp node name";
96 };
97
98 sshUser = mkOption {
99 type = types.attrs;
100 default = {};
101 description = "Overrides for the local uucp linux-user";
102 };
103
104 extraSSHConfig = mkOption {
105 type = types.str;
106 default = "";
107 description = "Extra SSH config";
108 };
109
110 remoteNodes = mkOption {
111 type = types.attrsOf (types.submodule nodeCfg);
112 default = {};
113 description = ''
114 Ports to set up
115 Names will probably need to be configured in sshConfig
116 '';
117 };
118
119 commandPath = mkOption {
120 type = types.listOf types.path;
121 default = [ "${pkgs.rmail}/bin" ];
122 description = ''
123 Command search path for all systems
124 '';
125 };
126
127 defaultCommands = mkOption {
128 type = types.listOf types.str;
129 default = ["rmail"];
130 description = "Commands allowed for remotes without explicit override";
131 };
132
133 defaultProtocols = mkOption {
134 type = types.separatedString "";
135 default = "te";
136 description = "UUCP protocol to use within ssh unless overriden";
137 };
138
139 incomingProtocols = mkOption {
140 type = types.separatedString "";
141 default = "te";
142 description = "UUCP protocols to use when called";
143 };
144
145 homeDir = mkOption {
146 type = types.path;
147 default = "/var/uucp";
148 description = "Home of the uucp user";
149 };
150
151 sshKeyDir = mkOption {
152 type = types.path;
153 default = "${cfg.homeDir}/.ssh/";
154 description = "Directory to store ssh keypairs";
155 };
156
157 spoolDir = mkOption {
158 type = types.path;
159 default = "/var/spool/uucp";
160 description = "Spool directory";
161 };
162
163 lockDir = mkOption {
164 type = types.path;
165 default = "/var/spool/uucp";
166 description = "Lock directory";
167 };
168
169 pubDir = mkOption {
170 type = types.path;
171 default = "/var/spool/uucppublic";
172 description = "Public directory";
173 };
174
175 logFile = mkOption {
176 type = types.path;
177 default = "/var/log/uucp";
178 description = "Log file";
179 };
180
181 statFile = mkOption {
182 type = types.path;
183 default = "/var/log/uucp.stat";
184 description = "Statistics file";
185 };
186
187 debugFile = mkOption {
188 type = types.path;
189 default = "/var/log/uucp.debug";
190 description = "Debug file";
191 };
192
193 interval = mkOption {
194 type = types.nullOr types.str;
195 default = "1h";
196 description = ''
197 Specification of when to run `uucico' in format used by systemd timers
198 The default is to do so every hour
199 '';
200 };
201
202 nmDispatch = mkOption {
203 type = types.bool;
204 default = config.networking.networkmanager.enable;
205 description = ''
206 Install a network-manager dispatcher script to automatically
207 call all remotes when networking is available
208 '';
209 };
210
211 extraConfig = mkOption {
212 type = types.lines;
213 default = ''
214 run-uuxqt 1
215 '';
216 description = "Extra configuration to append verbatim to `/etc/uucp/config'";
217 };
218
219 extraSys = mkOption {
220 type = types.lines;
221 default = ''
222 protocol-parameter g packet-size 4096
223 '';
224 description = "Extra configuration to prepend verbatim to `/etc/uucp/sys`";
225 };
226 };
227 };
228
229 config = mkIf cfg.enable {
230 environment.etc."uucp/config" = {
231 text = ''
232 hostname ${cfg.nodeName}
233
234 spool ${cfg.spoolDir}
235 lockdir ${cfg.lockDir}
236 pubdir ${cfg.pubDir}
237 logfile ${cfg.logFile}
238 statfile ${cfg.statFile}
239 debugfile ${cfg.debugFile}
240
241 ${cfg.extraConfig}
242 '';
243 };
244
245 users.users."uucp" = {
246 name = "uucp";
247 isSystemUser = true;
248 isNormalUser = false;
249 createHome = true;
250 home = cfg.homeDir;
251 description = "User for uucp over ssh";
252 useDefaultShell = true;
253 openssh.authorizedKeys.keys = map restrictKey (concatLists (mapAttrsToList (name: node: node.publicKeys) cfg.remoteNodes));
254 } // cfg.sshUser;
255
256 system.activationScripts."uucp-sshconfig" = ''
257 mkdir -p ${config.users.users."uucp".home}/.ssh
258 chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${config.users.users."uucp".home}/.ssh
259 chmod 700 ${config.users.users."uucp".home}/.ssh
260 ln -fs ${builtins.toFile "ssh-config" ''
261 ${concatStringsSep "\n" (mapAttrsToList sshConfig cfg.remoteNodes)}
262
263 ${cfg.extraSSHConfig}
264 ''} ${config.users.users."uucp".home}/.ssh/config
265
266 mkdir -p ${cfg.sshKeyDir}
267 chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.sshKeyDir}
268 chmod 700 ${cfg.sshKeyDir}
269
270 ${concatStringsSep "\n" (mapAttrsToList sshKeyGen cfg.remoteNodes)}
271 '';
272
273 system.activationScripts."uucp-logs" = ''
274 touch ${cfg.logFile}
275 chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.logFile}
276 chmod 644 ${cfg.logFile}
277 touch ${cfg.statFile}
278 chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.statFile}
279 chmod 644 ${cfg.statFile}
280 touch ${cfg.debugFile}
281 chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.debugFile}
282 chmod 644 ${cfg.debugFile}
283 '';
284
285 environment.etc."uucp/port" = {
286 text = ''
287 port ssh
288 type stdin
289 protocol ${cfg.incomingProtocols}
290 '' + concatStringsSep "\n" (mapAttrsToList portSpec cfg.remoteNodes);
291 };
292 environment.etc."uucp/sys" = {
293 text = cfg.extraSys + "\n" + concatStringsSep "\n" (mapAttrsToList sysSpec cfg.remoteNodes);
294 };
295
296 security.wrappers = let
297 wrapper = p: {
298 name = p;
299 value = {
300 source = "${pkgs.uucp}/bin/${p}";
301 owner = "root";
302 group = "root";
303 setuid = true;
304 setgid = false;
305 };
306 };
307 in listToAttrs (map wrapper ["uucico" "cu" "uucp" "uuname" "uustat" "uux" "uuxqt"]);
308
309 nixpkgs.overlays = [(self: super: {
310 uucp = super.lib.overrideDerivation super.uucp (oldAttrs: {
311 configureFlags = "--with-newconfigdir=/etc/uucp";
312 patches = [
313 (super.writeText "mailprogram" ''
314 policy.h | 2 +-
315 1 file changed, 1 insertion(+), 1 deletion(-)
316
317 diff --git a/policy.h b/policy.h
318 index 5afe34b..8e92c8b 100644
319 --- a/policy.h
320 +++ b/policy.h
321 @@ -240,7 +240,7 @@
322 the sendmail choice below. Otherwise, select one of the other
323 choices as appropriate. */
324 #if 1
325 -#define MAIL_PROGRAM "/usr/lib/sendmail -t"
326 +#define MAIL_PROGRAM "${config.security.wrapperDir}/sendmail -t"
327 /* #define MAIL_PROGRAM "/usr/sbin/sendmail -t" */
328 #define MAIL_PROGRAM_TO_BODY 1
329 #define MAIL_PROGRAM_SUBJECT_BODY 1
330 '')
331 ];
332 });
333 rmail = super.writeScriptBin "rmail" ''
334 #!${super.stdenv.shell}
335
336 # Dummy UUCP rmail command for postfix/qmail systems
337
338 IFS=" " read junk from junk junk junk junk junk junk junk relay
339
340 case "$from" in
341 *[@!]*) ;;
342 *) from="$from@$relay";;
343 esac
344
345 exec ${config.security.wrapperDir}/sendmail -G -i -f "$from" -- "$@"
346 '';
347 })];
348
349 environment.systemPackages = with pkgs; [
350 uucp
351 ];
352
353 systemd.services."uucico@" = {
354 serviceConfig = {
355 User = "uucp";
356 Type = "oneshot";
357 ExecStart = "${config.security.wrapperDir}/uucico -D -S %i";
358 };
359 };
360
361 systemd.timers."uucico@" = {
362 timerConfig.OnActiveSec = cfg.interval;
363 timerConfig.OnUnitActiveSec = cfg.interval;
364 };
365
366 systemd.targets."multi-user" = {
367 wants = mapAttrsToList (name: node: "uucico@${name}.timer") cfg.remoteNodes;
368 };
369
370 systemd.kill-user.enable = true;
371 systemd.targets."sleep" = {
372 after = [ "kill-user@uucp.service" ];
373 wants = [ "kill-user@uucp.service" ];
374 };
375
376 networking.networkmanager.dispatcherScripts = optional cfg.nmDispatch {
377 type = "basic";
378 source = pkgs.writeScript "callRemotes.sh" ''
379 #!${pkgs.stdenv.shell}
380
381 shopt -s extglob
382
383 case "''${2}" in
384 (?(vpn-)up)
385 ${concatStringsSep "\n " (mapAttrsToList (name: node: "${pkgs.systemd}/bin/systemctl start uucico@${name}.service") cfg.remoteNodes)}
386 ;;
387 esac
388 '';
389 };
390 };
391}
diff --git a/modules/yggdrasil/default.nix b/modules/yggdrasil/default.nix
new file mode 100644
index 00000000..91a550d6
--- /dev/null
+++ b/modules/yggdrasil/default.nix
@@ -0,0 +1,49 @@
1{ config, lib, customUtils, ... }:
2let
3 cfg = config.services.tinc.yggdrasil;
4in {
5 options = {
6 services.tinc.yggdrasil = lib.mkOption {
7 type = lib.types.submodule {
8 options = {
9 enable = lib.mkEnableOption "Yggdrasil tinc network";
10
11 connect = lib.mkOption {
12 default = true;
13 type = lib.types.bool;
14 description = ''
15 Connect to central server
16 '';
17 };
18 };
19 };
20 };
21 };
22
23 config = lib.mkIf cfg.enable {
24 services.tinc.networks.yggdrasil = {
25 name = config.networking.hostName;
26 hostSettings = customUtils.recImport { dir = ./hosts; };
27 debugLevel = 2;
28 interfaceType = "tap";
29 settings = {
30 Mode = "switch";
31 PingTimeout = 30;
32 ConnectTo = lib.mkIf cfg.connect "ymir";
33 };
34 };
35
36 sops.secrets = {
37 tinc-yggdrasil-rsa = {
38 key = "rsa";
39 path = "/etc/tinc/yggdrasil/rsa_key.priv";
40 sopsFile = ./hosts + "/${config.services.tinc.networks.yggdrasil.name}/private-keys.yaml";
41 };
42 tinc-yggdrasil-ed25519 = {
43 key = "ed25519";
44 path = "/etc/tinc/yggdrasil/rsa_key.priv";
45 sopsFile = ./hosts + "/${config.services.tinc.networks.yggdrasil.name}/private-keys.yaml";
46 };
47 };
48 };
49}
diff --git a/modules/yggdrasil/hosts/sif/default.nix b/modules/yggdrasil/hosts/sif/default.nix
new file mode 100644
index 00000000..32b844de
--- /dev/null
+++ b/modules/yggdrasil/hosts/sif/default.nix
@@ -0,0 +1,13 @@
1{
2 settings.Ed25519PublicKey = "qJqty+wiTNcYaHQCvQNiMqXYz30C9M3+LI/qjmU/9hK";
3 rsaPublicKey = ''
4 -----BEGIN RSA PUBLIC KEY-----
5 MIIBCgKCAQEA0ACaacg9EN0hBQct8ZwQ/i6EsXKP4DIwKwabM2rp8azValTHU2uI
6 WW6JRY+Eii6zRx9B5kJ96C4rJJeAGV6lZPAogaC2LbM7lcsZ7oRDWZGaQKcZFNGi
7 laEcDg2dRuDx1W4at0rb03SDLNPt8sXSV6BcK9n/7m7+s9cwM/+PB8FHDMnWvwbC
8 usbP23020s+CVr/PU1z/7J0y3Eat+Acut6x5X8DNewpqV96wQpqdAggbhtYERMFH
9 +i0sa1WUDQtJ6HGChbENRTMlsPJ6lnzXY+J0pzatzzvetLsOljES9uJ8dtk6qBC7
10 KRZo5lvdUwR6j9XiHMQeRerUt23b9ATFXQIDAQAB
11 -----END RSA PUBLIC KEY-----
12 '';
13}
diff --git a/modules/yggdrasil/hosts/sif/private-keys.yaml b/modules/yggdrasil/hosts/sif/private-keys.yaml
new file mode 100644
index 00000000..9be82bc1
--- /dev/null
+++ b/modules/yggdrasil/hosts/sif/private-keys.yaml
@@ -0,0 +1,34 @@
1ed25519: ENC[AES256_GCM,data:1CqB4y6CIm5JUsznpXPqqLJqCKmmoAJOZQTWb7+Jbn0oZMX27qSMK4CchHF7Bmo24EK8rk5EyW5aQLnoxp/2NA62p8SXdaoI8Qgz3EgsQ5QrlJrt1jvERpNs4vttT9V6+aK3Yojr9IuQSvJ4jyKSLrzrTnLzF9pXlaOf1Ru5SxySRWtVzynzurRpdUVS6goE+lb+Irg6x2geV719iQ9bu1C2smeQDREdS+dlfoxp02/pU6kTFA7KAm5vA91HKEfMqfSEzuBgUB0=,iv:n6Yh0zZ9AbT+83P42QNO2rCCISJV5nbO9wYcwaRYD2E=,tag:dJpXV9ZzLSO1B+LsyV3vAg==,type:str]
2rsa: ENC[AES256_GCM,data:7faQJAhoYt3MJidg4TVwysmLGZ4V1fA9NYYKgEMgky4q0Q9tBGhEsA60uj7iKcMMRhGku7feIFkj2+1qjKy+e1Bajfs2rqxgyqYmM6yOTrmorbXBVyrPOTOwJp3yp7O1vIXwoUS9vWIYxFszpfaLL0/8aARYVrYmpxf3gsBfQ4LciM1VKEgjG3uRBf1tDLaNuMNyzdan0DFghwuDojPOXUFv/6yuPxU2U0TagVjwAk4FThGwEasvV454RSm/GmqYtX+P4Vc3pEWNYAK1rXJAuXm1392Uash+HGQ+3ln5N9yWneewgPPr0pePAugxxN0qnwhy5MRKGQE3ZHCZ0beslfOm6pkmYTfww3lKNIJGabMfMD3COoAI7zWebUvksZPsgH6f1olbzABkZdS1s//WNMnWQHGxsWePXkLFe8bfnNXouEXHtLvQ7On0KPyt8y5QBI9bDPpTn92/O9jCevXSttrez4buBdCHFmCE8xgW5JKKEXgMubPPjEF3MABiGu0TMeWM4a1ibY7HfvNrRkO1pE9RhdRT/dFV/MrPxk7P0k16x9H4+QnE7VglfNZO3Wd3bnYxcH7hmAbIzpFnUJvolyNfmynwL2WwaYuBskXASD1FuqpM0tbhantqGyHVPe62+KimU0zDAJ1HMyqhIN0MD1MSXsdoItAsw033GYLB83L8xPatARJR9qEdKwrhmgSDY36AbJ8VI/RUzicZoYdhK8+M7bNGIkD5MgrQO35q+3oa6Xcib+5MtW0RVJKLP4y5/XNkjd4EPl6nahcVi63/FG7LJmO+/I7bkLIAWmIq8BHcXEwbz0womYp404pSfEPr3cy1N5S3yqRdzVxavTJb0PLMpHq2rWuHK2DIY77hEOAt0XcReWYsRkmTl+v9iQLF+D4GBLr+O2oZNJrocNVZYkfdjsrUd2cUOCV7ZQphO5Yc+yKrqzmCqUUvdoJ3vlaPxMXx4LACeMImo1sAFxoOgIpyfklo/bdhi9osiL55I8pAIh5hGes/uCbwaRnW+wbaYcMliCuUO8XelfXwBot8W+0l0wk2zKRSKtYKcX1n/Ax5mIt6mIoQkvyL82lccS9ppJLjt7DYlvK8L6imeV11ATf1ZhSGB3c67/XYik5BXz827Rj29K6fg/CvU65f/bEAuE39gSJ4mHsRl3bvkNLiUMEBrDuZnText33fCbqVA5DUIfqSbLUzXtqNl8vHnlOBICYwjv8PtUMJ6VTCDu33SmtQzJAfnmuewOKAC51FPsyaDhouTKllUaqx34NfEP8k2C8/4oNPgDcLjInm3f43tIuJbScdp8ltNVCLoChS8jbBOvrVYTI0eP+BuAuEfWYldUYq96oH/x9d0yvPqZ1rnwmqg4y6GfkACw6+/QvrDdtcM+1uI86RxZ7KGurb8KG7NPdSWhzz+72+TO5Tq29K8QETLzzalnVzaVWj/xGsjgkslxmDMKxLJQw0o24lgg/R30aU9BL6YwDVi10nu+Tv5kayb/NVLdMNWxfKNg1KZcf8M2ApgonjingbpUlinZ25/IIcQB9lMT4HSyvtGtIqnsPL4SQNsgBLcMzdwbL0EvS3qMAEVWKfUm2v9AA2+RMsKEKtD4UNF2xF7oACJiyTcw/xUOmkaTIZZ2ev0JVb4IYs1qx5Skz+IMAvWQ2FjBMXna5e/LYgBl6kdLSTcDvlymHpbjjuRdRq+uq+ZMXIACyZ+qUnZ0qcfWGPxOCI0hXPc5ac/zSGkPKYiWT/rCSuo+MoijjK4YZ2fub9TCYjZRS+QvLlXOM8F06Or0jQQOveezqJFZdoBGj248BtcPAVbYqfaytIlYjARlhQL/lKaaOrbONk6kIlDpwkhlzO50OkhALItlbW4Aa8zZ/WeXkfkb/6A7NLce42XDoOnvZt9UdYVTRphf8yxjRE2YMwZsmeTIieg8KwwJdnoJIhiQFdVDFgXb2xPZA2CbdvZwGwuFkLWgJUg6H+aHdw39UnNM+S9PYaOQ9oaS7IyeWhXMgP7TKM98uILsBg/Xn9tafHaslQfjVRDEaYtrmDZMYhb+h/MZKngx7uwmUyqHszAYN/M+RMJVy3s4uBu/EufWYVMorunpPEXGYA4Rg1HUuAOvWSvpM3PJG9Wnrazw6xmkwIUSKju5irpWATYmqSX3pPkG5C0sTatszVDAvTs9+/9Xdbney7/6QskSHMph8Kn/Udpq7PPrZWADkIi1k4oibgABOXOWBk5ZbNbiDrZA==,iv:ZUAqvOpcVCXQD2PFzUh0e2m20t6gVT3mYb7S50iV/m8=,tag:AssxMqjVUEwQ4R6Y7eG9Tg==,type:str]
3sops:
4 kms: []
5 gcp_kms: []
6 azure_kv: []
7 hc_vault: []
8 lastmodified: '2021-01-02T14:46:16Z'
9 mac: ENC[AES256_GCM,data:Phng7z7UlE6nO3FFIQPOHgKCqDm2uOGL57ryJbokjipSSdoWPinpz0zIJv9Z67b9uOf3CQoGtV4YwcudNkzDBKOyD8uA6RYwCKpbYcZIdiy8DLL46+VT/wq9toTkeDXM6jKupzzOARZhHT8DCOLqW7u8Q3S645cbTJmw0+LMIGk=,iv:y4KEh0+bKhtnSobKVdfaPuRsueNC1lcrEbUGfEAn+Bg=,tag:3Oi4e/hSgPVsoFQpnVQj+g==,type:str]
10 pgp:
11 - created_at: '2021-01-02T14:45:04Z'
12 enc: |
13 -----BEGIN PGP MESSAGE-----
14
15 hF4Dgwm4NZSaLAcSAQdAwWM12Zara3T2xDIX3rhakGxXFyme4LE5QZgE2GjnnWEw
16 T/vhPfsKFCjA2kAmj41NupjvTPL/nzfd7+MrdHRfC462Jrq+UF1W8A4bUa3OMH5J
17 0l4BuFhl93w/VBftvnG8oSBAFCPNDapNADjTVJQStgsZa0/uD93NnCxyQmtuJYsQ
18 URlH0KMT6Kouaec4qk3SqkAHzaIIAukahBHAPf2C5cvXYw7AAOOBOdRaWycsmZDc
19 =S4Ig
20 -----END PGP MESSAGE-----
21 fp: F1AF20B9511B63F681A14E8D51AEFBCD1DEF68F8
22 - created_at: '2021-01-02T14:45:04Z'
23 enc: |
24 -----BEGIN PGP MESSAGE-----
25
26 hF4DXxoViZlp6dISAQdA7apd+ipJ0lUiuPI5Sq6uj6iOQYFfuNDuzse1JFJMfn4w
27 McsGPcbMorZV0OVFmg9vuZ0GP9sb7mkm+oRuY9OeMDEifjWGHJ2UN4TvdEcCO1zx
28 0l4BvYyzFbShlQjge7+nrzVi2lzEvqsozEW76K3arWb/iYLCRyl0/Vhw5WT4K/UE
29 fw4cbqz7JrogVLFNeWSRPk3Y+Dg4Pf9rQnw1EJhUEIczYjnfajPhYe5K4M01mOby
30 =B0n7
31 -----END PGP MESSAGE-----
32 fp: 30D3453B8CD02FE2A3E7C78C0FB536FB87AE8F51
33 unencrypted_suffix: _unencrypted
34 version: 3.6.1
diff --git a/modules/yggdrasil/hosts/ymir.nix b/modules/yggdrasil/hosts/ymir.nix
new file mode 100644
index 00000000..b77a9216
--- /dev/null
+++ b/modules/yggdrasil/hosts/ymir.nix
@@ -0,0 +1,19 @@
1{
2 addresses = [{ address = "ymir.yggdrasil.li"; }];
3 settings.Ed25519PublicKey = "b/SobnMqByzHOQeO+iU7OZ1liD8a++knbi5ebNawnaC";
4 rsaPublicKey = ''
5 -----BEGIN RSA PUBLIC KEY-----
6 MIICCgKCAgEAuInSfQf5euFXEVkLLzf9TumQJ+3WRsxX4uKdOXBqrIC7yjSBP8j9
7 ql5rNWPzgXxFF5ERmwW+E3cyzJLU9Htu7r3muqM6nhSZizhCskifPRFc3e5ssSke
8 XhHICHfe90+qvab/hWx/NjkW59bBYIzDuJfq+ijDFMVNgOxaiM2f3/2prUUhP7bN
9 r3wVI8KCkOaknc0SOOmOhLzfJaD5wosqLOjgaNhlro2eMgMjQlxbyW8dVVgjwseR
10 Cl/mpu7r1pSMhS66RFH68wDoC3X81f7Zs9ZGDLTD8KXWhx0qgUMUAH4n6YGY0RM6
11 BZ3qR/3KFRU64QPVAERpb0JdsU9ggCVydHkjrWW23ptHOPAOO5+yQj7tSDCKTRy9
12 dHMQnbtPrgAb6iMhO1XTxA8Hdta1sCHsewsQekarwsA1bmk3hTgi/k8vwoGDUWtk
13 jgiDEPuutfmH4C6qxq9s+6lRboNKH8wgkVGpHiaq7mmePFdhzFdrj4+fYAMZTbil
14 2iygsJ+yFOjA7U+iT6QDK33/MLsrQg0Ue6RPiG1qnDyax7gBAjz52iWkiuSkUXk0
15 E5ImdP4XMILgGcWk8iPq5iRS03edE0pCpxGX3ZZwFE5+CoXgO6wR1ToL1vZEEHMQ
16 SHJPufKjkavPKbejPps/mLaJQVw3W10PAJssB9nxW2aHX3n0ugGaIvMCAwEAAQ==
17 -----END RSA PUBLIC KEY-----
18 '';
19}
diff --git a/overlays/nerdfonts.nix b/overlays/nerdfonts.nix
new file mode 100644
index 00000000..28581d72
--- /dev/null
+++ b/overlays/nerdfonts.nix
@@ -0,0 +1,5 @@
1final: prev: {
2 nerdfonts = prev.nerdfonts.override {
3 fonts = ["FiraMono" "FiraCode"];
4 };
5}
diff --git a/overlays/nvidia-kernel-5.7.nix b/overlays/nvidia-kernel-5.7.nix
new file mode 100644
index 00000000..92d3abb3
--- /dev/null
+++ b/overlays/nvidia-kernel-5.7.nix
@@ -0,0 +1,19 @@
1final: prev: {
2 linuxPackages_latest = prev.linuxPackages_latest.extend (self: super: {
3 nvidiaPackages = super.nvidiaPackages // {
4 stable = super.nvidiaPackages.stable.overrideAttrs (attrs: {
5 patches = [
6 (prev.fetchpatch {
7 name = "nvidia-kernel-5.7.patch";
8 url = "https://gitlab.com/snippets/1965550/raw";
9 sha256 = "03iwxhkajk65phc0h5j7v4gr4fjj6mhxdn04pa57am5qax8i2g9w";
10 })
11 ];
12
13 passthru = {
14 inherit (super.nvidiaPackages.stable) settings persistenced persistencedVersion settingsVersion;
15 };
16 });
17 };
18 });
19}
diff --git a/overlays/persistent-nix-shell/default.nix b/overlays/persistent-nix-shell/default.nix
new file mode 100644
index 00000000..60396335
--- /dev/null
+++ b/overlays/persistent-nix-shell/default.nix
@@ -0,0 +1,19 @@
1final: prev: {
2 persistent-nix-shell = prev.stdenv.mkDerivation {
3 name = "persistent-nix-shell";
4 src = ./persistent-nix-shell;
5
6 phases = [ "buildPhase" "installPhase" ];
7
8 inherit (final) zsh;
9
10 buildPhase = ''
11 substituteAll $src persistent-nix-shell
12 '';
13
14 installPhase = ''
15 install -m 0755 -D -t $out/bin \
16 persistent-nix-shell
17 '';
18 };
19}
diff --git a/overlays/persistent-nix-shell/persistent-nix-shell b/overlays/persistent-nix-shell/persistent-nix-shell
new file mode 100644
index 00000000..a17f6de6
--- /dev/null
+++ b/overlays/persistent-nix-shell/persistent-nix-shell
@@ -0,0 +1,20 @@
1#!@zsh@/bin/zsh
2
3set -e
4
5gcrootsDir=${PWD}/.nix-gc-roots
6
7if [[ ${#@} -ge 1 ]]; then
8 shellFile=${1}
9 shift
10else
11 shellFile=${PWD}/shell.nix
12fi
13
14set -x
15mkdir -p ${gcrootsDir}
16nix-instantiate ${shellFile} --indirect --add-root ${gcrootsDir}/shell.drv
17nix-store --indirect --add-root ${gcrootsDir}/shell.dep --realise $(nix-store --query --references ${gcrootsDir}/shell.drv)
18set +x
19
20exec nix-shell $(readlink ${gcrootsDir}/shell.drv) ${@}
diff --git a/overlays/pidgin.nix b/overlays/pidgin.nix
new file mode 100644
index 00000000..d346c7a1
--- /dev/null
+++ b/overlays/pidgin.nix
@@ -0,0 +1,15 @@
1final: prev:
2let
3 mucHistory = prev.fetchpatch {
4 url = "https://developer.pidgin.im/raw-attachment/ticket/16524/0001-only-request-unseed-chat-history-from-jabber-group-c.patch";
5 sha256 = "083wvmq7417xz55fxxhllqwql1hgjvin2sak08844121yw1jvc44";
6 };
7in {
8 pidgin-with-plugins = import (/. + prev.path + "/pkgs/applications/networking/instant-messengers/pidgin/wrapper.nix") {
9 inherit (prev) makeWrapper symlinkJoin;
10 plugins = with final; [ purple-lurch pidgin-carbons pidgin-opensteamworks pidgin-xmpp-receipts ];
11 pidgin = prev.pidgin.overrideAttrs (oldAttrs: {
12 patches = (oldAttrs.patches or []) ++ [mucHistory];
13 });
14 };
15}
diff --git a/overlays/urxvt/52-osc.pl b/overlays/urxvt/52-osc.pl
new file mode 100644
index 00000000..3292e8c4
--- /dev/null
+++ b/overlays/urxvt/52-osc.pl
@@ -0,0 +1,41 @@
1#! perl
2
3=head1 NAME
4
552-osc - Implement OSC 32 ; Interact with X11 clipboard
6
7=head1 SYNOPSIS
8
9 urxvt -pe 52-osc
10
11=head1 DESCRIPTION
12
13This extension implements OSC 52 for interacting with system clipboard
14
15Most code stolen from:
16http://ailin.tucana.uberspace.de/static/nei/*/Code/urxvt/
17
18=cut
19
20use MIME::Base64;
21use Encode;
22
23sub on_osc_seq {
24 my ($term, $op, $args) = @_;
25 return () unless $op eq 52;
26
27 my ($clip, $data) = split ';', $args, 2;
28 if ($data eq '?') {
29 # my $data_free = $term->selection();
30 # Encode::_utf8_off($data_free); # XXX
31 # $term->tt_write("\e]52;$clip;".encode_base64($data_free, '')."\a");
32 }
33 else {
34 my $data_decoded = decode_base64($data);
35 Encode::_utf8_on($data_decoded); # XXX
36 $term->selection($data_decoded, $clip =~ /c|^$/);
37 $term->selection_grab(urxvt::CurrentTime, $clip =~ /c|^$/);
38 }
39
40 ()
41}
diff --git a/overlays/urxvt/default.nix b/overlays/urxvt/default.nix
new file mode 100644
index 00000000..3c57d000
--- /dev/null
+++ b/overlays/urxvt/default.nix
@@ -0,0 +1,21 @@
1final: prev: {
2 rxvt_unicode-with-plugins = prev.rxvt-unicode.override {
3 configure = { availablePlugins, ... }: {
4 plugins = [ final.urxvt_osc_52 ] ++ builtins.attrValues availablePlugins;
5 };
6 };
7 urxvt_osc_52 = prev.stdenv.mkDerivation {
8 name = "rxvt_unicode-osc_52-0";
9 src = ./52-osc.pl;
10 unpackPhase = ''
11 cp $src 52-osc
12 '';
13 buildPhase = ''
14 sed -i 's|#! perl|#! ${final.perl}/bin/perl|g' 52-osc
15 '';
16 installPhase = ''
17 mkdir -p $out/lib/urxvt/perl
18 cp 52-osc $out/lib/urxvt/perl
19 '';
20 };
21}
diff --git a/overlays/v4l2loopback.nix b/overlays/v4l2loopback.nix
new file mode 100644
index 00000000..335f86a3
--- /dev/null
+++ b/overlays/v4l2loopback.nix
@@ -0,0 +1,37 @@
1final: prev: {
2 linuxPackages_latest = prev.linuxPackages_latest.extend (self: super: {
3 v4l2loopback = super.stdenv.mkDerivation rec {
4 name = "v4l2loopback-${version}-${self.kernel.version}";
5 version = "f62fb9076b6313e5eb82fdcaceadb6b3052f346e";
6
7 src = prev.fetchFromGitHub {
8 owner = "umlaeute";
9 repo = "v4l2loopback";
10 rev = "${version}";
11 sha256 = "VRFtimQQtT8vd1dx5KtUDkmXo3DSOybhNLcAIxQba44=";
12 fetchSubmodules = true;
13 };
14
15 hardeningDisable = [ "format" "pic" ];
16
17 preBuild = ''
18 substituteInPlace Makefile --replace "modules_install" "INSTALL_MOD_PATH=$out modules_install"
19 sed -i '/depmod/d' Makefile
20 export PATH=${final.kmod}/sbin:$PATH
21 '';
22
23 nativeBuildInputs = self.kernel.moduleBuildDependencies;
24 buildInputs = [ final.kmod ];
25
26 makeFlags = [
27 "KERNELRELEASE=${self.kernel.modDirVersion}"
28 "KERNEL_DIR=${self.kernel.dev}/lib/modules/${self.kernel.modDirVersion}/build"
29 ];
30
31 postInstall = ''
32 mkdir -p $out/bin
33 install -m0755 utils/v4l2loopback-ctl $out/bin
34 '';
35 };
36 });
37}
diff --git a/overlays/worktime/default.nix b/overlays/worktime/default.nix
new file mode 100644
index 00000000..ab6fb40a
--- /dev/null
+++ b/overlays/worktime/default.nix
@@ -0,0 +1,19 @@
1final: prev: {
2 worktime = prev.stdenv.mkDerivation rec {
3 name = "worktime";
4 src = ./worktime.py;
5
6 phases = [ "buildPhase" "installPhase" ];
7
8 python = prev.python39.withPackages (ps: with ps; [pyxdg dateutil uritools requests configparser tabulate]);
9
10 buildPhase = ''
11 substituteAll $src worktime
12 '';
13
14 installPhase = ''
15 install -m 0755 -D -t $out/bin \
16 worktime
17 '';
18 };
19}
diff --git a/overlays/worktime/worktime.py b/overlays/worktime/worktime.py
new file mode 100755
index 00000000..c7b013f9
--- /dev/null
+++ b/overlays/worktime/worktime.py
@@ -0,0 +1,430 @@
1#!@python@/bin/python
2
3import requests
4from requests.exceptions import HTTPError
5from requests.auth import HTTPBasicAuth
6from datetime import *
7from xdg import (BaseDirectory)
8import configparser
9from uritools import uricompose
10
11from dateutil.easter import *
12from dateutil.tz import *
13from dateutil.parser import isoparse
14
15from enum import Enum
16
17from math import (copysign, ceil)
18
19import calendar
20
21import argparse
22
23from copy import deepcopy
24
25import sys
26
27from tabulate import tabulate
28
29class TogglAPISection(Enum):
30 TOGGL = '/api/v8'
31 REPORTS = '/reports/api/v2'
32
33class TogglAPIError(Exception):
34 def __init__(self, http_error, response):
35 self.http_error = http_error
36 self.response = response
37
38 def __str__(self):
39 if not self.http_error is None:
40 return str(self.http_error)
41 else:
42 return self.response.text
43
44class TogglAPI(object):
45 def __init__(self, api_token, workspace_id):
46 self._api_token = api_token
47 self._workspace_id = workspace_id
48
49 def _make_url(self, api=TogglAPISection.TOGGL, section=['time_entries', 'current'], params={}):
50 if api is TogglAPISection.REPORTS:
51 params.update({'user_agent': 'worktime', 'workspace_id': self._workspace_id})
52
53 api_path = api.value
54 section_path = '/'.join(section)
55 uri = uricompose(scheme='https', host='api.track.toggl.com', path=f"{api_path}/{section_path}", query=params)
56
57 return uri
58
59 def _query(self, url, method):
60
61 headers = {'content-type': 'application/json'}
62 response = None
63
64 if method == 'GET':
65 response = requests.get(url, headers=headers, auth=HTTPBasicAuth(self._api_token, 'api_token'))
66 elif method == 'POST':
67 response = requests.post(url, headers=headers, auth=HTTPBasicAuth(self._api_token, 'api_token'))
68 else:
69 raise ValueError(f"Undefined HTTP method “{method}”")
70
71 response.raise_for_status()
72
73 return response
74
75 def get_billable_hours(self, start_date, end_date=datetime.now(timezone.utc), rounding=False):
76 billable_acc = timedelta(milliseconds = 0)
77 step = timedelta(days = 365)
78
79 for req_start in [start_date + x * step for x in range(0, ceil((end_date - start_date) / step))]:
80 req_end = end_date
81 if end_date > req_start + step:
82 req_end = datetime.combine((req_start + step).astimezone(timezone.utc).date(), time(tzinfo=timezone.utc))
83 elif req_start > start_date:
84 req_start = datetime.combine(req_start.astimezone(timezone.utc).date(), time(tzinfo=timezone.utc)) + timedelta(days = 1)
85
86 url = self._make_url(api = TogglAPISection.REPORTS, section = ['summary'], params={'since': req_start.astimezone(timezone.utc).isoformat(), 'until': req_end.astimezone(timezone.utc).isoformat(), 'rounding': rounding})
87 r = self._query(url = url, method='GET')
88 if not r or not r.json():
89 raise TogglAPIError(r)
90 billable_acc += timedelta(milliseconds=r.json()['total_billable']) if r.json()['total_billable'] else timedelta(milliseconds=0)
91
92 return billable_acc
93
94 def get_running_clock(self, now=datetime.now(timezone.utc)):
95 url = self._make_url(api = TogglAPISection.TOGGL, section = ['time_entries', 'current'])
96 r = self._query(url = url, method='GET')
97
98 if not r or not r.json():
99 raise TogglAPIError(r)
100
101 if not r.json()['data'] or not r.json()['data']['billable']:
102 return None
103
104 start = isoparse(r.json()['data']['start'])
105
106 return now - start if start <= now else None
107
108class Worktime(object):
109 time_worked = timedelta()
110 running_entry = None
111 now = datetime.now(tzlocal())
112 time_pulled_forward = timedelta()
113 is_workday = False
114 include_running = True
115 time_to_work = None
116 force_day_to_work = True
117
118 @staticmethod
119 def holidays(year):
120 holidays = dict()
121
122 y_easter = datetime.combine(easter(year), time(), tzinfo=tzlocal())
123
124 # Legal holidays in munich, bavaria
125 holidays[datetime(year, 1, 1, tzinfo=tzlocal()).date()] = 1
126 holidays[datetime(year, 1, 6, tzinfo=tzlocal()).date()] = 1
127 holidays[(y_easter+timedelta(days=-2)).date()] = 1
128 holidays[(y_easter+timedelta(days=+1)).date()] = 1
129 holidays[datetime(year, 5, 1, tzinfo=tzlocal()).date()] = 1
130 holidays[(y_easter+timedelta(days=+39)).date()] = 1
131 holidays[(y_easter+timedelta(days=+50)).date()] = 1
132 holidays[(y_easter+timedelta(days=+60)).date()] = 1
133 holidays[datetime(year, 8, 15, tzinfo=tzlocal()).date()] = 1
134 holidays[datetime(year, 10, 3, tzinfo=tzlocal()).date()] = 1
135 holidays[datetime(year, 11, 1, tzinfo=tzlocal()).date()] = 1
136 holidays[datetime(year, 12, 25, tzinfo=tzlocal()).date()] = 1
137 holidays[datetime(year, 12, 26, tzinfo=tzlocal()).date()] = 1
138
139 return holidays
140
141 @staticmethod
142 def config():
143 config = configparser.ConfigParser()
144 config_dir = BaseDirectory.load_first_config('worktime')
145 config.read(f"{config_dir}/worktime.ini")
146 return config
147
148 def __init__(self, start_datetime=None, end_datetime=None, now=None, include_running=True, force_day_to_work=True, **kwargs):
149 self.include_running = include_running
150 self.force_day_to_work = force_day_to_work
151
152 if now:
153 self.now = now
154
155 config = Worktime.config()
156 config_dir = BaseDirectory.load_first_config('worktime')
157 api = TogglAPI(api_token=config['TOGGL']['ApiToken'], workspace_id=config['TOGGL']['Workspace'])
158 date_format = config.get('WORKTIME', 'DateFormat', fallback='%Y-%m-%d')
159
160 start_date = start_datetime or datetime.strptime(config['WORKTIME']['StartDate'], date_format).replace(tzinfo=tzlocal())
161 end_date = end_datetime or self.now
162
163 try:
164 with open(f"{config_dir}/reset", 'r') as reset:
165 for line in reset:
166 stripped_line = line.strip()
167 reset_date = datetime.strptime(stripped_line, date_format).replace(tzinfo=tzlocal())
168
169 if reset_date > start_date and reset_date < end_date:
170 start_date = reset_date
171 except IOError as e:
172 if e.errno != 2:
173 raise e
174
175
176 hours_per_week = float(config.get('WORKTIME', 'HoursPerWeek', fallback=40))
177 workdays = set([int(d.strip()) for d in config.get('WORKTIME', 'Workdays', fallback='1,2,3,4,5').split(',')])
178 time_per_day = timedelta(hours = hours_per_week) / len(workdays)
179
180 holidays = dict()
181
182 for year in range(start_date.year, end_date.year + 1):
183 holidays |= {k: v * time_per_day for k, v in Worktime.holidays(year).items()}
184
185 try:
186 with open(f"{config_dir}/excused", 'r') as excused:
187 for line in excused:
188 stripped_line = line.strip()
189 if stripped_line:
190 splitLine = stripped_line.split(' ')
191 if len(splitLine) == 2:
192 [hours, date] = splitLine
193 day = datetime.strptime(date, date_format).replace(tzinfo=tzlocal()).date()
194 holidays[day] = timedelta(hours = float(hours))
195 else:
196 holidays[datetime.strptime(stripped_line, date_format).replace(tzinfo=tzlocal()).date()] = time_per_day
197 except IOError as e:
198 if e.errno != 2:
199 raise e
200
201 pull_forward = dict()
202
203 start_day = start_date.date()
204 end_day = end_date.date()
205
206 try:
207 with open(f"{config_dir}/pull-forward", 'r') as excused:
208 for line in excused:
209 stripped_line = line.strip()
210 if stripped_line:
211 [hours, date] = stripped_line.split(' ')
212 constr = date.split(',')
213 for d in [start_day + timedelta(days = x) for x in range(0, (end_day - start_day).days + 1 + int(timedelta(hours = float(hours)).total_seconds() / 60 * (7 / len(workdays)) * 2))]:
214 for c in constr:
215 if c in calendar.day_abbr:
216 if not d.strftime('%a') == c: break
217 elif "--" in c:
218 [fromDay,toDay] = c.split('--')
219 if fromDay != "":
220 fromDay = datetime.strptime(fromDay, date_format).replace(tzinfo=tzlocal()).date()
221 if not fromDay <= d: break
222 if toDay != "":
223 toDay = datetime.strptime(toDay, date_format).replace(tzinfo=tzlocal()).date()
224 if not d <= toDay: break
225 else:
226 if not d == datetime.strptime(c, date_format).replace(tzinfo=tzlocal()).date(): break
227 else:
228 if d >= end_date.date():
229 pull_forward[d] = min(timedelta(hours = float(hours)), time_per_day - (holidays[d] if d in holidays else timedelta()))
230 except IOError as e:
231 if e.errno != 2:
232 raise e
233
234 days_to_work = dict()
235
236 if pull_forward:
237 end_day = max(end_day, max(list(pull_forward)))
238
239 for day in [start_day + timedelta(days = x) for x in range(0, (end_day - start_day).days + 1)]:
240 if day.isoweekday() in workdays:
241 time_to_work = time_per_day
242 if day in holidays.keys():
243 time_to_work -= holidays[day]
244 if time_to_work > timedelta():
245 days_to_work[day] = time_to_work
246
247 extra_days_to_work = dict()
248
249 try:
250 with open(f"{config_dir}/days-to-work", 'r') as extra_days_to_work_file:
251 for line in extra_days_to_work_file:
252 stripped_line = line.strip()
253 if stripped_line:
254 splitLine = stripped_line.split(' ')
255 if len(splitLine) == 2:
256 [hours, date] = splitLine
257 day = datetime.strptime(date, date_format).replace(tzinfo=tzlocal()).date()
258 extra_days_to_work[day] = timedelta(hours = float(hours))
259 else:
260 extra_days_to_work[datetime.strptime(stripped_line, date_format).replace(tzinfo=tzlocal()).date()] = time_per_day
261 except IOError as e:
262 if e.errno != 2:
263 raise e
264
265
266 self.is_workday = self.now.date() in days_to_work or self.now.date() in extra_days_to_work
267
268 self.time_worked = timedelta()
269
270 if self.include_running:
271 self.running_entry = api.get_running_clock(self.now)
272
273 if self.running_entry:
274 self.time_worked += self.running_entry
275
276 if self.running_entry and self.include_running and self.force_day_to_work and not (self.now.date() in days_to_work or self.now.date() in extra_days_to_work):
277 extra_days_to_work[self.now.date()] = timedelta()
278
279 self.time_to_work = sum([days_to_work[day] for day in days_to_work.keys() if day <= end_date.date()], timedelta())
280 for day in [d for d in list(pull_forward) if d > end_date.date()]:
281 days_forward = set([d for d in days_to_work.keys() if d >= end_date.date() and d < day and (not d in pull_forward or d == end_date.date())])
282 extra_days_forward = set([d for d in extra_days_to_work.keys() if d >= end_date.date() and d < day and (not d in pull_forward or d == end_date.date())])
283 days_forward = days_forward.union(extra_days_forward)
284
285 extra_day_time_left = timedelta()
286 for extra_day in extra_days_forward:
287 day_time = max(timedelta(), time_per_day - extra_days_to_work[extra_day])
288 extra_day_time_left += day_time
289 extra_day_time = min(extra_day_time_left, pull_forward[day])
290 time_forward = pull_forward[day] - extra_day_time
291 if extra_day_time_left > timedelta():
292 for extra_day in extra_days_forward:
293 day_time = max(timedelta(), time_per_day - extra_days_to_work[extra_day])
294 extra_days_to_work[extra_day] += extra_day_time * (day_time / extra_day_time_left)
295
296 hours_per_day_forward = time_forward / len(days_forward) if len(days_forward) > 0 else timedelta()
297 days_forward.discard(end_date.date())
298
299 self.time_pulled_forward += time_forward - hours_per_day_forward * len(days_forward)
300
301 if end_date.date() in extra_days_to_work:
302 self.time_pulled_forward += extra_days_to_work[end_date.date()]
303
304 self.time_to_work += self.time_pulled_forward
305
306 self.time_worked += api.get_billable_hours(start_date, self.now, rounding = config.getboolean('WORKTIME', 'rounding', fallback=True))
307
308def worktime(**args):
309 worktime = Worktime(**args)
310
311 def format_worktime(worktime):
312 def difference_string(difference):
313 total_minutes_difference = round(difference / timedelta(minutes = 1))
314 (hours_difference, minutes_difference) = divmod(abs(total_minutes_difference), 60)
315 sign = '' if total_minutes_difference >= 0 else '-'
316
317 difference_string = f"{sign}"
318 if hours_difference != 0:
319 difference_string += f"{hours_difference}h"
320 if hours_difference == 0 or minutes_difference != 0:
321 difference_string += f"{minutes_difference}m"
322
323 return difference_string
324
325 difference = worktime.time_to_work - worktime.time_worked
326 total_minutes_difference = 5 * ceil(difference / timedelta(minutes = 5))
327
328 if worktime.running_entry and abs(difference) < timedelta(days = 1) and (total_minutes_difference > 0 or abs(worktime.running_entry) >= abs(difference)) :
329 clockout_time = worktime.now + difference
330 clockout_time += (5 - clockout_time.minute % 5) * timedelta(minutes = 1)
331 clockout_time = clockout_time.replace(second = 0, microsecond = 0)
332
333 if total_minutes_difference >= 0:
334 difference_string = difference_string(total_minutes_difference * timedelta(minutes = 1))
335 return "{difference_string}/{clockout_time}".format(difference_string = difference_string, clockout_time = clockout_time.strftime("%H:%M"))
336 else:
337 difference_string = difference_string(abs(total_minutes_difference) * timedelta(minutes = 1))
338 return "{clockout_time}/{difference_string}".format(difference_string = difference_string, clockout_time = clockout_time.strftime("%H:%M"))
339 else:
340 if worktime.running_entry:
341 difference_string = difference_string(abs(total_minutes_difference) * timedelta(minutes = 1))
342 indicator = '↓' if total_minutes_difference >= 0 else '↑' # '\u25b6'
343
344 return f"{indicator}{difference_string}"
345 else:
346 difference_string = difference_string(total_minutes_difference * timedelta(minutes = 1))
347 if worktime.is_workday:
348 return difference_string
349 else:
350 return f"({difference_string})"
351
352 if worktime.time_pulled_forward >= timedelta(minutes = 15):
353 worktime_no_pulled_forward = deepcopy(worktime)
354 worktime_no_pulled_forward.time_to_work -= worktime_no_pulled_forward.time_pulled_forward
355 worktime_no_pulled_forward.time_pulled_forward = timedelta()
356
357 difference_string = format_worktime(worktime)
358 difference_string_no_pulled_forward = format_worktime(worktime_no_pulled_forward)
359
360 print(f"{difference_string_no_pulled_forward}…{difference_string}")
361 else:
362 print(format_worktime(worktime))
363
364def time_worked(now, **args):
365 then = now.replace(hour = 0, minute = 0, second = 0, microsecond = 0)
366 if now.time() == time():
367 now = now + timedelta(days = 1)
368
369 then = Worktime(**dict(args, now = then))
370 now = Worktime(**dict(args, now = now))
371
372 worked = now.time_worked - then.time_worked
373
374 if args['do_round']:
375 total_minutes_difference = 5 * ceil(worked / timedelta(minutes = 5))
376 (hours_difference, minutes_difference) = divmod(abs(total_minutes_difference), 60)
377 sign = '' if total_minutes_difference >= 0 else '-'
378
379 difference_string = f"{sign}"
380 if hours_difference != 0:
381 difference_string += f"{hours_difference}h"
382 if hours_difference == 0 or minutes_difference != 0:
383 difference_string += f"{minutes_difference}m"
384
385 print(difference_string)
386 else:
387 print(worked)
388
389def diff(now, **args):
390 now = now.replace(hour = 0, minute = 0, second = 0, microsecond = 0)
391 then = now - timedelta.resolution
392
393 then = Worktime(**dict(args, now = then, include_running = False))
394 now = Worktime(**dict(args, now = now, include_running = False))
395
396 print(now.time_to_work - then.time_to_work)
397
398def holidays(now, **args):
399 config = Worktime.config()
400 date_format = config.get('WORKTIME', 'DateFormat', fallback='%Y-%m-%d')
401
402 table_data = []
403
404 holidays = Worktime.holidays(now.year)
405 for k, v in holidays.items():
406 kstr = k.strftime(date_format)
407
408 table_data += [[kstr, v]]
409 print(tabulate(table_data, tablefmt="plain"))
410
411def main():
412 parser = argparse.ArgumentParser(prog = "worktime", description = 'Track worktime using toggl API')
413 parser.add_argument('--time', dest = 'now', metavar = 'TIME', type = lambda s: datetime.fromisoformat(s).replace(tzinfo=tzlocal()), help = 'Time to calculate status for (default: current time)', default = datetime.now(tzlocal()))
414 parser.add_argument('--no-running', dest = 'include_running', action = 'store_false')
415 parser.add_argument('--no-force-day-to-work', dest = 'force_day_to_work', action = 'store_false')
416 subparsers = parser.add_subparsers(help = 'Subcommands')
417 parser.set_defaults(cmd = worktime)
418 time_worked_parser = subparsers.add_parser('time_worked', aliases = ['time', 'worked', 'today'])
419 time_worked_parser.add_argument('--no-round', dest = 'do_round', action = 'store_false')
420 time_worked_parser.set_defaults(cmd = time_worked)
421 diff_parser = subparsers.add_parser('diff')
422 diff_parser.set_defaults(cmd = diff)
423 holidays_parser = subparsers.add_parser('holidays')
424 holidays_parser.set_defaults(cmd = holidays)
425 args = parser.parse_args()
426
427 args.cmd(**vars(args))
428
429if __name__ == "__main__":
430 sys.exit(main())
diff --git a/system-profiles/default-locale.nix b/system-profiles/default-locale.nix
new file mode 100644
index 00000000..9775c095
--- /dev/null
+++ b/system-profiles/default-locale.nix
@@ -0,0 +1,7 @@
1{...}:
2{
3 i18n.defaultLocale = "en_US.UTF-8";
4 console.keyMap = "dvp";
5
6 time.timeZone = "Europe/Berlin";
7}
diff --git a/system-profiles/initrd-all-crypto-modules.nix b/system-profiles/initrd-all-crypto-modules.nix
new file mode 100644
index 00000000..6b1da298
--- /dev/null
+++ b/system-profiles/initrd-all-crypto-modules.nix
@@ -0,0 +1,7 @@
1{...}:
2{
3 boot.initrd.luks.cryptoModules = [
4 "serpent_generic" "algif_rng" "authencesn" "crct10dif_generic" "blowfish_generic" "aegis128" "crc32c_generic" "md4" "lz4hc" "cbc" "adiantum" "authenc" "seqiv" "ecdh_generic" "842" "pcbc" "curve25519-generic" "sha256_generic" "cmac" "async_tx" "async_raid6_recov" "async_memcpy" "async_xor" "gcm" "ccm" "async_pq" "sha512_generic" "echainiv" "anubis" "blowfish_common" "algif_hash" "tgr192" "ghash-generic" "crypto_simd" "michael_mic" "ansi_cprng" "cast_common" "rmd128" "sm4_generic" "twofish_common" "wp512" "zstd" "cast5_generic" "algif_skcipher" "crc32_generic" "sm3_generic" "nhpoly1305" "cryptd" "twofish_generic" "crypto_user" "af_alg" "des_generic" "rmd320" "salsa20_generic" "xts" "xxhash_generic" "ecrdsa_generic" "deflate" "rmd256" "camellia_generic" "lrw" "xor" "gf128mul" "ecc" "arc4" "crypto_engine" "ecb" "lz4" "xcbc" "aes_ti" "khazad" "streebog_generic" "cast6_generic" "blake2b_generic" "keywrap" "chacha_generic" "tea" "aes_generic" "fcrypt" "cts" "chacha20poly1305" "essiv" "hmac" "vmac" "poly1305_generic" "sha3_generic" "rmd160" "algif_aead" "ctr" "crct10dif_common" "jitterentropy_rng" "pcrypt" "serpent-avx-x86_64" "cast5-avx-x86_64" "twofish-x86_64-3way" "sha1-ssse3" "seed" "cfb" "blake2s_generic" "ofb" "cast6-avx-x86_64" "twofish-x86_64" "drbg" "serpent-sse2-x86_64" "camellia-aesni-avx2" "crct10dif-pclmul" "sha256-ssse3" "sha512-ssse3" "crc32-pclmul" "camellia-x86_64" "curve25519-x86_64" "nhpoly1305-avx2" "ghash-clmulni-intel" "poly1305-x86_64" "aegis128-aesni" "camellia-aesni-avx-x86_64" "blowfish-x86_64" "nhpoly1305-sse2" "crc32c-intel" "aesni-intel" "blake2s-x86_64" "twofish-avx-x86_64" "glue_helper" "chacha-x86_64" "serpent-avx2" "des3_ede-x86_64" "asym_tpm" "pkcs7_test_key" "tpm_key_parser"
5 "encrypted_keys"
6 ];
7}
diff --git a/system-profiles/openssh/default.nix b/system-profiles/openssh/default.nix
new file mode 100644
index 00000000..4db3d7db
--- /dev/null
+++ b/system-profiles/openssh/default.nix
@@ -0,0 +1,36 @@
1{ customUtils, lib, config, hostName, ... }:
2{
3 services.openssh = {
4 enable = true;
5 knownHosts = lib.zipAttrsWith (_name: values: builtins.head values) (lib.mapAttrsToList (name: lib.mapAttrs' (type: value: lib.nameValuePair "${name}-${type}" value)) (customUtils.recImport { dir = ./known-hosts; }));
6
7 hostKeys = [
8 { path = "/etc/ssh/ssh_host_rsa_key";
9 type = "rsa";
10 }
11 { path = "/etc/ssh/ssh_host_ed25519_key";
12 type = "ed25519";
13 }
14 ];
15 };
16
17 sops.secrets = {
18 ssh_host_rsa_key = {
19 key = "rsa";
20 path = "/etc/ssh/ssh_host_rsa_key";
21 sopsFile = ./host-keys + "/${hostName}.yaml";
22 };
23 ssh_host_ed25519_key = {
24 key = "ed25519";
25 path = "/etc/ssh/ssh_host_ed25519_key";
26 sopsFile = ./host-keys + "/${hostName}.yaml";
27 };
28 };
29
30 environment.etc = {
31 "ssh/ssh_host_rsa_key.pub".text = config.services.openssh.knownHosts."${hostName}-rsa".publicKey;
32 "ssh/ssh_host_ed25519_key.pub".text = config.services.openssh.knownHosts."${hostName}-ed25519".publicKey;
33 };
34
35 systemd.user.services."ssh-agent".enable = lib.mkForce false; # ssh-agent should be done via home-manager
36}
diff --git a/system-profiles/openssh/host-keys/sif.yaml b/system-profiles/openssh/host-keys/sif.yaml
new file mode 100644
index 00000000..ddef6dd5
--- /dev/null
+++ b/system-profiles/openssh/host-keys/sif.yaml
@@ -0,0 +1,34 @@
1ed25519: ENC[AES256_GCM,data:R7Ejs0DrCJOtEquvxuPCpwrOvV1xwCRtSMgzt7H6Dbv5z3zp94Ei8WKRPfju9dSz/4etHa1FV+1Zy7pAExWOOLU6qvaj4ZQa1FEMnJH76SN974D0hp2TON1l7QS/uRfopJJ0vnzITeCmeQcvvv0Rdz7ZUmyfPv8e76/k3h7FsGndu4wEkVg1/0a+E2dNST+/cp+l8RjXljYTiVLAByaMNz1XoK6wupef40Ce11zAGSmJS57gCwmc2yyq01sgnwex1TeDi+Pd43dTR/21n7AssqnpZGsSqpC4+RzHnxP3YGHN1dLTjHZ5fWW+zEHJZ/lG2eW4Gful+TnQ3fw2SHCjW/9BxpCjzo8GAByuJEr569fRXXAiDnmLG8GXGCpQDgSpjdkL4bFEDs0Uss3ydJEmwL3DaNkr/SHUyovwE2k5KnfIt6v0uEH+HsDSPRQpVoOgn7q3GgDmqwADfGt8MStdFVo3el/s6Rs+Q6r/ukYYu+Mon7Akv7HPGAnHDBSGOPBwy/a5Di3AA0TH/CHCmNdv,iv:HD2JAEUDz5BvZDOMAxb83UjoGZBewdePfSktD5Vh7qw=,tag:CIcXaGYLFeJrp+AU3dpStQ==,type:str]
2rsa: ENC[AES256_GCM,data:48rCmH0Id6ACaz4oGNfb72sIhfP5P8flVU4DniyTnRbaS98CIg9B3Utj7kZQYwFOOT/esQY7o/82udh8vW2j3D5eF6HfJPZa6ata5SV5b0HIZd+HMNEz2eo4FQ+ev5JWRA0I0FlYMYM/3+w855d2qolHc+voQf/+g4edlU460hx9rbhyn4injrGFdK/AxCNUwB1YXSB+UzRAV3p3U6IXd4ULQydWjJnm5lePZdBEa2q5ZKggFUVoF59fCrKPuxoRuiQTFB9UX2cRxS/MDp70m76NuxP93wGNFRfqWsVxZW7d5WH42ifLYTpIdnB0nRV4VcYdZbVxSTH2/CdRTrQ+BXut4pEsrxq91k6H3EM1FhYYDYTO+fOoKhpe/uidBTuqf3m2A7pM4zdZoTfT/0dcOxIDbtDl+2xfZEiXwXPEsoPAzLcnA23CasY0rc3I+xFgI2yIa+DFUXTiwOuqMbTRooacYVGWa5UsYO0JztizNNgp+GApxBu5fco3J2TVVBIaOpoeRZsX3BlT/8NdR3DBw00ERdj3UVBOUCNV9s++3BfnQgXDdSk/FJ0otrgXQgYxiUN6jbldP+vxt/b4S+tALu7iVApjS0UHP76zvnYotcW57UCX0Gpf9zJc2jPjxGmtIkenVSNB9ZufRg0zvGQ1qN6Ct3j/U2Ka/mfs5AE8PXYC6wFxqlOB5P5OdmVXF9srtnTirgeWbsA4Jeg+OvwAq9DbIvKDDHk0miVMu7JRsPsuCpGjgA1hHHunqFJ6vwKwKvF9k1kCmd61Kgj5MuF4BDnfRC4V22g84eO0ojJH2vU8vzjF9k5MA5zznGYgojZAtQnCXcC3k9CFbPjJE8fqk4fJ3kKoZZy+M3d4rn7JY4HZfjVs2iHZtQv7CskAi9JSULPCEirRsMnzm7jvKVUm0viS4E8dWeUoEDtMnZ1PU/IeWxlJua9hkzWmH3HqigmUq22SBTVJeb24KXyLJcvaeOsoQMOwmdJm7VUYqM8h9L8iomFf1bbExgfmhN20ACCs39sgW6wrW3ODmW85BiC/QkdApxEk65fjL+T23fN1KbXf9la/xz2FCXGUdgKMjUc+HdCtOB7VgvIehfRKodgq4l9E0zIkukT+4RdHiEA0GVga9sesLsLVtgM0UzBca7Sx9NSxEjRNrOxABohu30WPaFHoIOGg+szpD3GOGI4EpmwPK3NEjfG/RTG0ncTf910Shr9OTO69DUcoN9RVR/j6Kq7+WG9G9KxiSn2Qa0F5KFj+Pq5oIPIvGR4YkMPAvlWvvXtfTwweLr8OsNpd716WKap75iykjleeE7qGWPiFtyaiBn98VzZ9e85I8gPVCZcDTtMNXxATYDd1q0yzKV87dOL2SuzC1a2t8htb3lQuugemSfXcoLlYTdZloCClb63oLbLkDtmfd3WCpUFFlYSIxbnamixqdeLeiSL7FwkF/QpUhw0ILm+EQrZGiQ+JcOyPiiJQTDmtcAS6LbprHmapMeQCMtP3/Jk6WdfN5Jhj0x7feVJfATIoLPx0aqox7ekbU4aSzZ/8qjxlMqBcsHUEKfKCoZdVOwuklEKPhPJzGAyom/FNIVrU4EjSxcPtotMP/munJ1BWHFL5UJWPoTAZHz3Ywt50e9Fpwz9coo5NbPhRtFqVs4bOeFqJb1kq6/gPMY7J57+QSetcbBJvwVzCBsQSHoUHgvnIn7kAwM8QbfpilKZd5McL6AiGUtQbAgalXtptnnI/U59IJ8ILkdffQioM0PmzXRdTcSpe6pS6M60stQPwFE4Z9+LHr5LBuuM34YkCq1jd4EMWti+KbEFzipvdAfCYLVdacCqPXzEg3mZ00A/xCXkKk4GDpOIbC3u2T6d5UyIY6b5aX1ZJEbTa1ndVU41+Rt5z0x8tuJkqNVRK8VJ2aVqc8t+o7Ga9IRV9YcIPtDWYcVRt6zTz+DqFEmTRrnBO0AwXdT+eGrMn6X3Nj+DIjPf2B5Nve/98PyRZDRlXkcAKe5FLrVqh1d888MX7pfH+BK8I4kmFokqitxKMw63aeakjpotgN1jUdMtZR9weo6pp4TCStQwlRA41fMLgjxiiZquiajxz1zTlYKr2CVpaw6fDLkBvUw44+ZNHHn75O1/lCuT6MmK9Bu1TwsNsNd4JQSpeY0KHF6VZM0EaS3rIsnZz61X02fdpmo6Nw0mFtB4AidghSI1l+tKVVMnYJqB48eU0w+PHhdkWsi6F3Agp4L4043tWr1o4+WGBnOnx0/rNUEEu30Dtwsty0V87k1c5GRo8PKnzSHtis+QaDOL+xSIVMhsFOcuLV+VhVm84NWjgpKcCb++NInR2BbAwNRBArdIaOfQVw/HlkfnAJNWmOr6y8db1PEPNQVPVflb8bfMsZjZpBy6w1JN2Y15ZL7L0tltyUyDomT3IfMURpIW6OoyaIQS+T6NAkqq0bOllnkRnBziTNae57zl/iNg4AlWtq0NRSed0ls7Dr3yYe5Y+JvGmuW+HEA1r1/Yp9ZYQrhAc6qmAZU4+XuOgANr/4tRFA2091M05+Ow0QMquZr5odLlSyVxDU+5P/+lxb4U2ltLg/lcDeTT7qJZt3vqNVRaWg+864uUO5GFtq56x+egmYpXrgGtIRjABA6oxEHRH8RENeVMIpriv0KQ0WrGvlCnZYOMkydN1YBIX9B5gXZQBDzN3POKyo7KiBtcyMi+iOkH15AaB6G5yZymckAsfVzA/VPi/M115vjOtcqK/yyYbKpzF5ZybRlDlwsWww8aymN5i9b6q0ZI1iQoV/8P8lnal1ziYABr1s30ZqYByZ3PS1OIg6Qc/UtFT8C0OhtBPtwPEy/Xalvtqhl8/9y4Yuv6D5MNCwbL5oQ2JafEaFCOslGklg0ORCwgF+Ho5ZcZPB1tK2c424wde+KBDrBDfDrrCrYoxpeuvpFoRyy0DF3xh+25OkBfxb7+VXQxn3wXDBR7Yr1tuBLQUlbiZ0d4uPj9Pw7RA4UQTMXCRfmUiHP2nTdCbBg41M/5Cx6xaOzXJPOrkvsCZnXvNpc7+nkynsHYMqOuuWuuedh4WMU7YEPZt4CasJ5iofrhjzJNI5G5+rTMwaLTNA5AM2hi9DPqvFOaRUv/noYlTxtBT3LzUfgprPYnCKQd4j+6wO/bfbrlQq8WFH+RwFw4W0eMTJRFYFF2ctX0HOi9TOyTTHh/4l/mzTi4OaeFi5GsboDeczeh4acQCt5IjJih4qRnB7fdQy7PF8ccNuGvKWpN2mnDZJnoCrjbX8ihwzePzYPkUQ/ZKUI4KWVuixiis8b+CH0p92/dSOzGvP2MeQVMwRMpqk2EZdj3oE/gYG7RvfgM12N5ZLAUACczbwS17rquSwgznojewY+g/8oTolY81w4P/hPqX6FhWVeeZZaBJVKVDJSm4Aqp67FSYtoOHkEsOROtqklA7JVourGOgU6DfHlaSGGU6khAPv0smaYgi7+8tztNHoaaGSrtWU5+fxEzfD+NrOLm6Wz+ZZNvxWMMwrwkdeYqSc1tJACNo6CDtN/Tx+ZZCVVE+O+Qi5HFHx13c/ChQhRVMCha0VS8sYMokcZ5H2FLFLApOpwYLLFAiNwWr18PxRf3830xRwSM5bbASl9XLz2xxrbzpApDz5EDvvWJ/lPqnxYxVKCY9cEmDLuRtpEezTUpC8t6LqxhkqCTGiHjto668Mx8OTCDI8ys/D2qOFojbDyYIzl3l+AiVucXHXUrOkPJhhiOufiR6rks5UYqC6Mffb14e8JAY4IGsszmPqVY0hFS9AWp/aHuQ61stnMmhNxUw11fCDuUd4R+NvQlWSmN4Nyv0JmC6rHaa+Y/FEIQTCmFRiutTX+0nLPwJYKx+oQYklRDZ1DLtqMcF7PPUZGSFnZGGLN6UxoDhmrwz6NYwn+bBViL6iWFIgLbpRme/3TFBplDVIDUsSmbqsiysOmtzjw3y4YgiYmlZoEc//WK109KVrQUDSKKpjCMki6wsJ+27v5rlvhyBrG8QMUqWtTTB7Xu63iRrlASXcLk8Y99ehrxw/R3bjyXu5jHe0sowoKATerpiukBIOBTzPyFWQp/8MG6wWTzDM+HyGrAoCnkC3RanF56ldyqBzbqMbBIOx+fRWWuUl27YTwWLaPoZbO/+kgwzmguUj+bfqxExa4OTiMamLoZQh3LfIIJhQMd6MR1SDpRNg6D4YI7ftzrgQIe/CjjlWAfzkgeaPLx7Qeer0Te7wdJymWnQs5NBNAdWGcBgAS7/h4JZPBaqvd8KYPl2cg0Mv9k6+ogExv8YAz25rRPaJB1pKbhB5bkNIBRbUzCUhD3nk5uumvgKS0p6qn8hcD1CqEPt+ZH2CemlP5wjt9aHxplVZgG5vFZX0SREGDTSKwQoUbig1cHmRMOkO7vgS3B8eB7/J1UK3QeQe6roQSM80kbcLYH2flUmqQluGO1ZZDUttlp0ACP2lwlaBysp4X3KYPIwiHf1mXH/EQqgt305FNiKynDV6or7VWYQe5CXvTGg,iv:X57Ayvq6r0m1SGeVrBH8WCZ7TihobLLhy7spX4NIly8=,tag:caDTP5SwuWJAWGpwr9x0eQ==,type:str]
3sops:
4 kms: []
5 gcp_kms: []
6 azure_kv: []
7 hc_vault: []
8 lastmodified: '2021-01-02T19:05:26Z'
9 mac: ENC[AES256_GCM,data:yJGzs0W0R+b6WPkUaQc9cxeTBBEXot0ffUAG77Of88kREFsD5ams9qEDCs8LhPhMtLSH5L8bqMLF28n2w6d9gf41NDBl/oj+XTJE26c4D+MWF2A0fqTvwv1l3524TfavVU8iur0bCbytNfcHSZ3zCQAYElswOGupO+K0Y3hwKKI=,iv:jHSgQV6Jg2Yckp8G0Z23Ny74ZQxZ/+C/neXKrEWUVak=,tag:DhOr2cVhIq8i4JAO+fdXxA==,type:str]
10 pgp:
11 - created_at: '2021-01-02T19:04:29Z'
12 enc: |
13 -----BEGIN PGP MESSAGE-----
14
15 hF4Dgwm4NZSaLAcSAQdArkswGx9w0Rbfp1N89qALAbPMhboirsnlNvms/FomXiUw
16 taW9n4oEJ5oW2UYzNNn72SwF1jYbrqczAbxt3dM9PSz1gHFoh+ZJhGokVFJvJ7sO
17 0l4BEOkWmL/9uyOiCq574nH6OxxTPu9C4GNU8lv/Z/qJ+oAocJkGknsIJzd8M5ax
18 Fo/HqAGGfvnH3RI5FO3tTxfAKlfxlO2MJ2lsCypJuez5WewPnaTPjTbogjhzG2aQ
19 =HXLp
20 -----END PGP MESSAGE-----
21 fp: F1AF20B9511B63F681A14E8D51AEFBCD1DEF68F8
22 - created_at: '2021-01-02T19:04:29Z'
23 enc: |
24 -----BEGIN PGP MESSAGE-----
25
26 hF4DXxoViZlp6dISAQdAUSTwFAciB+Yh2IieFoN/xmQd+GU/g+cuKej6cZk78TUw
27 ETM8c1TSovML5q9usUX0pl/AbRBwp2In47RMkTn4Mul1XxJuXhgCnrc5swwYrS+h
28 0l4BOxJ3bF/yYyKfGrmc/mNe51sdHH+fgQ9IXaQhcopw4kyZqvBXhJF/oP/mhnOL
29 VMhsfg50ol1XmXVefyo5JPedbzABm3vRZv9U+/zvKNJxIro2hWchd5CxvzN4l/MR
30 =30r5
31 -----END PGP MESSAGE-----
32 fp: 30D3453B8CD02FE2A3E7C78C0FB536FB87AE8F51
33 unencrypted_suffix: _unencrypted
34 version: 3.6.1
diff --git a/system-profiles/openssh/known-hosts/sif.nix b/system-profiles/openssh/known-hosts/sif.nix
new file mode 100644
index 00000000..8326d389
--- /dev/null
+++ b/system-profiles/openssh/known-hosts/sif.nix
@@ -0,0 +1,16 @@
1let
2 hostNames = ["sif.asgard.yggdrasil" "sif.faraday.asgard.yggdrasil" "sif.midgard.yggdrasil"];
3in {
4 rsa = {
5 inherit hostNames;
6 publicKey = ''
7 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCeFqJep1CuWakcoiAkz4bSaHbAIwM89Er46o3KUpjCWGTmDmhJyBiG38pupcctH0awwElkX09GsNx230mTtjT6qcxN+vGsGMJIqFD+/7ZobSLJDHYCo6Jx23jZUjg1SqxYjwB5ooWGI61Vh6SaOy8WRrUn0q8rJyd9SEC+3tJlKO5QqRi/Vnwzj47m+YjGz2UlqJ9a4GeRh1O5SiGx5jd4a/VoeK1XJcW94XeWpPQdUGnVYUXZn9cwYVrogmXdr18ImnPxghsQg4xwS2A6KMjUw9m56XkqIq7vTslmL9JaYcjlSCHbsSVq9+Wu1oKxoyndN7Sim7SkAZwHFUEMBNlontBitgYl6z10VdKX739os6h07uXjGEs+mPk4/CkGZhvhnErV2T9FO+65jnU3mZkeX5tfJHqJ8PnDch2JD6O7+Mjpce4zs/x3mwH36peER6iiIBYGlSF0AlUDShdqj+fPWFu6gZ9piOAZ2L3YXDA0ulM6pL69SbulrUNOwtTy6LkBfKDwpaQK1KO1VOYBaKa7s+krOJXW18k+tpfo4aKSeTuwvykMPndKMKvxcsxNymkGo2AzLw017Qgshzv9rRbLNMBFd85S3krakGyBVL0HAVrAdkjvsWqj5FnHAjgBc1AZnZPbJu3g9/wm7k8rPMV0jxKMpW+zxjVFYDhFUWYp9w==
8 '';
9 };
10 ed25519 = {
11 inherit hostNames;
12 publicKey = ''
13 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOfiwlzGcNQjamtIwv7fmXnddjajraeovaM6gRNui1+v
14 '';
15 };
16}
diff --git a/system-profiles/openssh/known-hosts/ymir.nix b/system-profiles/openssh/known-hosts/ymir.nix
new file mode 100644
index 00000000..f29baf1d
--- /dev/null
+++ b/system-profiles/openssh/known-hosts/ymir.nix
@@ -0,0 +1,16 @@
1let
2 hostNames = ["ymir.yggdrasil.li" "ymir.niflheim.yggdrasil"];
3in {
4 rsa = {
5 inherit hostNames;
6 publicKey = ''
7 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDNr7oFNneR3sVuAhdbnU83PuG6gTU6rDmiz+qykkRUr5Qdtm0NIr9lI7nhoO/MaALWmkMXsBGjvJ2UxvY959g0wQRHJZnuJDwOMo3YJjfuDGMTtp8ikzd646uMHQB+y/xb4dou6f0INr94eRsZcji7AQgZQnyWVV3DZuSADBfNK0Tx6sT6IdbJXaCwYoexnfSfzDdu3i5zMuReF4zdkFUEfAdcbOM8Cr0Abnn4+iLVrof/QaOEuZDC+Pf5QUhkAArETdavSCUIbV6+1md0jz/T8yalgrTCsYOoEUbSPwM/8vmiYDWSo/tvAf3KnVIPjjK2UFz7Qu0HyK0y1dBEXoYLGZ1ep4x67aE4zy7GlR2GZdAYilHknugZB+/kvYGDEixHFfcUh/uvF5PY8sm63C6HUBT1s/aQHXGHgE4uUru6YvbU3UW3fRdslABY/atZ9gc3MuKu9Zk27b1SYfAAoK1R8rKsOKWqUWvvMVCfKBNKqqb7+30q75iGeneB8Tb1C9lToyDG2Yl5p+Gpfnj8YmaU/xFm0HFEC42crRbaQyz01LmupHWf8VwH/O2LsjztAF9b4Oe2q/NwqQAF+h5hIm2tfM2fzxHGCmw1sFYf6dEdkyV5pge/IJrnuQn27iO06tRC6tvrt/ocbpwEEOk/3WWpAWW4oT8L5ceh7iAXrCRWpw==
8 '';
9 };
10 ed25519 = {
11 inherit hostNames;
12 publicKey = ''
13 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDeBBux2bIXnS/RUv+Y/NCpzI/SCW0KOJSzf48KDiEZD
14 '';
15 };
16}
diff --git a/system-profiles/sudo.nix b/system-profiles/sudo.nix
new file mode 100644
index 00000000..f2401b9f
--- /dev/null
+++ b/system-profiles/sudo.nix
@@ -0,0 +1,39 @@
1{ ... }:
2{
3 security.sudo.extraRules = [
4 { groups = "wheel";
5 commands = map (command: { inherit command; options = "NOPASSWD"; }) [
6 "/run/current-system/sw/sbin/shutdown"
7 "/run/current-system/sw/sbin/reboot"
8 "/run/current-system/sw/sbin/halt"
9 "/run/current-system/sw/bin/systemctl"
10 ];
11 }
12 ];
13
14 users.extraGroups.network = {};
15
16 security.polkit = {
17 enable = true;
18 extraConfig = ''
19 polkit.addRule(function(action, subject) {
20 if ( action.id == "org.freedesktop.systemd1.manage-units"
21 && subject.isInGroup("wheel")
22 ) {
23 return polkit.Result.YES;
24 }
25 });
26
27 polkit.addRule(function(action, subject) {
28 if ((action.id == "org.blueman.rfkill.setstate" ||
29 action.id == "org.blueman.network.setup" ||
30 action.id == "org.freedesktop.NetworkManager.settings.modify.system"
31 ) && subject.local
32 && subject.active && subject.isInGroup("network")
33 ) {
34 return polkit.Result.YES;
35 }
36 });
37 '';
38 };
39}
diff --git a/user-profiles/direnv.nix b/user-profiles/direnv.nix
new file mode 100644
index 00000000..2c1e58d6
--- /dev/null
+++ b/user-profiles/direnv.nix
@@ -0,0 +1,9 @@
1{ userName, ... }:
2{
3 home-manager.users.${userName} = {
4 programs.direnv = {
5 enable = true;
6 enableNixDirenvIntegration = true;
7 };
8 };
9}
diff --git a/user-profiles/mpv/default.nix b/user-profiles/mpv/default.nix
new file mode 100644
index 00000000..045094db
--- /dev/null
+++ b/user-profiles/mpv/default.nix
@@ -0,0 +1,83 @@
1{ config, userName, pkgs, ... }:
2{
3 home-manager.users.${userName}.programs.mpv = {
4 enable = true;
5 bindings = {
6 "CTRL+n" = "af toggle \"lavfi=[dynaudnorm=f=100:g=31:s=20.0]\"";
7 };
8 config = {
9 ytdl = true;
10 ytdl-format = "bestvideo[width<=2560][height<=1440][fps<=60][protocol!=http_dash_segments]+bestaudio[protocol!=http_dash_segments]/best[width<=2560][height<=1440][fps<=60][protocol!=http_dash_segments]/best[protocol!=http_dash_segments]";
11 ytdl-raw-options = "netrc=,mark-watched=,cookies=${config.home-manager.users.${userName}.home.homeDirectory}/Downloads/cookies.txt";
12 sub = false;
13 osd-font = "DejaVu Sans";
14 vo = "gpu";
15 hwdec = "auto";
16 force-window = "yes";
17 script-opts = "osc-layout=topbar,vidscale=no,deadzonesize=0.9";
18 af = "lavfi=[dynaudnorm=f=100:g=31:s=20.0]";
19 };
20 scripts = let
21 reload = pkgs.stdenv.mkDerivation rec {
22 version = "2b8a719f";
23 pname = "reload";
24 name = "${pname}-${version}";
25
26 src = pkgs.fetchFromGitHub {
27 owner = "4e6";
28 repo = "mpv-reload";
29 rev = "2b8a719fe166d6d42b5f1dd64761f97997b54a86";
30 sha256 = "19ycvnwzf8vgv0g63d4k1ll6hlfrd92is9gl8hzfic7w32ycphbg";
31 };
32
33 installPhase = ''
34 install -d $out/share/mpv/scripts
35 install -m 0644 reload.lua $out/share/mpv/scripts/${passthru.scriptName}
36 '';
37
38 passthru.scriptName = "reload.lua";
39 };
40 autosave = pkgs.stdenv.mkDerivation rec {
41 version = "0bv9wjrq";
42 pname = "autosave";
43 name = "${pname}-${version}.lua";
44
45 src = pkgs.fetchzip {
46 url = "https://gist.github.com/Hakkin/5489e511bd6c8068a0fc09304c9c5a82/archive/7a19f7cdb6dd0b1c6878b41e13b244e2503c15fc.zip";
47 sha256 = "0bv9wjrqm2ragd7rp8vw768bja2ghascwlljd6rzzf2ybi10fxs2";
48 };
49
50 installPhase = ''
51 install -d $out/share/mpv/scripts
52 install -m 0644 autosave.lua $out/share/mpv/scripts/${passthru.scriptName}
53 '';
54
55 passthru.scriptName = "autosave.lua";
56 };
57 mpris = pkgs.stdenv.mkDerivation rec {
58 version = "0.4";
59 pname = "mpv-mpris";
60 name = "${pname}-${version}.so";
61
62 src = pkgs.fetchFromGitHub {
63 owner = "hoyon";
64 repo = "mpv-mpris";
65 rev = version;
66 sha256 = "1fr3jvja8s2gdpx8qyk9r17977flms3qpm8zci62nd9r5wjdvr5i";
67 };
68
69 installPhase = ''
70 install -d $out/share/mpv/scripts
71 install -m 0644 mpris.so $out/share/mpv/scripts/${passthru.scriptName}
72 '';
73
74 nativeBuildInputs = with pkgs; [ pkgconfig glib mpv ];
75
76 passthru.scriptName = "mpris.so";
77 };
78 in [ reload
79 autosave
80 mpris
81 ];
82 };
83}
diff --git a/user-profiles/tmux/default.nix b/user-profiles/tmux/default.nix
new file mode 100644
index 00000000..9e66cadd
--- /dev/null
+++ b/user-profiles/tmux/default.nix
@@ -0,0 +1,26 @@
1{ userName, pkgs, lib, ... }:
2{
3 home-manager.users.${userName} = {
4 programs.tmux = {
5 enable = true;
6 clock24 = true;
7 historyLimit = 50000;
8 extraConfig = lib.readFile (pkgs.stdenv.mkDerivation {
9 name = "tmux.conf";
10 src = ./tmux.conf;
11
12 buildInputs = with pkgs; [ makeWrapper ];
13
14 phases = [ "installPhase" ];
15
16 inherit (pkgs) zsh;
17 mandb = pkgs.man-db;
18
19 installPhase = ''
20 substituteAll $src $out
21 '';
22 });
23 tmuxp.enable = true;
24 };
25 };
26}
diff --git a/user-profiles/tmux/tmux.conf b/user-profiles/tmux/tmux.conf
new file mode 100644
index 00000000..1403698d
--- /dev/null
+++ b/user-profiles/tmux/tmux.conf
@@ -0,0 +1,25 @@
1set-option -g history-limit 50000
2set-option -g status-bg black
3set-option -g status-fg white
4set-option -g clock-mode-colour white
5set-option -g clock-mode-style 24
6set-option -g bell-action any
7set-option -g default-shell @zsh@/bin/zsh
8set-option -g update-environment 'DISPLAY SSH_ASKPASS SSH_AUTH_SOCK SSH_AGENT_PID SSH_CONNECTION WINDOWID XAUTHORITY PROMPT_INFO PATH PGHOST PGLOG'
9set-option -g mouse on
10set-option -g set-clipboard on
11set-option -g terminal-overrides 'rxvt-uni*:XT:Ms=\E]52;%p1%s;%p2%s\007'
12
13## determine if we should enable 256-colour support
14if "[[ ''${TERM} =~ 256color || ''${TERM} == fbterm ]]" 'set -g default-terminal tmux-256color'
15
16set-option -g status-right ""
17
18bind / command-prompt "split-window -h 'exec @mandb@/bin/man %%'"
19bind C clock-mode
20bind r respawn-pane -k
21
22bind -n M-Left select-pane -L
23bind -n M-Right select-pane -R
24bind -n M-Up select-pane -U
25bind -n M-Down select-pane -D \ No newline at end of file
diff --git a/user-profiles/utils.nix b/user-profiles/utils.nix
new file mode 100644
index 00000000..7b9a0da7
--- /dev/null
+++ b/user-profiles/utils.nix
@@ -0,0 +1,23 @@
1{ userName, pkgs, ... }:
2{
3 home-manager.users.${userName} = {
4 programs = {
5 htop = {
6 enable = true;
7 delay = 5;
8 highlightBaseName = true;
9 treeView = true;
10 };
11
12 jq.enable = true;
13 };
14
15 home.packages = with pkgs; [
16 autossh usbutils pciutils exa ag pwgen unzip magic-wormhole
17 qrencode tty-clock dnsutils openssl sshfs psmisc mosh tree
18 vnstat file pv bc fast-cli zip nmap aspell aspellDicts.de
19 aspellDicts.en borgbackup man-pages rsync socat telnet yq
20 cached-nix-shell persistent-nix-shell rage
21 ];
22 };
23}
diff --git a/user-profiles/zsh/default.nix b/user-profiles/zsh/default.nix
new file mode 100644
index 00000000..88873c1a
--- /dev/null
+++ b/user-profiles/zsh/default.nix
@@ -0,0 +1,31 @@
1{ userName, pkgs, customUtils, lib, config, ... }:
2let
3 dotDir = ".config/zsh";
4 p10kZsh = "${dotDir}/.p10k.zsh";
5 cfg = config.home-manager.users.${userName};
6in {
7 home-manager.users.${userName} = {
8 programs.zsh = {
9 inherit dotDir;
10 enable = true;
11 autocd = true;
12 enableCompletion = true;
13
14 plugins = [
15 { name = "powerlevel10k";
16 file = "share/zsh-powerlevel10k/powerlevel10k.zsh-theme";
17 src = pkgs.zsh-powerlevel10k;
18 }
19 ];
20 initExtraBeforeCompInit = ''
21 source "${cfg.home.homeDirectory}/${p10kZsh}"
22 '';
23 initExtra = lib.mkAfter ''
24 source ${./zshrc}
25 source "${pkgs.zsh-syntax-highlighting}/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh"
26 '';
27 };
28
29 home.file.${p10kZsh}.source = ./p10k.zsh;
30 };
31}
diff --git a/user-profiles/zsh/p10k.zsh b/user-profiles/zsh/p10k.zsh
new file mode 100644
index 00000000..e3b364c2
--- /dev/null
+++ b/user-profiles/zsh/p10k.zsh
@@ -0,0 +1,1578 @@
1# Generated by Powerlevel10k configuration wizard on 2021-01-03 at 15:43 CET.
2# Based on romkatv/powerlevel10k/config/p10k-lean.zsh.
3# Wizard options: nerdfont-complete + powerline, small icons, unicode, lean, 24h time,
4# 2 lines, solid, no frame, darkest-ornaments, sparse, many icons, concise,
5# transient_prompt, instant_prompt=quiet.
6# Type `p10k configure` to generate another config.
7#
8# Config for Powerlevel10k with lean prompt style. Type `p10k configure` to generate
9# your own config based on it.
10#
11# Tip: Looking for a nice color? Here's a one-liner to print colormap.
12#
13# for i in {0..255}; do print -Pn "%K{$i} %k%F{$i}${(l:3::0:)i}%f " ${${(M)$((i%6)):#3}:+$'\n'}; done
14
15# Temporarily change options.
16'builtin' 'local' '-a' 'p10k_config_opts'
17[[ ! -o 'aliases' ]] || p10k_config_opts+=('aliases')
18[[ ! -o 'sh_glob' ]] || p10k_config_opts+=('sh_glob')
19[[ ! -o 'no_brace_expand' ]] || p10k_config_opts+=('no_brace_expand')
20'builtin' 'setopt' 'no_aliases' 'no_sh_glob' 'brace_expand'
21
22() {
23 emulate -L zsh -o extended_glob
24
25 # Unset all configuration options. This allows you to apply configuration changes without
26 # restarting zsh. Edit ~/.p10k.zsh and type `source ~/.p10k.zsh`.
27 unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR'
28
29 # Zsh >= 5.1 is required.
30 autoload -Uz is-at-least && is-at-least 5.1 || return
31
32 # The list of segments shown on the left. Fill it with the most important segments.
33 typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(
34 # =========================[ Line #1 ]=========================
35 # os_icon # os identifier
36 context # user@hostname
37 dir # current directory
38 vcs # git status
39 # =========================[ Line #2 ]=========================
40 newline # \n
41 context # user@hostname
42 dir
43 prompt_char # prompt symbol
44 )
45
46 # The list of segments shown on the right. Fill it with less important segments.
47 # Right prompt on the last prompt line (where you are typing your commands) gets
48 # automatically hidden when the input line reaches it. Right prompt above the
49 # last prompt line gets hidden if it would overlap with left prompt.
50 typeset -g POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(
51 # =========================[ Line #1 ]=========================
52 status # exit code of the last command
53 command_execution_time # duration of the last command
54 time # current time
55 background_jobs # presence of background jobs
56 # =========================[ Line #2 ]=========================
57 newline
58 direnv # direnv status (https://direnv.net/)
59 asdf # asdf version manager (https://github.com/asdf-vm/asdf)
60 virtualenv # python virtual environment (https://docs.python.org/3/library/venv.html)
61 anaconda # conda environment (https://conda.io/)
62 pyenv # python environment (https://github.com/pyenv/pyenv)
63 goenv # go environment (https://github.com/syndbg/goenv)
64 nodenv # node.js version from nodenv (https://github.com/nodenv/nodenv)
65 nvm # node.js version from nvm (https://github.com/nvm-sh/nvm)
66 nodeenv # node.js environment (https://github.com/ekalinin/nodeenv)
67 # node_version # node.js version
68 # go_version # go version (https://golang.org)
69 # rust_version # rustc version (https://www.rust-lang.org)
70 # dotnet_version # .NET version (https://dotnet.microsoft.com)
71 # php_version # php version (https://www.php.net/)
72 # laravel_version # laravel php framework version (https://laravel.com/)
73 # java_version # java version (https://www.java.com/)
74 # package # name@version from package.json (https://docs.npmjs.com/files/package.json)
75 rbenv # ruby version from rbenv (https://github.com/rbenv/rbenv)
76 rvm # ruby version from rvm (https://rvm.io)
77 fvm # flutter version management (https://github.com/leoafarias/fvm)
78 luaenv # lua version from luaenv (https://github.com/cehoffman/luaenv)
79 jenv # java version from jenv (https://github.com/jenv/jenv)
80 plenv # perl version from plenv (https://github.com/tokuhirom/plenv)
81 phpenv # php version from phpenv (https://github.com/phpenv/phpenv)
82 scalaenv # scala version from scalaenv (https://github.com/scalaenv/scalaenv)
83 haskell_stack # haskell version from stack (https://haskellstack.org/)
84 kubecontext # current kubernetes context (https://kubernetes.io/)
85 terraform # terraform workspace (https://www.terraform.io)
86 aws # aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html)
87 aws_eb_env # aws elastic beanstalk environment (https://aws.amazon.com/elasticbeanstalk/)
88 azure # azure account name (https://docs.microsoft.com/en-us/cli/azure)
89 gcloud # google cloud cli account and project (https://cloud.google.com/)
90 google_app_cred # google application credentials (https://cloud.google.com/docs/authentication/production)
91 nordvpn # nordvpn connection status, linux only (https://nordvpn.com/)
92 ranger # ranger shell (https://github.com/ranger/ranger)
93 nnn # nnn shell (https://github.com/jarun/nnn)
94 vim_shell # vim shell indicator (:sh)
95 midnight_commander # midnight commander shell (https://midnight-commander.org/)
96 nix_shell # nix shell (https://nixos.org/nixos/nix-pills/developing-with-nix-shell.html)
97 # vpn_ip # virtual private network indicator
98 # load # CPU load
99 # disk_usage # disk usage
100 # ram # free RAM
101 # swap # used swap
102 todo # todo items (https://github.com/todotxt/todo.txt-cli)
103 timewarrior # timewarrior tracking status (https://timewarrior.net/)
104 taskwarrior # taskwarrior task count (https://taskwarrior.org/)
105 status # exit code of the last command
106 command_execution_time # duration of the last command
107 time # current time
108 background_jobs # presence of background jobs
109 # ip # ip address and bandwidth usage for a specified network interface
110 # public_ip # public IP address
111 # proxy # system-wide http/https/ftp proxy
112 # battery # internal battery
113 # wifi # wifi speed
114 # example # example user-defined segment (see prompt_example function below)
115 )
116
117 # Defines character set used by powerlevel10k. It's best to let `p10k configure` set it for you.
118 typeset -g POWERLEVEL9K_MODE=nerdfont-complete
119 # When set to `moderate`, some icons will have an extra space after them. This is meant to avoid
120 # icon overlap when using non-monospace fonts. When set to `none`, spaces are not added.
121 typeset -g POWERLEVEL9K_ICON_PADDING=none
122
123 # Basic style options that define the overall look of your prompt. You probably don't want to
124 # change them.
125 typeset -g POWERLEVEL9K_BACKGROUND= # transparent background
126 typeset -g POWERLEVEL9K_{LEFT,RIGHT}_{LEFT,RIGHT}_WHITESPACE= # no surrounding whitespace
127 typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SUBSEGMENT_SEPARATOR=' ' # separate segments with a space
128 typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SEGMENT_SEPARATOR= # no end-of-line symbol
129
130 # When set to true, icons appear before content on both sides of the prompt. When set
131 # to false, icons go after content. If empty or not set, icons go before content in the left
132 # prompt and after content in the right prompt.
133 #
134 # You can also override it for a specific segment:
135 #
136 # POWERLEVEL9K_STATUS_ICON_BEFORE_CONTENT=false
137 #
138 # Or for a specific segment in specific state:
139 #
140 # POWERLEVEL9K_DIR_NOT_WRITABLE_ICON_BEFORE_CONTENT=false
141 typeset -g POWERLEVEL9K_ICON_BEFORE_CONTENT=true
142
143 # Add an empty line before each prompt.
144 typeset -g POWERLEVEL9K_PROMPT_ADD_NEWLINE=true
145
146 # Connect left prompt lines with these symbols.
147 typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_PREFIX=
148 typeset -g POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_PREFIX=
149 typeset -g POWERLEVEL9K_MULTILINE_LAST_PROMPT_PREFIX=
150 # Connect right prompt lines with these symbols.
151 typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_SUFFIX=
152 typeset -g POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_SUFFIX=
153 typeset -g POWERLEVEL9K_MULTILINE_LAST_PROMPT_SUFFIX=
154
155 # The left end of left prompt.
156 typeset -g POWERLEVEL9K_LEFT_PROMPT_FIRST_SEGMENT_START_SYMBOL=
157 # The right end of right prompt.
158 typeset -g POWERLEVEL9K_RIGHT_PROMPT_LAST_SEGMENT_END_SYMBOL=
159
160 # Ruler, a.k.a. the horizontal line before each prompt. If you set it to true, you'll
161 # probably want to set POWERLEVEL9K_PROMPT_ADD_NEWLINE=false above and
162 # POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR=' ' below.
163 typeset -g POWERLEVEL9K_SHOW_RULER=false
164 typeset -g POWERLEVEL9K_RULER_CHAR='─' # reasonable alternative: '·'
165 typeset -g POWERLEVEL9K_RULER_FOREGROUND=238
166
167 # Filler between left and right prompt on the first prompt line. You can set it to '·' or '─'
168 # to make it easier to see the alignment between left and right prompt and to separate prompt
169 # from command output. It serves the same purpose as ruler (see above) without increasing
170 # the number of prompt lines. You'll probably want to set POWERLEVEL9K_SHOW_RULER=false
171 # if using this. You might also like POWERLEVEL9K_PROMPT_ADD_NEWLINE=false for more compact
172 # prompt.
173 typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR='─'
174 if [[ $POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR != ' ' ]]; then
175 # The color of the filler.
176 typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_FOREGROUND=238
177 # Add a space between the end of left prompt and the filler.
178 typeset -g POWERLEVEL9K_LEFT_PROMPT_LAST_SEGMENT_END_SYMBOL=' '
179 # Add a space between the filler and the start of right prompt.
180 typeset -g POWERLEVEL9K_RIGHT_PROMPT_FIRST_SEGMENT_START_SYMBOL=' '
181 # Start filler from the edge of the screen if there are no left segments on the first line.
182 typeset -g POWERLEVEL9K_EMPTY_LINE_LEFT_PROMPT_FIRST_SEGMENT_END_SYMBOL='%{%}'
183 # End filler on the edge of the screen if there are no right segments on the first line.
184 typeset -g POWERLEVEL9K_EMPTY_LINE_RIGHT_PROMPT_FIRST_SEGMENT_START_SYMBOL='%{%}'
185 fi
186
187 #################################[ os_icon: os identifier ]##################################
188 # OS identifier color.
189 typeset -g POWERLEVEL9K_OS_ICON_FOREGROUND=
190 # Custom icon.
191 # typeset -g POWERLEVEL9K_OS_ICON_CONTENT_EXPANSION='⭐'
192
193 ################################[ prompt_char: prompt symbol ]################################
194 typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_{VIINS,VICMD,VIVIS,VIOWR}_FOREGROUND=2
195 # Default prompt symbol.
196 typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIINS_CONTENT_EXPANSION='❯'
197 # Prompt symbol in command vi mode.
198 typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VICMD_CONTENT_EXPANSION='❮'
199 # Prompt symbol in visual vi mode.
200 typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION='V'
201 # Prompt symbol in overwrite vi mode.
202 typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIOWR_CONTENT_EXPANSION='▶'
203 typeset -g POWERLEVEL9K_PROMPT_CHAR_OVERWRITE_STATE=true
204 # No line terminator if prompt_char is the last segment.
205 typeset -g POWERLEVEL9K_PROMPT_CHAR_LEFT_PROMPT_LAST_SEGMENT_END_SYMBOL=''
206 # No line introducer if prompt_char is the first segment.
207 typeset -g POWERLEVEL9K_PROMPT_CHAR_LEFT_PROMPT_FIRST_SEGMENT_START_SYMBOL=
208
209 ##################################[ dir: current directory ]##################################
210 # Default current directory color.
211 typeset -g POWERLEVEL9K_DIR_FOREGROUND=31
212 # If directory is too long, shorten some of its segments to the shortest possible unique
213 # prefix. The shortened directory can be tab-completed to the original.
214 typeset -g POWERLEVEL9K_SHORTEN_STRATEGY=truncate_to_unique
215 # Replace removed segment suffixes with this symbol.
216 typeset -g POWERLEVEL9K_SHORTEN_DELIMITER=
217 # Color of the shortened directory segments.
218 typeset -g POWERLEVEL9K_DIR_SHORTENED_FOREGROUND=103
219 # Color of the anchor directory segments. Anchor segments are never shortened. The first
220 # segment is always an anchor.
221 typeset -g POWERLEVEL9K_DIR_ANCHOR_FOREGROUND=39
222 # Display anchor directory segments in bold.
223 typeset -g POWERLEVEL9K_DIR_ANCHOR_BOLD=true
224 # Don't shorten directories that contain any of these files. They are anchors.
225 local anchor_files=(
226 .bzr
227 .citc
228 .git
229 .hg
230 .node-version
231 .python-version
232 .go-version
233 .ruby-version
234 .lua-version
235 .java-version
236 .perl-version
237 .php-version
238 .tool-version
239 .shorten_folder_marker
240 .svn
241 .terraform
242 CVS
243 Cargo.toml
244 composer.json
245 go.mod
246 package.json
247 stack.yaml
248 )
249 typeset -g POWERLEVEL9K_SHORTEN_FOLDER_MARKER="(${(j:|:)anchor_files})"
250 # If set to "first" ("last"), remove everything before the first (last) subdirectory that contains
251 # files matching $POWERLEVEL9K_SHORTEN_FOLDER_MARKER. For example, when the current directory is
252 # /foo/bar/git_repo/nested_git_repo/baz, prompt will display git_repo/nested_git_repo/baz (first)
253 # or nested_git_repo/baz (last). This assumes that git_repo and nested_git_repo contain markers
254 # and other directories don't.
255 #
256 # Optionally, "first" and "last" can be followed by ":<offset>" where <offset> is an integer.
257 # This moves the truncation point to the right (positive offset) or to the left (negative offset)
258 # relative to the marker. Plain "first" and "last" are equivalent to "first:0" and "last:0"
259 # respectively.
260 typeset -g POWERLEVEL9K_DIR_TRUNCATE_BEFORE_MARKER=false
261 # Don't shorten this many last directory segments. They are anchors.
262 typeset -g POWERLEVEL9K_SHORTEN_DIR_LENGTH=1
263 # Shorten directory if it's longer than this even if there is space for it. The value can
264 # be either absolute (e.g., '80') or a percentage of terminal width (e.g, '50%'). If empty,
265 # directory will be shortened only when prompt doesn't fit or when other parameters demand it
266 # (see POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS and POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS_PCT below).
267 # If set to `0`, directory will always be shortened to its minimum length.
268 typeset -g POWERLEVEL9K_DIR_MAX_LENGTH=30%
269 # When `dir` segment is on the last prompt line, try to shorten it enough to leave at least this
270 # many columns for typing commands.
271 typeset -g POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS=40
272 # When `dir` segment is on the last prompt line, try to shorten it enough to leave at least
273 # COLUMNS * POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS_PCT * 0.01 columns for typing commands.
274 typeset -g POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS_PCT=50
275 # If set to true, embed a hyperlink into the directory. Useful for quickly
276 # opening a directory in the file manager simply by clicking the link.
277 # Can also be handy when the directory is shortened, as it allows you to see
278 # the full directory that was used in previous commands.
279 typeset -g POWERLEVEL9K_DIR_HYPERLINK=false
280
281 # Enable special styling for non-writable and non-existent directories. See POWERLEVEL9K_LOCK_ICON
282 # and POWERLEVEL9K_DIR_CLASSES below.
283 typeset -g POWERLEVEL9K_DIR_SHOW_WRITABLE=v3
284
285 # The default icon shown next to non-writable and non-existent directories when
286 # POWERLEVEL9K_DIR_SHOW_WRITABLE is set to v3.
287 # typeset -g POWERLEVEL9K_LOCK_ICON='⭐'
288
289 # POWERLEVEL9K_DIR_CLASSES allows you to specify custom icons and colors for different
290 # directories. It must be an array with 3 * N elements. Each triplet consists of:
291 #
292 # 1. A pattern against which the current directory ($PWD) is matched. Matching is done with
293 # extended_glob option enabled.
294 # 2. Directory class for the purpose of styling.
295 # 3. An empty string.
296 #
297 # Triplets are tried in order. The first triplet whose pattern matches $PWD wins.
298 #
299 # If POWERLEVEL9K_DIR_SHOW_WRITABLE is set to v3, non-writable and non-existent directories
300 # acquire class suffix _NOT_WRITABLE and NON_EXISTENT respectively.
301 #
302 # For example, given these settings:
303 #
304 # typeset -g POWERLEVEL9K_DIR_CLASSES=(
305 # '~/work(|/*)' WORK ''
306 # '~(|/*)' HOME ''
307 # '*' DEFAULT '')
308 #
309 # Whenever the current directory is ~/work or a subdirectory of ~/work, it gets styled with one
310 # of the following classes depending on its writability and existence: WORK, WORK_NOT_WRITABLE or
311 # WORK_NON_EXISTENT.
312 #
313 # Simply assigning classes to directories doesn't have any visible effects. It merely gives you an
314 # option to define custom colors and icons for different directory classes.
315 #
316 # # Styling for WORK.
317 # typeset -g POWERLEVEL9K_DIR_WORK_VISUAL_IDENTIFIER_EXPANSION='⭐'
318 # typeset -g POWERLEVEL9K_DIR_WORK_FOREGROUND=31
319 # typeset -g POWERLEVEL9K_DIR_WORK_SHORTENED_FOREGROUND=103
320 # typeset -g POWERLEVEL9K_DIR_WORK_ANCHOR_FOREGROUND=39
321 #
322 # # Styling for WORK_NOT_WRITABLE.
323 # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_VISUAL_IDENTIFIER_EXPANSION='⭐'
324 # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_FOREGROUND=31
325 # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_SHORTENED_FOREGROUND=103
326 # typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_ANCHOR_FOREGROUND=39
327 #
328 # # Styling for WORK_NON_EXISTENT.
329 # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_VISUAL_IDENTIFIER_EXPANSION='⭐'
330 # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_FOREGROUND=31
331 # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_SHORTENED_FOREGROUND=103
332 # typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_ANCHOR_FOREGROUND=39
333 #
334 # If a styling parameter isn't explicitly defined for some class, it falls back to the classless
335 # parameter. For example, if POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_FOREGROUND is not set, it falls
336 # back to POWERLEVEL9K_DIR_FOREGROUND.
337 #
338 typeset -g POWERLEVEL9K_DIR_CLASSES=()
339
340 # Custom prefix.
341 # typeset -g POWERLEVEL9K_DIR_PREFIX='%fin '
342
343 #####################################[ vcs: git status ]######################################
344 # Branch icon. Set this parameter to '\uF126 ' for the popular Powerline branch icon.
345 typeset -g POWERLEVEL9K_VCS_BRANCH_ICON=''
346
347 # Untracked files icon. It's really a question mark, your font isn't broken.
348 # Change the value of this parameter to show a different icon.
349 typeset -g POWERLEVEL9K_VCS_UNTRACKED_ICON='?'
350
351 # Formatter for Git status.
352 #
353 # Example output: master ⇣42⇡42 *42 merge ~42 +42 !42 ?42.
354 #
355 # You can edit the function to customize how Git status looks.
356 #
357 # VCS_STATUS_* parameters are set by gitstatus plugin. See reference:
358 # https://github.com/romkatv/gitstatus/blob/master/gitstatus.plugin.zsh.
359 function my_git_formatter() {
360 emulate -L zsh
361
362 if [[ -n $P9K_CONTENT ]]; then
363 # If P9K_CONTENT is not empty, use it. It's either "loading" or from vcs_info (not from
364 # gitstatus plugin). VCS_STATUS_* parameters are not available in this case.
365 typeset -g my_git_format=$P9K_CONTENT
366 return
367 fi
368
369 if (( $1 )); then
370 # Styling for up-to-date Git status.
371 local meta='%f' # default foreground
372 local clean='%76F' # green foreground
373 local modified='%178F' # yellow foreground
374 local untracked='%39F' # blue foreground
375 local conflicted='%196F' # red foreground
376 else
377 # Styling for incomplete and stale Git status.
378 local meta='%244F' # grey foreground
379 local clean='%244F' # grey foreground
380 local modified='%244F' # grey foreground
381 local untracked='%244F' # grey foreground
382 local conflicted='%244F' # grey foreground
383 fi
384
385 local res
386 local where # branch or tag
387 if [[ -n $VCS_STATUS_LOCAL_BRANCH ]]; then
388 res+="${clean}${(g::)POWERLEVEL9K_VCS_BRANCH_ICON}"
389 where=${(V)VCS_STATUS_LOCAL_BRANCH}
390 elif [[ -n $VCS_STATUS_TAG ]]; then
391 res+="${meta}#"
392 where=${(V)VCS_STATUS_TAG}
393 fi
394
395 # If local branch name or tag is at most 32 characters long, show it in full.
396 # Otherwise show the first 12 … the last 12.
397 # Tip: To always show local branch name in full without truncation, delete the next line.
398 (( $#where > 32 )) && where[13,-13]="…"
399
400 res+="${clean}${where//\%/%%}" # escape %
401
402 # Display the current Git commit if there is no branch or tag.
403 # Tip: To always display the current Git commit, remove `[[ -z $where ]] &&` from the next line.
404 [[ -z $where ]] && res+="${meta}@${clean}${VCS_STATUS_COMMIT[1,8]}"
405
406 # Show tracking branch name if it differs from local branch.
407 if [[ -n ${VCS_STATUS_REMOTE_BRANCH:#$VCS_STATUS_LOCAL_BRANCH} ]]; then
408 res+="${meta}:${clean}${(V)VCS_STATUS_REMOTE_BRANCH//\%/%%}" # escape %
409 fi
410
411 # ⇣42 if behind the remote.
412 (( VCS_STATUS_COMMITS_BEHIND )) && res+=" ${clean}⇣${VCS_STATUS_COMMITS_BEHIND}"
413 # ⇡42 if ahead of the remote; no leading space if also behind the remote: ⇣42⇡42.
414 (( VCS_STATUS_COMMITS_AHEAD && !VCS_STATUS_COMMITS_BEHIND )) && res+=" "
415 (( VCS_STATUS_COMMITS_AHEAD )) && res+="${clean}⇡${VCS_STATUS_COMMITS_AHEAD}"
416 # ⇠42 if behind the push remote.
417 (( VCS_STATUS_PUSH_COMMITS_BEHIND )) && res+=" ${clean}⇠${VCS_STATUS_PUSH_COMMITS_BEHIND}"
418 (( VCS_STATUS_PUSH_COMMITS_AHEAD && !VCS_STATUS_PUSH_COMMITS_BEHIND )) && res+=" "
419 # ⇢42 if ahead of the push remote; no leading space if also behind: ⇠42⇢42.
420 (( VCS_STATUS_PUSH_COMMITS_AHEAD )) && res+="${clean}⇢${VCS_STATUS_PUSH_COMMITS_AHEAD}"
421 # *42 if have stashes.
422 (( VCS_STATUS_STASHES )) && res+=" ${clean}*${VCS_STATUS_STASHES}"
423 # 'merge' if the repo is in an unusual state.
424 [[ -n $VCS_STATUS_ACTION ]] && res+=" ${conflicted}${VCS_STATUS_ACTION}"
425 # ~42 if have merge conflicts.
426 (( VCS_STATUS_NUM_CONFLICTED )) && res+=" ${conflicted}~${VCS_STATUS_NUM_CONFLICTED}"
427 # +42 if have staged changes.
428 (( VCS_STATUS_NUM_STAGED )) && res+=" ${modified}+${VCS_STATUS_NUM_STAGED}"
429 # !42 if have unstaged changes.
430 (( VCS_STATUS_NUM_UNSTAGED )) && res+=" ${modified}!${VCS_STATUS_NUM_UNSTAGED}"
431 # ?42 if have untracked files. It's really a question mark, your font isn't broken.
432 # See POWERLEVEL9K_VCS_UNTRACKED_ICON above if you want to use a different icon.
433 # Remove the next line if you don't want to see untracked files at all.
434 (( VCS_STATUS_NUM_UNTRACKED )) && res+=" ${untracked}${(g::)POWERLEVEL9K_VCS_UNTRACKED_ICON}${VCS_STATUS_NUM_UNTRACKED}"
435 # "─" if the number of unstaged files is unknown. This can happen due to
436 # POWERLEVEL9K_VCS_MAX_INDEX_SIZE_DIRTY (see below) being set to a non-negative number lower
437 # than the number of files in the Git index, or due to bash.showDirtyState being set to false
438 # in the repository config. The number of staged and untracked files may also be unknown
439 # in this case.
440 (( VCS_STATUS_HAS_UNSTAGED == -1 )) && res+=" ${modified}─"
441
442 typeset -g my_git_format=$res
443 }
444 functions -M my_git_formatter 2>/dev/null
445
446 # Don't count the number of unstaged, untracked and conflicted files in Git repositories with
447 # more than this many files in the index. Negative value means infinity.
448 #
449 # If you are working in Git repositories with tens of millions of files and seeing performance
450 # sagging, try setting POWERLEVEL9K_VCS_MAX_INDEX_SIZE_DIRTY to a number lower than the output
451 # of `git ls-files | wc -l`. Alternatively, add `bash.showDirtyState = false` to the repository's
452 # config: `git config bash.showDirtyState false`.
453 typeset -g POWERLEVEL9K_VCS_MAX_INDEX_SIZE_DIRTY=-1
454
455 # Don't show Git status in prompt for repositories whose workdir matches this pattern.
456 # For example, if set to '~', the Git repository at $HOME/.git will be ignored.
457 # Multiple patterns can be combined with '|': '~(|/foo)|/bar/baz/*'.
458 # typeset -g POWERLEVEL9K_VCS_DISABLED_WORKDIR_PATTERN='~'
459
460 # Disable the default Git status formatting.
461 typeset -g POWERLEVEL9K_VCS_DISABLE_GITSTATUS_FORMATTING=true
462 # Install our own Git status formatter.
463 typeset -g POWERLEVEL9K_VCS_CONTENT_EXPANSION='${$((my_git_formatter(1)))+${my_git_format}}'
464 typeset -g POWERLEVEL9K_VCS_LOADING_CONTENT_EXPANSION='${$((my_git_formatter(0)))+${my_git_format}}'
465 # Enable counters for staged, unstaged, etc.
466 typeset -g POWERLEVEL9K_VCS_{STAGED,UNSTAGED,UNTRACKED,CONFLICTED,COMMITS_AHEAD,COMMITS_BEHIND}_MAX_NUM=-1
467
468 # Icon color.
469 typeset -g POWERLEVEL9K_VCS_VISUAL_IDENTIFIER_COLOR=76
470 typeset -g POWERLEVEL9K_VCS_LOADING_VISUAL_IDENTIFIER_COLOR=244
471 # Custom icon.
472 # typeset -g POWERLEVEL9K_VCS_VISUAL_IDENTIFIER_EXPANSION='⭐'
473 # Custom prefix.
474 # typeset -g POWERLEVEL9K_VCS_PREFIX='%fon '
475
476 # Show status of repositories of these types. You can add svn and/or hg if you are
477 # using them. If you do, your prompt may become slow even when your current directory
478 # isn't in an svn or hg reposotiry.
479 typeset -g POWERLEVEL9K_VCS_BACKENDS=(git)
480
481 # These settings are used for repositories other than Git or when gitstatusd fails and
482 # Powerlevel10k has to fall back to using vcs_info.
483 typeset -g POWERLEVEL9K_VCS_CLEAN_FOREGROUND=76
484 typeset -g POWERLEVEL9K_VCS_UNTRACKED_FOREGROUND=76
485 typeset -g POWERLEVEL9K_VCS_MODIFIED_FOREGROUND=178
486
487 ##########################[ status: exit code of the last command ]###########################
488 # Enable OK_PIPE, ERROR_PIPE and ERROR_SIGNAL status states to allow us to enable, disable and
489 # style them independently from the regular OK and ERROR state.
490 typeset -g POWERLEVEL9K_STATUS_EXTENDED_STATES=true
491
492 # Status on success. No content, just an icon. No need to show it if prompt_char is enabled as
493 # it will signify success by turning green.
494 typeset -g POWERLEVEL9K_STATUS_OK=false
495 typeset -g POWERLEVEL9K_STATUS_OK_FOREGROUND=70
496 typeset -g POWERLEVEL9K_STATUS_OK_VISUAL_IDENTIFIER_EXPANSION='✔'
497
498 # Status when some part of a pipe command fails but the overall exit status is zero. It may look
499 # like this: 1|0.
500 typeset -g POWERLEVEL9K_STATUS_OK_PIPE=true
501 typeset -g POWERLEVEL9K_STATUS_OK_PIPE_FOREGROUND=70
502 typeset -g POWERLEVEL9K_STATUS_OK_PIPE_VISUAL_IDENTIFIER_EXPANSION='✔'
503
504 # Status when it's just an error code (e.g., '1'). No need to show it if prompt_char is enabled as
505 # it will signify error by turning red.
506 typeset -g POWERLEVEL9K_STATUS_ERROR=true
507 typeset -g POWERLEVEL9K_STATUS_ERROR_FOREGROUND=160
508 typeset -g POWERLEVEL9K_STATUS_ERROR_VISUAL_IDENTIFIER_EXPANSION='✘'
509
510 # Status when the last command was terminated by a signal.
511 typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL=true
512 typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL_FOREGROUND=160
513 # Use terse signal names: "INT" instead of "SIGINT(2)".
514 typeset -g POWERLEVEL9K_STATUS_VERBOSE_SIGNAME=false
515 typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL_VISUAL_IDENTIFIER_EXPANSION='✘'
516
517 # Status when some part of a pipe command fails and the overall exit status is also non-zero.
518 # It may look like this: 1|0.
519 typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE=true
520 typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_FOREGROUND=160
521 typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_VISUAL_IDENTIFIER_EXPANSION='✘'
522
523 ###################[ command_execution_time: duration of the last command ]###################
524 # Show duration of the last command if takes at least this many seconds.
525 typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_THRESHOLD=3
526 # Show this many fractional digits. Zero means round to seconds.
527 typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_PRECISION=0
528 # Execution time color.
529 typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FOREGROUND=101
530 # Duration format: 1d 2h 3m 4s.
531 typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FORMAT='d h m s'
532 # Custom icon.
533 # typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_VISUAL_IDENTIFIER_EXPANSION='⭐'
534 # Custom prefix.
535 # typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_PREFIX='%ftook '
536
537 #######################[ background_jobs: presence of background jobs ]#######################
538 # Don't show the number of background jobs.
539 typeset -g POWERLEVEL9K_BACKGROUND_JOBS_VERBOSE=false
540 # Background jobs color.
541 typeset -g POWERLEVEL9K_BACKGROUND_JOBS_FOREGROUND=70
542 # Custom icon.
543 # typeset -g POWERLEVEL9K_BACKGROUND_JOBS_VISUAL_IDENTIFIER_EXPANSION='⭐'
544
545 #######################[ direnv: direnv status (https://direnv.net/) ]########################
546 # Direnv color.
547 typeset -g POWERLEVEL9K_DIRENV_FOREGROUND=178
548 # Custom icon.
549 # typeset -g POWERLEVEL9K_DIRENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
550
551 ###############[ asdf: asdf version manager (https://github.com/asdf-vm/asdf) ]###############
552 # Default asdf color. Only used to display tools for which there is no color override (see below).
553 # Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_FOREGROUND.
554 typeset -g POWERLEVEL9K_ASDF_FOREGROUND=66
555
556 # There are four parameters that can be used to hide asdf tools. Each parameter describes
557 # conditions under which a tool gets hidden. Parameters can hide tools but not unhide them. If at
558 # least one parameter decides to hide a tool, that tool gets hidden. If no parameter decides to
559 # hide a tool, it gets shown.
560 #
561 # Special note on the difference between POWERLEVEL9K_ASDF_SOURCES and
562 # POWERLEVEL9K_ASDF_PROMPT_ALWAYS_SHOW. Consider the effect of the following commands:
563 #
564 # asdf local python 3.8.1
565 # asdf global python 3.8.1
566 #
567 # After running both commands the current python version is 3.8.1 and its source is "local" as
568 # it takes precedence over "global". If POWERLEVEL9K_ASDF_PROMPT_ALWAYS_SHOW is set to false,
569 # it'll hide python version in this case because 3.8.1 is the same as the global version.
570 # POWERLEVEL9K_ASDF_SOURCES will hide python version only if the value of this parameter doesn't
571 # contain "local".
572
573 # Hide tool versions that don't come from one of these sources.
574 #
575 # Available sources:
576 #
577 # - shell `asdf current` says "set by ASDF_${TOOL}_VERSION environment variable"
578 # - local `asdf current` says "set by /some/not/home/directory/file"
579 # - global `asdf current` says "set by /home/username/file"
580 #
581 # Note: If this parameter is set to (shell local global), it won't hide tools.
582 # Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_SOURCES.
583 typeset -g POWERLEVEL9K_ASDF_SOURCES=(shell local global)
584
585 # If set to false, hide tool versions that are the same as global.
586 #
587 # Note: The name of this parameter doesn't reflect its meaning at all.
588 # Note: If this parameter is set to true, it won't hide tools.
589 # Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_PROMPT_ALWAYS_SHOW.
590 typeset -g POWERLEVEL9K_ASDF_PROMPT_ALWAYS_SHOW=false
591
592 # If set to false, hide tool versions that are equal to "system".
593 #
594 # Note: If this parameter is set to true, it won't hide tools.
595 # Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_SHOW_SYSTEM.
596 typeset -g POWERLEVEL9K_ASDF_SHOW_SYSTEM=true
597
598 # If set to non-empty value, hide tools unless there is a file matching the specified file pattern
599 # in the current directory, or its parent directory, or its grandparent directory, and so on.
600 #
601 # Note: If this parameter is set to empty value, it won't hide tools.
602 # Note: SHOW_ON_UPGLOB isn't specific to asdf. It works with all prompt segments.
603 # Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_SHOW_ON_UPGLOB.
604 #
605 # Example: Hide nodejs version when there is no package.json and no *.js files in the current
606 # directory, in `..`, in `../..` and so on.
607 #
608 # typeset -g POWERLEVEL9K_ASDF_NODEJS_SHOW_ON_UPGLOB='*.js|package.json'
609 typeset -g POWERLEVEL9K_ASDF_SHOW_ON_UPGLOB=
610
611 # Ruby version from asdf.
612 typeset -g POWERLEVEL9K_ASDF_RUBY_FOREGROUND=168
613 # typeset -g POWERLEVEL9K_ASDF_RUBY_VISUAL_IDENTIFIER_EXPANSION='⭐'
614 # typeset -g POWERLEVEL9K_ASDF_RUBY_SHOW_ON_UPGLOB='*.foo|*.bar'
615
616 # Python version from asdf.
617 typeset -g POWERLEVEL9K_ASDF_PYTHON_FOREGROUND=37
618 # typeset -g POWERLEVEL9K_ASDF_PYTHON_VISUAL_IDENTIFIER_EXPANSION='⭐'
619 # typeset -g POWERLEVEL9K_ASDF_PYTHON_SHOW_ON_UPGLOB='*.foo|*.bar'
620
621 # Go version from asdf.
622 typeset -g POWERLEVEL9K_ASDF_GOLANG_FOREGROUND=37
623 # typeset -g POWERLEVEL9K_ASDF_GOLANG_VISUAL_IDENTIFIER_EXPANSION='⭐'
624 # typeset -g POWERLEVEL9K_ASDF_GOLANG_SHOW_ON_UPGLOB='*.foo|*.bar'
625
626 # Node.js version from asdf.
627 typeset -g POWERLEVEL9K_ASDF_NODEJS_FOREGROUND=70
628 # typeset -g POWERLEVEL9K_ASDF_NODEJS_VISUAL_IDENTIFIER_EXPANSION='⭐'
629 # typeset -g POWERLEVEL9K_ASDF_NODEJS_SHOW_ON_UPGLOB='*.foo|*.bar'
630
631 # Rust version from asdf.
632 typeset -g POWERLEVEL9K_ASDF_RUST_FOREGROUND=37
633 # typeset -g POWERLEVEL9K_ASDF_RUST_VISUAL_IDENTIFIER_EXPANSION='⭐'
634 # typeset -g POWERLEVEL9K_ASDF_RUST_SHOW_ON_UPGLOB='*.foo|*.bar'
635
636 # .NET Core version from asdf.
637 typeset -g POWERLEVEL9K_ASDF_DOTNET_CORE_FOREGROUND=134
638 # typeset -g POWERLEVEL9K_ASDF_DOTNET_CORE_VISUAL_IDENTIFIER_EXPANSION='⭐'
639 # typeset -g POWERLEVEL9K_ASDF_DOTNET_SHOW_ON_UPGLOB='*.foo|*.bar'
640
641 # Flutter version from asdf.
642 typeset -g POWERLEVEL9K_ASDF_FLUTTER_FOREGROUND=38
643 # typeset -g POWERLEVEL9K_ASDF_FLUTTER_VISUAL_IDENTIFIER_EXPANSION='⭐'
644 # typeset -g POWERLEVEL9K_ASDF_FLUTTER_SHOW_ON_UPGLOB='*.foo|*.bar'
645
646 # Lua version from asdf.
647 typeset -g POWERLEVEL9K_ASDF_LUA_FOREGROUND=32
648 # typeset -g POWERLEVEL9K_ASDF_LUA_VISUAL_IDENTIFIER_EXPANSION='⭐'
649 # typeset -g POWERLEVEL9K_ASDF_LUA_SHOW_ON_UPGLOB='*.foo|*.bar'
650
651 # Java version from asdf.
652 typeset -g POWERLEVEL9K_ASDF_JAVA_FOREGROUND=32
653 # typeset -g POWERLEVEL9K_ASDF_JAVA_VISUAL_IDENTIFIER_EXPANSION='⭐'
654 # typeset -g POWERLEVEL9K_ASDF_JAVA_SHOW_ON_UPGLOB='*.foo|*.bar'
655
656 # Perl version from asdf.
657 typeset -g POWERLEVEL9K_ASDF_PERL_FOREGROUND=67
658 # typeset -g POWERLEVEL9K_ASDF_PERL_VISUAL_IDENTIFIER_EXPANSION='⭐'
659 # typeset -g POWERLEVEL9K_ASDF_PERL_SHOW_ON_UPGLOB='*.foo|*.bar'
660
661 # Erlang version from asdf.
662 typeset -g POWERLEVEL9K_ASDF_ERLANG_FOREGROUND=125
663 # typeset -g POWERLEVEL9K_ASDF_ERLANG_VISUAL_IDENTIFIER_EXPANSION='⭐'
664 # typeset -g POWERLEVEL9K_ASDF_ERLANG_SHOW_ON_UPGLOB='*.foo|*.bar'
665
666 # Elixir version from asdf.
667 typeset -g POWERLEVEL9K_ASDF_ELIXIR_FOREGROUND=129
668 # typeset -g POWERLEVEL9K_ASDF_ELIXIR_VISUAL_IDENTIFIER_EXPANSION='⭐'
669 # typeset -g POWERLEVEL9K_ASDF_ELIXIR_SHOW_ON_UPGLOB='*.foo|*.bar'
670
671 # Postgres version from asdf.
672 typeset -g POWERLEVEL9K_ASDF_POSTGRES_FOREGROUND=31
673 # typeset -g POWERLEVEL9K_ASDF_POSTGRES_VISUAL_IDENTIFIER_EXPANSION='⭐'
674 # typeset -g POWERLEVEL9K_ASDF_POSTGRES_SHOW_ON_UPGLOB='*.foo|*.bar'
675
676 # PHP version from asdf.
677 typeset -g POWERLEVEL9K_ASDF_PHP_FOREGROUND=99
678 # typeset -g POWERLEVEL9K_ASDF_PHP_VISUAL_IDENTIFIER_EXPANSION='⭐'
679 # typeset -g POWERLEVEL9K_ASDF_PHP_SHOW_ON_UPGLOB='*.foo|*.bar'
680
681 # Haskell version from asdf.
682 typeset -g POWERLEVEL9K_ASDF_HASKELL_FOREGROUND=172
683 # typeset -g POWERLEVEL9K_ASDF_HASKELL_VISUAL_IDENTIFIER_EXPANSION='⭐'
684 # typeset -g POWERLEVEL9K_ASDF_HASKELL_SHOW_ON_UPGLOB='*.foo|*.bar'
685
686 # Julia version from asdf.
687 typeset -g POWERLEVEL9K_ASDF_JULIA_FOREGROUND=70
688 # typeset -g POWERLEVEL9K_ASDF_JULIA_VISUAL_IDENTIFIER_EXPANSION='⭐'
689 # typeset -g POWERLEVEL9K_ASDF_JULIA_SHOW_ON_UPGLOB='*.foo|*.bar'
690
691 ##########[ nordvpn: nordvpn connection status, linux only (https://nordvpn.com/) ]###########
692 # NordVPN connection indicator color.
693 typeset -g POWERLEVEL9K_NORDVPN_FOREGROUND=39
694 # Hide NordVPN connection indicator when not connected.
695 typeset -g POWERLEVEL9K_NORDVPN_{DISCONNECTED,CONNECTING,DISCONNECTING}_CONTENT_EXPANSION=
696 typeset -g POWERLEVEL9K_NORDVPN_{DISCONNECTED,CONNECTING,DISCONNECTING}_VISUAL_IDENTIFIER_EXPANSION=
697 # Custom icon.
698 # typeset -g POWERLEVEL9K_NORDVPN_VISUAL_IDENTIFIER_EXPANSION='⭐'
699
700 #################[ ranger: ranger shell (https://github.com/ranger/ranger) ]##################
701 # Ranger shell color.
702 typeset -g POWERLEVEL9K_RANGER_FOREGROUND=178
703 # Custom icon.
704 # typeset -g POWERLEVEL9K_RANGER_VISUAL_IDENTIFIER_EXPANSION='⭐'
705
706 ######################[ nnn: nnn shell (https://github.com/jarun/nnn) ]#######################
707 # Nnn shell color.
708 typeset -g POWERLEVEL9K_NNN_FOREGROUND=72
709 # Custom icon.
710 # typeset -g POWERLEVEL9K_NNN_VISUAL_IDENTIFIER_EXPANSION='⭐'
711
712 ###########################[ vim_shell: vim shell indicator (:sh) ]###########################
713 # Vim shell indicator color.
714 typeset -g POWERLEVEL9K_VIM_SHELL_FOREGROUND=34
715 # Custom icon.
716 # typeset -g POWERLEVEL9K_VIM_SHELL_VISUAL_IDENTIFIER_EXPANSION='⭐'
717
718 ######[ midnight_commander: midnight commander shell (https://midnight-commander.org/) ]######
719 # Midnight Commander shell color.
720 typeset -g POWERLEVEL9K_MIDNIGHT_COMMANDER_FOREGROUND=178
721 # Custom icon.
722 # typeset -g POWERLEVEL9K_MIDNIGHT_COMMANDER_VISUAL_IDENTIFIER_EXPANSION='⭐'
723
724 #[ nix_shell: nix shell (https://nixos.org/nixos/nix-pills/developing-with-nix-shell.html) ]##
725 # Nix shell color.
726 typeset -g POWERLEVEL9K_NIX_SHELL_FOREGROUND=74
727
728 # Tip: If you want to see just the icon without "pure" and "impure", uncomment the next line.
729 # typeset -g POWERLEVEL9K_NIX_SHELL_CONTENT_EXPANSION=
730
731 # Custom icon.
732 # typeset -g POWERLEVEL9K_NIX_SHELL_VISUAL_IDENTIFIER_EXPANSION='⭐'
733
734 ##################################[ disk_usage: disk usage ]##################################
735 # Colors for different levels of disk usage.
736 typeset -g POWERLEVEL9K_DISK_USAGE_NORMAL_FOREGROUND=35
737 typeset -g POWERLEVEL9K_DISK_USAGE_WARNING_FOREGROUND=220
738 typeset -g POWERLEVEL9K_DISK_USAGE_CRITICAL_FOREGROUND=160
739 # Thresholds for different levels of disk usage (percentage points).
740 typeset -g POWERLEVEL9K_DISK_USAGE_WARNING_LEVEL=90
741 typeset -g POWERLEVEL9K_DISK_USAGE_CRITICAL_LEVEL=95
742 # If set to true, hide disk usage when below $POWERLEVEL9K_DISK_USAGE_WARNING_LEVEL percent.
743 typeset -g POWERLEVEL9K_DISK_USAGE_ONLY_WARNING=false
744 # Custom icon.
745 # typeset -g POWERLEVEL9K_DISK_USAGE_VISUAL_IDENTIFIER_EXPANSION='⭐'
746
747 ######################################[ ram: free RAM ]#######################################
748 # RAM color.
749 typeset -g POWERLEVEL9K_RAM_FOREGROUND=66
750 # Custom icon.
751 # typeset -g POWERLEVEL9K_RAM_VISUAL_IDENTIFIER_EXPANSION='⭐'
752
753 #####################################[ swap: used swap ]######################################
754 # Swap color.
755 typeset -g POWERLEVEL9K_SWAP_FOREGROUND=96
756 # Custom icon.
757 # typeset -g POWERLEVEL9K_SWAP_VISUAL_IDENTIFIER_EXPANSION='⭐'
758
759 ######################################[ load: CPU load ]######################################
760 # Show average CPU load over this many last minutes. Valid values are 1, 5 and 15.
761 typeset -g POWERLEVEL9K_LOAD_WHICH=5
762 # Load color when load is under 50%.
763 typeset -g POWERLEVEL9K_LOAD_NORMAL_FOREGROUND=66
764 # Load color when load is between 50% and 70%.
765 typeset -g POWERLEVEL9K_LOAD_WARNING_FOREGROUND=178
766 # Load color when load is over 70%.
767 typeset -g POWERLEVEL9K_LOAD_CRITICAL_FOREGROUND=166
768 # Custom icon.
769 # typeset -g POWERLEVEL9K_LOAD_VISUAL_IDENTIFIER_EXPANSION='⭐'
770
771 ################[ todo: todo items (https://github.com/todotxt/todo.txt-cli) ]################
772 # Todo color.
773 typeset -g POWERLEVEL9K_TODO_FOREGROUND=110
774 # Hide todo when the total number of tasks is zero.
775 typeset -g POWERLEVEL9K_TODO_HIDE_ZERO_TOTAL=true
776 # Hide todo when the number of tasks after filtering is zero.
777 typeset -g POWERLEVEL9K_TODO_HIDE_ZERO_FILTERED=false
778
779 # Todo format. The following parameters are available within the expansion.
780 #
781 # - P9K_TODO_TOTAL_TASK_COUNT The total number of tasks.
782 # - P9K_TODO_FILTERED_TASK_COUNT The number of tasks after filtering.
783 #
784 # These variables correspond to the last line of the output of `todo.sh -p ls`:
785 #
786 # TODO: 24 of 42 tasks shown
787 #
788 # Here 24 is P9K_TODO_FILTERED_TASK_COUNT and 42 is P9K_TODO_TOTAL_TASK_COUNT.
789 #
790 # typeset -g POWERLEVEL9K_TODO_CONTENT_EXPANSION='$P9K_TODO_FILTERED_TASK_COUNT'
791
792 # Custom icon.
793 # typeset -g POWERLEVEL9K_TODO_VISUAL_IDENTIFIER_EXPANSION='⭐'
794
795 ###########[ timewarrior: timewarrior tracking status (https://timewarrior.net/) ]############
796 # Timewarrior color.
797 typeset -g POWERLEVEL9K_TIMEWARRIOR_FOREGROUND=110
798 # If the tracked task is longer than 24 characters, truncate and append "…".
799 # Tip: To always display tasks without truncation, delete the following parameter.
800 # Tip: To hide task names and display just the icon when time tracking is enabled, set the
801 # value of the following parameter to "".
802 typeset -g POWERLEVEL9K_TIMEWARRIOR_CONTENT_EXPANSION='${P9K_CONTENT:0:24}${${P9K_CONTENT:24}:+…}'
803
804 # Custom icon.
805 # typeset -g POWERLEVEL9K_TIMEWARRIOR_VISUAL_IDENTIFIER_EXPANSION='⭐'
806
807 ##############[ taskwarrior: taskwarrior task count (https://taskwarrior.org/) ]##############
808 # Taskwarrior color.
809 typeset -g POWERLEVEL9K_TASKWARRIOR_FOREGROUND=74
810
811 # Taskwarrior segment format. The following parameters are available within the expansion.
812 #
813 # - P9K_TASKWARRIOR_PENDING_COUNT The number of pending tasks: `task +PENDING count`.
814 # - P9K_TASKWARRIOR_OVERDUE_COUNT The number of overdue tasks: `task +OVERDUE count`.
815 #
816 # Zero values are represented as empty parameters.
817 #
818 # The default format:
819 #
820 # '${P9K_TASKWARRIOR_OVERDUE_COUNT:+"!$P9K_TASKWARRIOR_OVERDUE_COUNT/"}$P9K_TASKWARRIOR_PENDING_COUNT'
821 #
822 # typeset -g POWERLEVEL9K_TASKWARRIOR_CONTENT_EXPANSION='$P9K_TASKWARRIOR_PENDING_COUNT'
823
824 # Custom icon.
825 # typeset -g POWERLEVEL9K_TASKWARRIOR_VISUAL_IDENTIFIER_EXPANSION='⭐'
826
827 ##################################[ context: user@hostname ]##################################
828 # Context color when running with privileges.
829 typeset -g POWERLEVEL9K_CONTEXT_ROOT_FOREGROUND=178
830 # Context color in SSH without privileges.
831 typeset -g POWERLEVEL9K_CONTEXT_{REMOTE,REMOTE_SUDO}_FOREGROUND=31
832 # Default context color (no privileges, no SSH).
833 typeset -g POWERLEVEL9K_CONTEXT_FOREGROUND=31
834
835 # Context format when running with privileges: bold user@hostname.
836 typeset -g POWERLEVEL9K_CONTEXT_ROOT_TEMPLATE='%B%n@%m'
837 # Context format when in SSH without privileges: user@hostname.
838 typeset -g POWERLEVEL9K_CONTEXT_{REMOTE,REMOTE_SUDO}_TEMPLATE='%n@%m'
839 # Default context format (no privileges, no SSH): user@hostname.
840 typeset -g POWERLEVEL9K_CONTEXT_TEMPLATE='%n@%m'
841
842 # Don't show context unless running with privileges or in SSH.
843 # Tip: Remove the next line to always show context.
844 typeset -g POWERLEVEL9K_CONTEXT_{DEFAULT,SUDO}_{CONTENT,VISUAL_IDENTIFIER}_EXPANSION=
845
846 # Custom icon.
847 # typeset -g POWERLEVEL9K_CONTEXT_VISUAL_IDENTIFIER_EXPANSION='⭐'
848 # Custom prefix.
849 # typeset -g POWERLEVEL9K_CONTEXT_PREFIX='%fwith '
850
851 ###[ virtualenv: python virtual environment (https://docs.python.org/3/library/venv.html) ]###
852 # Python virtual environment color.
853 typeset -g POWERLEVEL9K_VIRTUALENV_FOREGROUND=37
854 # Don't show Python version next to the virtual environment name.
855 typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_PYTHON_VERSION=false
856 # If set to "false", won't show virtualenv if pyenv is already shown.
857 # If set to "if-different", won't show virtualenv if it's the same as pyenv.
858 typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_WITH_PYENV=false
859 # Separate environment name from Python version only with a space.
860 typeset -g POWERLEVEL9K_VIRTUALENV_{LEFT,RIGHT}_DELIMITER=
861 # Custom icon.
862 # typeset -g POWERLEVEL9K_VIRTUALENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
863
864 #####################[ anaconda: conda environment (https://conda.io/) ]######################
865 # Anaconda environment color.
866 typeset -g POWERLEVEL9K_ANACONDA_FOREGROUND=37
867
868 # Anaconda segment format. The following parameters are available within the expansion.
869 #
870 # - CONDA_PREFIX Absolute path to the active Anaconda/Miniconda environment.
871 # - CONDA_DEFAULT_ENV Name of the active Anaconda/Miniconda environment.
872 # - CONDA_PROMPT_MODIFIER Configurable prompt modifier (see below).
873 # - P9K_ANACONDA_PYTHON_VERSION Current python version (python --version).
874 #
875 # CONDA_PROMPT_MODIFIER can be configured with the following command:
876 #
877 # conda config --set env_prompt '({default_env}) '
878 #
879 # The last argument is a Python format string that can use the following variables:
880 #
881 # - prefix The same as CONDA_PREFIX.
882 # - default_env The same as CONDA_DEFAULT_ENV.
883 # - name The last segment of CONDA_PREFIX.
884 # - stacked_env Comma-separated list of names in the environment stack. The first element is
885 # always the same as default_env.
886 #
887 # Note: '({default_env}) ' is the default value of env_prompt.
888 #
889 # The default value of POWERLEVEL9K_ANACONDA_CONTENT_EXPANSION expands to $CONDA_PROMPT_MODIFIER
890 # without the surrounding parentheses, or to the last path component of CONDA_PREFIX if the former
891 # is empty.
892 typeset -g POWERLEVEL9K_ANACONDA_CONTENT_EXPANSION='${${${${CONDA_PROMPT_MODIFIER#\(}% }%\)}:-${CONDA_PREFIX:t}}'
893
894 # Custom icon.
895 # typeset -g POWERLEVEL9K_ANACONDA_VISUAL_IDENTIFIER_EXPANSION='⭐'
896
897 ################[ pyenv: python environment (https://github.com/pyenv/pyenv) ]################
898 # Pyenv color.
899 typeset -g POWERLEVEL9K_PYENV_FOREGROUND=37
900 # Hide python version if it doesn't come from one of these sources.
901 typeset -g POWERLEVEL9K_PYENV_SOURCES=(shell local global)
902 # If set to false, hide python version if it's the same as global:
903 # $(pyenv version-name) == $(pyenv global).
904 typeset -g POWERLEVEL9K_PYENV_PROMPT_ALWAYS_SHOW=false
905 # If set to false, hide python version if it's equal to "system".
906 typeset -g POWERLEVEL9K_PYENV_SHOW_SYSTEM=true
907
908 # Pyenv segment format. The following parameters are available within the expansion.
909 #
910 # - P9K_CONTENT Current pyenv environment (pyenv version-name).
911 # - P9K_PYENV_PYTHON_VERSION Current python version (python --version).
912 #
913 # The default format has the following logic:
914 #
915 # 1. Display "$P9K_CONTENT $P9K_PYENV_PYTHON_VERSION" if $P9K_PYENV_PYTHON_VERSION is not
916 # empty and unequal to $P9K_CONTENT.
917 # 2. Otherwise display just "$P9K_CONTENT".
918 typeset -g POWERLEVEL9K_PYENV_CONTENT_EXPANSION='${P9K_CONTENT}${${P9K_PYENV_PYTHON_VERSION:#$P9K_CONTENT}:+ $P9K_PYENV_PYTHON_VERSION}'
919
920 # Custom icon.
921 # typeset -g POWERLEVEL9K_PYENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
922
923 ################[ goenv: go environment (https://github.com/syndbg/goenv) ]################
924 # Goenv color.
925 typeset -g POWERLEVEL9K_GOENV_FOREGROUND=37
926 # Hide go version if it doesn't come from one of these sources.
927 typeset -g POWERLEVEL9K_GOENV_SOURCES=(shell local global)
928 # If set to false, hide go version if it's the same as global:
929 # $(goenv version-name) == $(goenv global).
930 typeset -g POWERLEVEL9K_GOENV_PROMPT_ALWAYS_SHOW=false
931 # If set to false, hide go version if it's equal to "system".
932 typeset -g POWERLEVEL9K_GOENV_SHOW_SYSTEM=true
933 # Custom icon.
934 # typeset -g POWERLEVEL9K_GOENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
935
936 ##########[ nodenv: node.js version from nodenv (https://github.com/nodenv/nodenv) ]##########
937 # Nodenv color.
938 typeset -g POWERLEVEL9K_NODENV_FOREGROUND=70
939 # Hide node version if it doesn't come from one of these sources.
940 typeset -g POWERLEVEL9K_NODENV_SOURCES=(shell local global)
941 # If set to false, hide node version if it's the same as global:
942 # $(nodenv version-name) == $(nodenv global).
943 typeset -g POWERLEVEL9K_NODENV_PROMPT_ALWAYS_SHOW=false
944 # If set to false, hide node version if it's equal to "system".
945 typeset -g POWERLEVEL9K_NODENV_SHOW_SYSTEM=true
946 # Custom icon.
947 # typeset -g POWERLEVEL9K_NODENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
948
949 ##############[ nvm: node.js version from nvm (https://github.com/nvm-sh/nvm) ]###############
950 # Nvm color.
951 typeset -g POWERLEVEL9K_NVM_FOREGROUND=70
952 # Custom icon.
953 # typeset -g POWERLEVEL9K_NVM_VISUAL_IDENTIFIER_EXPANSION='⭐'
954
955 ############[ nodeenv: node.js environment (https://github.com/ekalinin/nodeenv) ]############
956 # Nodeenv color.
957 typeset -g POWERLEVEL9K_NODEENV_FOREGROUND=70
958 # Don't show Node version next to the environment name.
959 typeset -g POWERLEVEL9K_NODEENV_SHOW_NODE_VERSION=false
960 # Separate environment name from Node version only with a space.
961 typeset -g POWERLEVEL9K_NODEENV_{LEFT,RIGHT}_DELIMITER=
962 # Custom icon.
963 # typeset -g POWERLEVEL9K_NODEENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
964
965 ##############################[ node_version: node.js version ]###############################
966 # Node version color.
967 typeset -g POWERLEVEL9K_NODE_VERSION_FOREGROUND=70
968 # Show node version only when in a directory tree containing package.json.
969 typeset -g POWERLEVEL9K_NODE_VERSION_PROJECT_ONLY=true
970 # Custom icon.
971 # typeset -g POWERLEVEL9K_NODE_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐'
972
973 #######################[ go_version: go version (https://golang.org) ]########################
974 # Go version color.
975 typeset -g POWERLEVEL9K_GO_VERSION_FOREGROUND=37
976 # Show go version only when in a go project subdirectory.
977 typeset -g POWERLEVEL9K_GO_VERSION_PROJECT_ONLY=true
978 # Custom icon.
979 # typeset -g POWERLEVEL9K_GO_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐'
980
981 #################[ rust_version: rustc version (https://www.rust-lang.org) ]##################
982 # Rust version color.
983 typeset -g POWERLEVEL9K_RUST_VERSION_FOREGROUND=37
984 # Show rust version only when in a rust project subdirectory.
985 typeset -g POWERLEVEL9K_RUST_VERSION_PROJECT_ONLY=true
986 # Custom icon.
987 # typeset -g POWERLEVEL9K_RUST_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐'
988
989 ###############[ dotnet_version: .NET version (https://dotnet.microsoft.com) ]################
990 # .NET version color.
991 typeset -g POWERLEVEL9K_DOTNET_VERSION_FOREGROUND=134
992 # Show .NET version only when in a .NET project subdirectory.
993 typeset -g POWERLEVEL9K_DOTNET_VERSION_PROJECT_ONLY=true
994 # Custom icon.
995 # typeset -g POWERLEVEL9K_DOTNET_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐'
996
997 #####################[ php_version: php version (https://www.php.net/) ]######################
998 # PHP version color.
999 typeset -g POWERLEVEL9K_PHP_VERSION_FOREGROUND=99
1000 # Show PHP version only when in a PHP project subdirectory.
1001 typeset -g POWERLEVEL9K_PHP_VERSION_PROJECT_ONLY=true
1002 # Custom icon.
1003 # typeset -g POWERLEVEL9K_PHP_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐'
1004
1005 ##########[ laravel_version: laravel php framework version (https://laravel.com/) ]###########
1006 # Laravel version color.
1007 typeset -g POWERLEVEL9K_LARAVEL_VERSION_FOREGROUND=161
1008 # Custom icon.
1009 # typeset -g POWERLEVEL9K_LARAVEL_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐'
1010
1011 ####################[ java_version: java version (https://www.java.com/) ]####################
1012 # Java version color.
1013 typeset -g POWERLEVEL9K_JAVA_VERSION_FOREGROUND=32
1014 # Show java version only when in a java project subdirectory.
1015 typeset -g POWERLEVEL9K_JAVA_VERSION_PROJECT_ONLY=true
1016 # Show brief version.
1017 typeset -g POWERLEVEL9K_JAVA_VERSION_FULL=false
1018 # Custom icon.
1019 # typeset -g POWERLEVEL9K_JAVA_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐'
1020
1021 ###[ package: name@version from package.json (https://docs.npmjs.com/files/package.json) ]####
1022 # Package color.
1023 typeset -g POWERLEVEL9K_PACKAGE_FOREGROUND=117
1024 # Package format. The following parameters are available within the expansion.
1025 #
1026 # - P9K_PACKAGE_NAME The value of `name` field in package.json.
1027 # - P9K_PACKAGE_VERSION The value of `version` field in package.json.
1028 #
1029 # typeset -g POWERLEVEL9K_PACKAGE_CONTENT_EXPANSION='${P9K_PACKAGE_NAME//\%/%%}@${P9K_PACKAGE_VERSION//\%/%%}'
1030 # Custom icon.
1031 # typeset -g POWERLEVEL9K_PACKAGE_VISUAL_IDENTIFIER_EXPANSION='⭐'
1032
1033 #############[ rbenv: ruby version from rbenv (https://github.com/rbenv/rbenv) ]##############
1034 # Rbenv color.
1035 typeset -g POWERLEVEL9K_RBENV_FOREGROUND=168
1036 # Hide ruby version if it doesn't come from one of these sources.
1037 typeset -g POWERLEVEL9K_RBENV_SOURCES=(shell local global)
1038 # If set to false, hide ruby version if it's the same as global:
1039 # $(rbenv version-name) == $(rbenv global).
1040 typeset -g POWERLEVEL9K_RBENV_PROMPT_ALWAYS_SHOW=false
1041 # If set to false, hide ruby version if it's equal to "system".
1042 typeset -g POWERLEVEL9K_RBENV_SHOW_SYSTEM=true
1043 # Custom icon.
1044 # typeset -g POWERLEVEL9K_RBENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
1045
1046 #######################[ rvm: ruby version from rvm (https://rvm.io) ]########################
1047 # Rvm color.
1048 typeset -g POWERLEVEL9K_RVM_FOREGROUND=168
1049 # Don't show @gemset at the end.
1050 typeset -g POWERLEVEL9K_RVM_SHOW_GEMSET=false
1051 # Don't show ruby- at the front.
1052 typeset -g POWERLEVEL9K_RVM_SHOW_PREFIX=false
1053 # Custom icon.
1054 # typeset -g POWERLEVEL9K_RVM_VISUAL_IDENTIFIER_EXPANSION='⭐'
1055
1056 ###########[ fvm: flutter version management (https://github.com/leoafarias/fvm) ]############
1057 # Fvm color.
1058 typeset -g POWERLEVEL9K_FVM_FOREGROUND=38
1059 # Custom icon.
1060 # typeset -g POWERLEVEL9K_FVM_VISUAL_IDENTIFIER_EXPANSION='⭐'
1061
1062 ##########[ luaenv: lua version from luaenv (https://github.com/cehoffman/luaenv) ]###########
1063 # Lua color.
1064 typeset -g POWERLEVEL9K_LUAENV_FOREGROUND=32
1065 # Hide lua version if it doesn't come from one of these sources.
1066 typeset -g POWERLEVEL9K_LUAENV_SOURCES=(shell local global)
1067 # If set to false, hide lua version if it's the same as global:
1068 # $(luaenv version-name) == $(luaenv global).
1069 typeset -g POWERLEVEL9K_LUAENV_PROMPT_ALWAYS_SHOW=false
1070 # If set to false, hide lua version if it's equal to "system".
1071 typeset -g POWERLEVEL9K_LUAENV_SHOW_SYSTEM=true
1072 # Custom icon.
1073 # typeset -g POWERLEVEL9K_LUAENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
1074
1075 ###############[ jenv: java version from jenv (https://github.com/jenv/jenv) ]################
1076 # Java color.
1077 typeset -g POWERLEVEL9K_JENV_FOREGROUND=32
1078 # Hide java version if it doesn't come from one of these sources.
1079 typeset -g POWERLEVEL9K_JENV_SOURCES=(shell local global)
1080 # If set to false, hide java version if it's the same as global:
1081 # $(jenv version-name) == $(jenv global).
1082 typeset -g POWERLEVEL9K_JENV_PROMPT_ALWAYS_SHOW=false
1083 # If set to false, hide java version if it's equal to "system".
1084 typeset -g POWERLEVEL9K_JENV_SHOW_SYSTEM=true
1085 # Custom icon.
1086 # typeset -g POWERLEVEL9K_JENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
1087
1088 ###########[ plenv: perl version from plenv (https://github.com/tokuhirom/plenv) ]############
1089 # Perl color.
1090 typeset -g POWERLEVEL9K_PLENV_FOREGROUND=67
1091 # Hide perl version if it doesn't come from one of these sources.
1092 typeset -g POWERLEVEL9K_PLENV_SOURCES=(shell local global)
1093 # If set to false, hide perl version if it's the same as global:
1094 # $(plenv version-name) == $(plenv global).
1095 typeset -g POWERLEVEL9K_PLENV_PROMPT_ALWAYS_SHOW=false
1096 # If set to false, hide perl version if it's equal to "system".
1097 typeset -g POWERLEVEL9K_PLENV_SHOW_SYSTEM=true
1098 # Custom icon.
1099 # typeset -g POWERLEVEL9K_PLENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
1100
1101 ############[ phpenv: php version from phpenv (https://github.com/phpenv/phpenv) ]############
1102 # PHP color.
1103 typeset -g POWERLEVEL9K_PHPENV_FOREGROUND=99
1104 # Hide php version if it doesn't come from one of these sources.
1105 typeset -g POWERLEVEL9K_PHPENV_SOURCES=(shell local global)
1106 # If set to false, hide php version if it's the same as global:
1107 # $(phpenv version-name) == $(phpenv global).
1108 typeset -g POWERLEVEL9K_PHPENV_PROMPT_ALWAYS_SHOW=false
1109 # If set to false, hide php version if it's equal to "system".
1110 typeset -g POWERLEVEL9K_PHPENV_SHOW_SYSTEM=true
1111 # Custom icon.
1112 # typeset -g POWERLEVEL9K_PHPENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
1113
1114 #######[ scalaenv: scala version from scalaenv (https://github.com/scalaenv/scalaenv) ]#######
1115 # Scala color.
1116 typeset -g POWERLEVEL9K_SCALAENV_FOREGROUND=160
1117 # Hide scala version if it doesn't come from one of these sources.
1118 typeset -g POWERLEVEL9K_SCALAENV_SOURCES=(shell local global)
1119 # If set to false, hide scala version if it's the same as global:
1120 # $(scalaenv version-name) == $(scalaenv global).
1121 typeset -g POWERLEVEL9K_SCALAENV_PROMPT_ALWAYS_SHOW=false
1122 # If set to false, hide scala version if it's equal to "system".
1123 typeset -g POWERLEVEL9K_SCALAENV_SHOW_SYSTEM=true
1124 # Custom icon.
1125 # typeset -g POWERLEVEL9K_SCALAENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
1126
1127 ##########[ haskell_stack: haskell version from stack (https://haskellstack.org/) ]###########
1128 # Haskell color.
1129 typeset -g POWERLEVEL9K_HASKELL_STACK_FOREGROUND=172
1130 # Hide haskell version if it doesn't come from one of these sources.
1131 #
1132 # shell: version is set by STACK_YAML
1133 # local: version is set by stack.yaml up the directory tree
1134 # global: version is set by the implicit global project (~/.stack/global-project/stack.yaml)
1135 typeset -g POWERLEVEL9K_HASKELL_STACK_SOURCES=(shell local)
1136 # If set to false, hide haskell version if it's the same as in the implicit global project.
1137 typeset -g POWERLEVEL9K_HASKELL_STACK_ALWAYS_SHOW=true
1138 # Custom icon.
1139 # typeset -g POWERLEVEL9K_HASKELL_STACK_VISUAL_IDENTIFIER_EXPANSION='⭐'
1140
1141 #############[ kubecontext: current kubernetes context (https://kubernetes.io/) ]#############
1142 # Show kubecontext only when the the command you are typing invokes one of these tools.
1143 # Tip: Remove the next line to always show kubecontext.
1144 typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito|k9s|helmfile'
1145
1146 # Kubernetes context classes for the purpose of using different colors, icons and expansions with
1147 # different contexts.
1148 #
1149 # POWERLEVEL9K_KUBECONTEXT_CLASSES is an array with even number of elements. The first element
1150 # in each pair defines a pattern against which the current kubernetes context gets matched.
1151 # More specifically, it's P9K_CONTENT prior to the application of context expansion (see below)
1152 # that gets matched. If you unset all POWERLEVEL9K_KUBECONTEXT_*CONTENT_EXPANSION parameters,
1153 # you'll see this value in your prompt. The second element of each pair in
1154 # POWERLEVEL9K_KUBECONTEXT_CLASSES defines the context class. Patterns are tried in order. The
1155 # first match wins.
1156 #
1157 # For example, given these settings:
1158 #
1159 # typeset -g POWERLEVEL9K_KUBECONTEXT_CLASSES=(
1160 # '*prod*' PROD
1161 # '*test*' TEST
1162 # '*' DEFAULT)
1163 #
1164 # If your current kubernetes context is "deathray-testing/default", its class is TEST
1165 # because "deathray-testing/default" doesn't match the pattern '*prod*' but does match '*test*'.
1166 #
1167 # You can define different colors, icons and content expansions for different classes:
1168 #
1169 # typeset -g POWERLEVEL9K_KUBECONTEXT_TEST_FOREGROUND=28
1170 # typeset -g POWERLEVEL9K_KUBECONTEXT_TEST_VISUAL_IDENTIFIER_EXPANSION='⭐'
1171 # typeset -g POWERLEVEL9K_KUBECONTEXT_TEST_CONTENT_EXPANSION='> ${P9K_CONTENT} <'
1172 typeset -g POWERLEVEL9K_KUBECONTEXT_CLASSES=(
1173 # '*prod*' PROD # These values are examples that are unlikely
1174 # '*test*' TEST # to match your needs. Customize them as needed.
1175 '*' DEFAULT)
1176 typeset -g POWERLEVEL9K_KUBECONTEXT_DEFAULT_FOREGROUND=134
1177 # typeset -g POWERLEVEL9K_KUBECONTEXT_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='⭐'
1178
1179 # Use POWERLEVEL9K_KUBECONTEXT_CONTENT_EXPANSION to specify the content displayed by kubecontext
1180 # segment. Parameter expansions are very flexible and fast, too. See reference:
1181 # http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion.
1182 #
1183 # Within the expansion the following parameters are always available:
1184 #
1185 # - P9K_CONTENT The content that would've been displayed if there was no content
1186 # expansion defined.
1187 # - P9K_KUBECONTEXT_NAME The current context's name. Corresponds to column NAME in the
1188 # output of `kubectl config get-contexts`.
1189 # - P9K_KUBECONTEXT_CLUSTER The current context's cluster. Corresponds to column CLUSTER in the
1190 # output of `kubectl config get-contexts`.
1191 # - P9K_KUBECONTEXT_NAMESPACE The current context's namespace. Corresponds to column NAMESPACE
1192 # in the output of `kubectl config get-contexts`. If there is no
1193 # namespace, the parameter is set to "default".
1194 # - P9K_KUBECONTEXT_USER The current context's user. Corresponds to column AUTHINFO in the
1195 # output of `kubectl config get-contexts`.
1196 #
1197 # If the context points to Google Kubernetes Engine (GKE) or Elastic Kubernetes Service (EKS),
1198 # the following extra parameters are available:
1199 #
1200 # - P9K_KUBECONTEXT_CLOUD_NAME Either "gke" or "eks".
1201 # - P9K_KUBECONTEXT_CLOUD_ACCOUNT Account/project ID.
1202 # - P9K_KUBECONTEXT_CLOUD_ZONE Availability zone.
1203 # - P9K_KUBECONTEXT_CLOUD_CLUSTER Cluster.
1204 #
1205 # P9K_KUBECONTEXT_CLOUD_* parameters are derived from P9K_KUBECONTEXT_CLUSTER. For example,
1206 # if P9K_KUBECONTEXT_CLUSTER is "gke_my-account_us-east1-a_my-cluster-01":
1207 #
1208 # - P9K_KUBECONTEXT_CLOUD_NAME=gke
1209 # - P9K_KUBECONTEXT_CLOUD_ACCOUNT=my-account
1210 # - P9K_KUBECONTEXT_CLOUD_ZONE=us-east1-a
1211 # - P9K_KUBECONTEXT_CLOUD_CLUSTER=my-cluster-01
1212 #
1213 # If P9K_KUBECONTEXT_CLUSTER is "arn:aws:eks:us-east-1:123456789012:cluster/my-cluster-01":
1214 #
1215 # - P9K_KUBECONTEXT_CLOUD_NAME=eks
1216 # - P9K_KUBECONTEXT_CLOUD_ACCOUNT=123456789012
1217 # - P9K_KUBECONTEXT_CLOUD_ZONE=us-east-1
1218 # - P9K_KUBECONTEXT_CLOUD_CLUSTER=my-cluster-01
1219 typeset -g POWERLEVEL9K_KUBECONTEXT_DEFAULT_CONTENT_EXPANSION=
1220 # Show P9K_KUBECONTEXT_CLOUD_CLUSTER if it's not empty and fall back to P9K_KUBECONTEXT_NAME.
1221 POWERLEVEL9K_KUBECONTEXT_DEFAULT_CONTENT_EXPANSION+='${P9K_KUBECONTEXT_CLOUD_CLUSTER:-${P9K_KUBECONTEXT_NAME}}'
1222 # Append the current context's namespace if it's not "default".
1223 POWERLEVEL9K_KUBECONTEXT_DEFAULT_CONTENT_EXPANSION+='${${:-/$P9K_KUBECONTEXT_NAMESPACE}:#/default}'
1224
1225 # Custom prefix.
1226 # typeset -g POWERLEVEL9K_KUBECONTEXT_PREFIX='%fat '
1227
1228 ################[ terraform: terraform workspace (https://www.terraform.io) ]#################
1229 # Don't show terraform workspace if it's literally "default".
1230 typeset -g POWERLEVEL9K_TERRAFORM_SHOW_DEFAULT=false
1231 # POWERLEVEL9K_TERRAFORM_CLASSES is an array with even number of elements. The first element
1232 # in each pair defines a pattern against which the current terraform workspace gets matched.
1233 # More specifically, it's P9K_CONTENT prior to the application of context expansion (see below)
1234 # that gets matched. If you unset all POWERLEVEL9K_TERRAFORM_*CONTENT_EXPANSION parameters,
1235 # you'll see this value in your prompt. The second element of each pair in
1236 # POWERLEVEL9K_TERRAFORM_CLASSES defines the workspace class. Patterns are tried in order. The
1237 # first match wins.
1238 #
1239 # For example, given these settings:
1240 #
1241 # typeset -g POWERLEVEL9K_TERRAFORM_CLASSES=(
1242 # '*prod*' PROD
1243 # '*test*' TEST
1244 # '*' OTHER)
1245 #
1246 # If your current terraform workspace is "project_test", its class is TEST because "project_test"
1247 # doesn't match the pattern '*prod*' but does match '*test*'.
1248 #
1249 # You can define different colors, icons and content expansions for different classes:
1250 #
1251 # typeset -g POWERLEVEL9K_TERRAFORM_TEST_FOREGROUND=28
1252 # typeset -g POWERLEVEL9K_TERRAFORM_TEST_VISUAL_IDENTIFIER_EXPANSION='⭐'
1253 # typeset -g POWERLEVEL9K_TERRAFORM_TEST_CONTENT_EXPANSION='> ${P9K_CONTENT} <'
1254 typeset -g POWERLEVEL9K_TERRAFORM_CLASSES=(
1255 # '*prod*' PROD # These values are examples that are unlikely
1256 # '*test*' TEST # to match your needs. Customize them as needed.
1257 '*' OTHER)
1258 typeset -g POWERLEVEL9K_TERRAFORM_OTHER_FOREGROUND=38
1259 # typeset -g POWERLEVEL9K_TERRAFORM_OTHER_VISUAL_IDENTIFIER_EXPANSION='⭐'
1260
1261 #[ aws: aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) ]#
1262 # Show aws only when the the command you are typing invokes one of these tools.
1263 # Tip: Remove the next line to always show aws.
1264 typeset -g POWERLEVEL9K_AWS_SHOW_ON_COMMAND='aws|awless|terraform|pulumi|terragrunt'
1265
1266 # POWERLEVEL9K_AWS_CLASSES is an array with even number of elements. The first element
1267 # in each pair defines a pattern against which the current AWS profile gets matched.
1268 # More specifically, it's P9K_CONTENT prior to the application of context expansion (see below)
1269 # that gets matched. If you unset all POWERLEVEL9K_AWS_*CONTENT_EXPANSION parameters,
1270 # you'll see this value in your prompt. The second element of each pair in
1271 # POWERLEVEL9K_AWS_CLASSES defines the profile class. Patterns are tried in order. The
1272 # first match wins.
1273 #
1274 # For example, given these settings:
1275 #
1276 # typeset -g POWERLEVEL9K_AWS_CLASSES=(
1277 # '*prod*' PROD
1278 # '*test*' TEST
1279 # '*' DEFAULT)
1280 #
1281 # If your current AWS profile is "company_test", its class is TEST
1282 # because "company_test" doesn't match the pattern '*prod*' but does match '*test*'.
1283 #
1284 # You can define different colors, icons and content expansions for different classes:
1285 #
1286 # typeset -g POWERLEVEL9K_AWS_TEST_FOREGROUND=28
1287 # typeset -g POWERLEVEL9K_AWS_TEST_VISUAL_IDENTIFIER_EXPANSION='⭐'
1288 # typeset -g POWERLEVEL9K_AWS_TEST_CONTENT_EXPANSION='> ${P9K_CONTENT} <'
1289 typeset -g POWERLEVEL9K_AWS_CLASSES=(
1290 # '*prod*' PROD # These values are examples that are unlikely
1291 # '*test*' TEST # to match your needs. Customize them as needed.
1292 '*' DEFAULT)
1293 typeset -g POWERLEVEL9K_AWS_DEFAULT_FOREGROUND=208
1294 # typeset -g POWERLEVEL9K_AWS_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='⭐'
1295
1296 #[ aws_eb_env: aws elastic beanstalk environment (https://aws.amazon.com/elasticbeanstalk/) ]#
1297 # AWS Elastic Beanstalk environment color.
1298 typeset -g POWERLEVEL9K_AWS_EB_ENV_FOREGROUND=70
1299 # Custom icon.
1300 # typeset -g POWERLEVEL9K_AWS_EB_ENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
1301
1302 ##########[ azure: azure account name (https://docs.microsoft.com/en-us/cli/azure) ]##########
1303 # Show azure only when the the command you are typing invokes one of these tools.
1304 # Tip: Remove the next line to always show azure.
1305 typeset -g POWERLEVEL9K_AZURE_SHOW_ON_COMMAND='az|terraform|pulumi|terragrunt'
1306 # Azure account name color.
1307 typeset -g POWERLEVEL9K_AZURE_FOREGROUND=32
1308 # Custom icon.
1309 # typeset -g POWERLEVEL9K_AZURE_VISUAL_IDENTIFIER_EXPANSION='⭐'
1310
1311 ##########[ gcloud: google cloud account and project (https://cloud.google.com/) ]###########
1312 # Show gcloud only when the the command you are typing invokes one of these tools.
1313 # Tip: Remove the next line to always show gcloud.
1314 typeset -g POWERLEVEL9K_GCLOUD_SHOW_ON_COMMAND='gcloud|gcs'
1315 # Google cloud color.
1316 typeset -g POWERLEVEL9K_GCLOUD_FOREGROUND=32
1317
1318 # Google cloud format. Change the value of POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION and/or
1319 # POWERLEVEL9K_GCLOUD_COMPLETE_CONTENT_EXPANSION if the default is too verbose or not informative
1320 # enough. You can use the following parameters in the expansions. Each of them corresponds to the
1321 # output of `gcloud` tool.
1322 #
1323 # Parameter | Source
1324 # -------------------------|--------------------------------------------------------------------
1325 # P9K_GCLOUD_CONFIGURATION | gcloud config configurations list --format='value(name)'
1326 # P9K_GCLOUD_ACCOUNT | gcloud config get-value account
1327 # P9K_GCLOUD_PROJECT_ID | gcloud config get-value project
1328 # P9K_GCLOUD_PROJECT_NAME | gcloud projects describe $P9K_GCLOUD_PROJECT_ID --format='value(name)'
1329 #
1330 # Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurrences of '%' replaced with '%%'.
1331 #
1332 # Obtaining project name requires sending a request to Google servers. This can take a long time
1333 # and even fail. When project name is unknown, P9K_GCLOUD_PROJECT_NAME is not set and gcloud
1334 # prompt segment is in state PARTIAL. When project name gets known, P9K_GCLOUD_PROJECT_NAME gets
1335 # set and gcloud prompt segment transitions to state COMPLETE.
1336 #
1337 # You can customize the format, icon and colors of gcloud segment separately for states PARTIAL
1338 # and COMPLETE. You can also hide gcloud in state PARTIAL by setting
1339 # POWERLEVEL9K_GCLOUD_PARTIAL_VISUAL_IDENTIFIER_EXPANSION and
1340 # POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION to empty.
1341 typeset -g POWERLEVEL9K_GCLOUD_PARTIAL_CONTENT_EXPANSION='${P9K_GCLOUD_PROJECT_ID//\%/%%}'
1342 typeset -g POWERLEVEL9K_GCLOUD_COMPLETE_CONTENT_EXPANSION='${P9K_GCLOUD_PROJECT_NAME//\%/%%}'
1343
1344 # Send a request to Google (by means of `gcloud projects describe ...`) to obtain project name
1345 # this often. Negative value disables periodic polling. In this mode project name is retrieved
1346 # only when the current configuration, account or project id changes.
1347 typeset -g POWERLEVEL9K_GCLOUD_REFRESH_PROJECT_NAME_SECONDS=60
1348
1349 # Custom icon.
1350 # typeset -g POWERLEVEL9K_GCLOUD_VISUAL_IDENTIFIER_EXPANSION='⭐'
1351
1352 #[ google_app_cred: google application credentials (https://cloud.google.com/docs/authentication/production) ]#
1353 # Show google_app_cred only when the the command you are typing invokes one of these tools.
1354 # Tip: Remove the next line to always show google_app_cred.
1355 typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_SHOW_ON_COMMAND='terraform|pulumi|terragrunt'
1356
1357 # Google application credentials classes for the purpose of using different colors, icons and
1358 # expansions with different credentials.
1359 #
1360 # POWERLEVEL9K_GOOGLE_APP_CRED_CLASSES is an array with even number of elements. The first
1361 # element in each pair defines a pattern against which the current kubernetes context gets
1362 # matched. More specifically, it's P9K_CONTENT prior to the application of context expansion
1363 # (see below) that gets matched. If you unset all POWERLEVEL9K_GOOGLE_APP_CRED_*CONTENT_EXPANSION
1364 # parameters, you'll see this value in your prompt. The second element of each pair in
1365 # POWERLEVEL9K_GOOGLE_APP_CRED_CLASSES defines the context class. Patterns are tried in order.
1366 # The first match wins.
1367 #
1368 # For example, given these settings:
1369 #
1370 # typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_CLASSES=(
1371 # '*:*prod*:*' PROD
1372 # '*:*test*:*' TEST
1373 # '*' DEFAULT)
1374 #
1375 # If your current Google application credentials is "service_account deathray-testing x@y.com",
1376 # its class is TEST because it doesn't match the pattern '* *prod* *' but does match '* *test* *'.
1377 #
1378 # You can define different colors, icons and content expansions for different classes:
1379 #
1380 # typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_TEST_FOREGROUND=28
1381 # typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_TEST_VISUAL_IDENTIFIER_EXPANSION='⭐'
1382 # typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_TEST_CONTENT_EXPANSION='$P9K_GOOGLE_APP_CRED_PROJECT_ID'
1383 typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_CLASSES=(
1384 # '*:*prod*:*' PROD # These values are examples that are unlikely
1385 # '*:*test*:*' TEST # to match your needs. Customize them as needed.
1386 '*' DEFAULT)
1387 typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_FOREGROUND=32
1388 # typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='⭐'
1389
1390 # Use POWERLEVEL9K_GOOGLE_APP_CRED_CONTENT_EXPANSION to specify the content displayed by
1391 # google_app_cred segment. Parameter expansions are very flexible and fast, too. See reference:
1392 # http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion.
1393 #
1394 # You can use the following parameters in the expansion. Each of them corresponds to one of the
1395 # fields in the JSON file pointed to by GOOGLE_APPLICATION_CREDENTIALS.
1396 #
1397 # Parameter | JSON key file field
1398 # ---------------------------------+---------------
1399 # P9K_GOOGLE_APP_CRED_TYPE | type
1400 # P9K_GOOGLE_APP_CRED_PROJECT_ID | project_id
1401 # P9K_GOOGLE_APP_CRED_CLIENT_EMAIL | client_email
1402 #
1403 # Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurrences of '%' replaced by '%%'.
1404 typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_CONTENT_EXPANSION='${P9K_GOOGLE_APP_CRED_PROJECT_ID//\%/%%}'
1405
1406 ###############################[ public_ip: public IP address ]###############################
1407 # Public IP color.
1408 typeset -g POWERLEVEL9K_PUBLIC_IP_FOREGROUND=94
1409 # Custom icon.
1410 # typeset -g POWERLEVEL9K_PUBLIC_IP_VISUAL_IDENTIFIER_EXPANSION='⭐'
1411
1412 ########################[ vpn_ip: virtual private network indicator ]#########################
1413 # VPN IP color.
1414 typeset -g POWERLEVEL9K_VPN_IP_FOREGROUND=81
1415 # When on VPN, show just an icon without the IP address.
1416 # Tip: To display the private IP address when on VPN, remove the next line.
1417 typeset -g POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION=
1418 # Regular expression for the VPN network interface. Run `ifconfig` or `ip -4 a show` while on VPN
1419 # to see the name of the interface.
1420 typeset -g POWERLEVEL9K_VPN_IP_INTERFACE='(gpd|wg|(.*tun))[0-9]*'
1421 # If set to true, show one segment per matching network interface. If set to false, show only
1422 # one segment corresponding to the first matching network interface.
1423 # Tip: If you set it to true, you'll probably want to unset POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION.
1424 typeset -g POWERLEVEL9K_VPN_IP_SHOW_ALL=false
1425 # Custom icon.
1426 # typeset -g POWERLEVEL9K_VPN_IP_VISUAL_IDENTIFIER_EXPANSION='⭐'
1427
1428 ###########[ ip: ip address and bandwidth usage for a specified network interface ]###########
1429 # IP color.
1430 typeset -g POWERLEVEL9K_IP_FOREGROUND=38
1431 # The following parameters are accessible within the expansion:
1432 #
1433 # Parameter | Meaning
1434 # ----------------------+---------------
1435 # P9K_IP_IP | IP address
1436 # P9K_IP_INTERFACE | network interface
1437 # P9K_IP_RX_BYTES | total number of bytes received
1438 # P9K_IP_TX_BYTES | total number of bytes sent
1439 # P9K_IP_RX_RATE | receive rate (since last prompt)
1440 # P9K_IP_TX_RATE | send rate (since last prompt)
1441 typeset -g POWERLEVEL9K_IP_CONTENT_EXPANSION='$P9K_IP_IP${P9K_IP_RX_RATE:+ %70F⇣$P9K_IP_RX_RATE}${P9K_IP_TX_RATE:+ %215F⇡$P9K_IP_TX_RATE}'
1442 # Show information for the first network interface whose name matches this regular expression.
1443 # Run `ifconfig` or `ip -4 a show` to see the names of all network interfaces.
1444 typeset -g POWERLEVEL9K_IP_INTERFACE='e.*'
1445 # Custom icon.
1446 # typeset -g POWERLEVEL9K_IP_VISUAL_IDENTIFIER_EXPANSION='⭐'
1447
1448 #########################[ proxy: system-wide http/https/ftp proxy ]##########################
1449 # Proxy color.
1450 typeset -g POWERLEVEL9K_PROXY_FOREGROUND=68
1451 # Custom icon.
1452 # typeset -g POWERLEVEL9K_PROXY_VISUAL_IDENTIFIER_EXPANSION='⭐'
1453
1454 ################################[ battery: internal battery ]#################################
1455 # Show battery in red when it's below this level and not connected to power supply.
1456 typeset -g POWERLEVEL9K_BATTERY_LOW_THRESHOLD=20
1457 typeset -g POWERLEVEL9K_BATTERY_LOW_FOREGROUND=160
1458 # Show battery in green when it's charging or fully charged.
1459 typeset -g POWERLEVEL9K_BATTERY_{CHARGING,CHARGED}_FOREGROUND=70
1460 # Show battery in yellow when it's discharging.
1461 typeset -g POWERLEVEL9K_BATTERY_DISCONNECTED_FOREGROUND=178
1462 # Battery pictograms going from low to high level of charge.
1463 typeset -g POWERLEVEL9K_BATTERY_STAGES='\uf58d\uf579\uf57a\uf57b\uf57c\uf57d\uf57e\uf57f\uf580\uf581\uf578'
1464 # Don't show the remaining time to charge/discharge.
1465 typeset -g POWERLEVEL9K_BATTERY_VERBOSE=false
1466
1467 #####################################[ wifi: wifi speed ]#####################################
1468 # WiFi color.
1469 typeset -g POWERLEVEL9K_WIFI_FOREGROUND=68
1470 # Custom icon.
1471 # typeset -g POWERLEVEL9K_WIFI_VISUAL_IDENTIFIER_EXPANSION='⭐'
1472
1473 # Use different colors and icons depending on signal strength ($P9K_WIFI_BARS).
1474 #
1475 # # Wifi colors and icons for different signal strength levels (low to high).
1476 # typeset -g my_wifi_fg=(68 68 68 68 68) # <-- change these values
1477 # typeset -g my_wifi_icon=('WiFi' 'WiFi' 'WiFi' 'WiFi' 'WiFi') # <-- change these values
1478 #
1479 # typeset -g POWERLEVEL9K_WIFI_CONTENT_EXPANSION='%F{${my_wifi_fg[P9K_WIFI_BARS+1]}}$P9K_WIFI_LAST_TX_RATE Mbps'
1480 # typeset -g POWERLEVEL9K_WIFI_VISUAL_IDENTIFIER_EXPANSION='%F{${my_wifi_fg[P9K_WIFI_BARS+1]}}${my_wifi_icon[P9K_WIFI_BARS+1]}'
1481 #
1482 # The following parameters are accessible within the expansions:
1483 #
1484 # Parameter | Meaning
1485 # ----------------------+---------------
1486 # P9K_WIFI_SSID | service set identifier, a.k.a. network name
1487 # P9K_WIFI_LINK_AUTH | authentication protocol such as "wpa2-psk" or "none"; empty if unknown
1488 # P9K_WIFI_LAST_TX_RATE | wireless transmit rate in megabits per second
1489 # P9K_WIFI_RSSI | signal strength in dBm, from -120 to 0
1490 # P9K_WIFI_NOISE | noise in dBm, from -120 to 0
1491 # P9K_WIFI_BARS | signal strength in bars, from 0 to 4 (derived from P9K_WIFI_RSSI and P9K_WIFI_NOISE)
1492
1493 ####################################[ time: current time ]####################################
1494 # Current time color.
1495 typeset -g POWERLEVEL9K_TIME_FOREGROUND=66
1496 # Format for the current time: 09:51:02. See `man 3 strftime`.
1497 typeset -g POWERLEVEL9K_TIME_FORMAT='%D{%H:%M}'
1498 # If set to true, time will update when you hit enter. This way prompts for the past
1499 # commands will contain the start times of their commands as opposed to the default
1500 # behavior where they contain the end times of their preceding commands.
1501 typeset -g POWERLEVEL9K_TIME_UPDATE_ON_COMMAND=false
1502 # Custom icon.
1503 typeset -g POWERLEVEL9K_TIME_VISUAL_IDENTIFIER_EXPANSION=''
1504 # Custom prefix.
1505 # typeset -g POWERLEVEL9K_TIME_PREFIX='%fat '
1506
1507 # Example of a user-defined prompt segment. Function prompt_example will be called on every
1508 # prompt if `example` prompt segment is added to POWERLEVEL9K_LEFT_PROMPT_ELEMENTS or
1509 # POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS. It displays an icon and orange text greeting the user.
1510 #
1511 # Type `p10k help segment` for documentation and a more sophisticated example.
1512 function prompt_example() {
1513 p10k segment -f 208 -i '⭐' -t 'hello, %n'
1514 }
1515
1516 # User-defined prompt segments may optionally provide an instant_prompt_* function. Its job
1517 # is to generate the prompt segment for display in instant prompt. See
1518 # https://github.com/romkatv/powerlevel10k/blob/master/README.md#instant-prompt.
1519 #
1520 # Powerlevel10k will call instant_prompt_* at the same time as the regular prompt_* function
1521 # and will record all `p10k segment` calls it makes. When displaying instant prompt, Powerlevel10k
1522 # will replay these calls without actually calling instant_prompt_*. It is imperative that
1523 # instant_prompt_* always makes the same `p10k segment` calls regardless of environment. If this
1524 # rule is not observed, the content of instant prompt will be incorrect.
1525 #
1526 # Usually, you should either not define instant_prompt_* or simply call prompt_* from it. If
1527 # instant_prompt_* is not defined for a segment, the segment won't be shown in instant prompt.
1528 function instant_prompt_example() {
1529 # Since prompt_example always makes the same `p10k segment` calls, we can call it from
1530 # instant_prompt_example. This will give us the same `example` prompt segment in the instant
1531 # and regular prompts.
1532 prompt_example
1533 }
1534
1535 # User-defined prompt segments can be customized the same way as built-in segments.
1536 # typeset -g POWERLEVEL9K_EXAMPLE_FOREGROUND=208
1537 # typeset -g POWERLEVEL9K_EXAMPLE_VISUAL_IDENTIFIER_EXPANSION='⭐'
1538
1539 # Transient prompt works similarly to the builtin transient_rprompt option. It trims down prompt
1540 # when accepting a command line. Supported values:
1541 #
1542 # - off: Don't change prompt when accepting a command line.
1543 # - always: Trim down prompt when accepting a command line.
1544 # - same-dir: Trim down prompt when accepting a command line unless this is the first command
1545 # typed after changing current working directory.
1546 typeset -g POWERLEVEL9K_TRANSIENT_PROMPT=off
1547
1548 function p10k-on-pre-prompt() { p10k display '1'=show '2/left/(dir|context)'=hide '2/right/*'=show '2/right/(status|command_execution_time|time|background_jobs)'=hide }
1549 function p10k-on-post-prompt() { p10k display '2/left/(dir|context|prompt_char)'=show 'empty_line|1'=hide '2/right/*'=hide '2/right/(status|command_execution_time|time|background_jobs)'=show }
1550
1551 # Instant prompt mode.
1552 #
1553 # - off: Disable instant prompt. Choose this if you've tried instant prompt and found
1554 # it incompatible with your zsh configuration files.
1555 # - quiet: Enable instant prompt and don't print warnings when detecting console output
1556 # during zsh initialization. Choose this if you've read and understood
1557 # https://github.com/romkatv/powerlevel10k/blob/master/README.md#instant-prompt.
1558 # - verbose: Enable instant prompt and print a warning when detecting console output during
1559 # zsh initialization. Choose this if you've never tried instant prompt, haven't
1560 # seen the warning, or if you are unsure what this all means.
1561 typeset -g POWERLEVEL9K_INSTANT_PROMPT=quiet
1562
1563 # Hot reload allows you to change POWERLEVEL9K options after Powerlevel10k has been initialized.
1564 # For example, you can type POWERLEVEL9K_BACKGROUND=red and see your prompt turn red. Hot reload
1565 # can slow down prompt by 1-2 milliseconds, so it's better to keep it turned off unless you
1566 # really need it.
1567 typeset -g POWERLEVEL9K_DISABLE_HOT_RELOAD=true
1568
1569 # If p10k is already loaded, reload configuration.
1570 # This works even with POWERLEVEL9K_DISABLE_HOT_RELOAD=true.
1571 (( ! $+functions[p10k] )) || p10k reload
1572}
1573
1574# Tell `p10k configure` which file it should overwrite.
1575typeset -g POWERLEVEL9K_CONFIG_FILE=${${(%):-%x}:a}
1576
1577(( ${#p10k_config_opts} )) && setopt ${p10k_config_opts[@]}
1578'builtin' 'unset' 'p10k_config_opts'
diff --git a/user-profiles/zsh/zshrc b/user-profiles/zsh/zshrc
new file mode 100644
index 00000000..3660ef27
--- /dev/null
+++ b/user-profiles/zsh/zshrc
@@ -0,0 +1,26 @@
1fancy-ctrl-z () {
2 emulate -LR zsh
3 if [[ $#BUFFER -eq 0 ]]; then
4 [[ -n $(jobs -s) ]] && bg
5 zle redisplay
6 else
7 zle push-input
8 fi
9}
10zle -N fancy-ctrl-z
11bindkey '^Z' fancy-ctrl-z
12
13function fancy-ctrl-d() {
14 zle || exit 0
15 [[ -n $BUFFER ]] && return
16 typeset -g precmd_functions=(fancy-ctrl-d)
17 zle accept-line
18}
19zle -N fancy-ctrl-d
20bindkey '^D' fancy-ctrl-d
21setopt ignore_eof
22
23# word navigation for urxvt
24bindkey -e
25bindkey ';5C' emacs-forward-word
26bindkey ';5D' emacs-backward-word \ No newline at end of file
diff --git a/users/gkleen/authorized-keys/gkleen-sif.pub b/users/gkleen/authorized-keys/gkleen-sif.pub
new file mode 100644
index 00000000..e9aaf215
--- /dev/null
+++ b/users/gkleen/authorized-keys/gkleen-sif.pub
@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKrHPERae+OUTNOzNf9d2767ljFCm5hgmQw48Dj4RrlU gkleen@sif.midgard.yggdrasil
diff --git a/users/gkleen/default.nix b/users/gkleen/default.nix
new file mode 100644
index 00000000..582bac46
--- /dev/null
+++ b/users/gkleen/default.nix
@@ -0,0 +1,48 @@
1{ flake, userName, pkgs, customUtils, lib, ... }:
2{
3 imports = with flake.nixosModules.userProfiles.${userName}; [
4 zsh tmux utils direnv
5 ];
6
7 users.users.${userName} = {
8 description = "Gregor Kleen";
9 extraGroups = [ "wheel" "networkmanager" "lp" "dialout" "audio" "video" "xmpp" "mail" "ssh" "vboxusers" "libvirtd" "wireshark" "games"];
10 group = "users";
11 uid = 1000;
12 createHome = true;
13 home = "/home/${userName}";
14 shell = "${pkgs.zsh}/bin/zsh";
15 isNormalUser = true;
16 openssh.authorizedKeys.keyFiles = lib.attrValues (customUtils.recImport rec { dir = ./authorized-keys; _import = name: _base: dir + "/${name}"; });
17 hashedPassword = "$6$rounds=500000$dOMgCU7DAk$yQFYGOURTEt12387LIYBnFKSWmtwXMUk1LJWnV0m7OFt.y2TnxQn2abdGA5dhwG9EmMB5wZGXf4J5F71c746C/";
18 };
19
20 home-manager.users.${userName} = {
21 programs = {
22 git = {
23 enable = true;
24 userEmail = "gkleen@yggdrasil.li";
25 userName = "Gregor Kleen";
26 delta.enable = true;
27 extraConfig = {
28 pull.rebase = false;
29 submodule.recurse = true;
30 };
31 };
32
33 ssh = {
34 enable = true;
35 controlMaster = "auto";
36 controlPersist = "30m";
37 serverAliveInterval = 6;
38 serverAliveCountMax = 10;
39 hashKnownHosts = true;
40 extraConfig = ''
41 IdentitiesOnly true
42 '';
43 };
44
45 gpg.enable = true;
46 };
47 };
48}
diff --git a/users/root.nix b/users/root.nix
new file mode 100644
index 00000000..be331141
--- /dev/null
+++ b/users/root.nix
@@ -0,0 +1,52 @@
1{ flake, lib, config, hostName, userName, pkgs, ... }:
2let
3 haveGKleen = flake.nixosModules.accounts ? "gkleen@${hostName}";
4in {
5 imports = with flake.nixosModules.userProfiles.${userName}; [
6 zsh tmux direnv utils
7 ];
8
9 users.users.${userName} = lib.mkIf haveGKleen {
10 inherit (config.users.users."gkleen") hashedPassword shell;
11 openssh.authorizedKeys.keyFiles = config.users.users."gkleen".openssh.authorizedKeys.keyFiles;
12 };
13
14 home-manager.users.${userName} = {
15 programs = {
16 git = {
17 enable = true;
18 userEmail = "gkleen@yggdrasil.li";
19 userName = "Gregor Kleen";
20 delta.enable = true;
21 extraConfig = {
22 pull.rebase = false;
23 };
24 };
25
26 ssh = {
27 enable = true;
28 controlMaster = "auto";
29 controlPersist = "30m";
30 serverAliveInterval = 6;
31 hashKnownHosts = true;
32 extraConfig = ''
33 IdentitiesOnly true
34 ServerAliveCountMax 10
35 '';
36 };
37
38 gpg.enable = true;
39 };
40
41 services = {
42 gpg-agent = {
43 enable = true;
44 enableSshSupport = true;
45 extraConfig = ''
46 pinentry-program ${pkgs.pinentry-curses}/bin/pinentry
47 grab
48 '';
49 };
50 };
51 };
52}