summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--accounts/gkleen@installer.nix1
-rw-r--r--accounts/gkleen@sif/alacritty.nix43
-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.nix236
-rw-r--r--accounts/gkleen@sif/dunst-settings.nix67
-rw-r--r--accounts/gkleen@sif/emacs.el178
-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.nix237
-rw-r--r--accounts/gkleen@sif/store.kdbx.lftp6
-rw-r--r--accounts/gkleen@sif/systemd.nix80
-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.hs246
-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.hs895
-rw-r--r--accounts/gkleen@sif/xresources.nix46
-rw-r--r--accounts/gkleen@sif/zshrc410
-rw-r--r--accounts/gkleen@surtr.nix1
-rw-r--r--accounts/mherold@surtr.nix1
-rw-r--r--accounts/mkleen@surtr.nix1
-rw-r--r--accounts/mwagner@surtr.nix1
-rw-r--r--accounts/root@sif.nix18
-rw-r--r--accounts/some@surtr.nix1
-rw-r--r--accounts/tkleen@surtr.nix1
-rw-r--r--accounts/vkleen@surtr.nix1
-rw-r--r--flake.lock18
-rw-r--r--hosts/sif/default.nix355
-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--hosts/surtr/default.nix126
-rw-r--r--hosts/surtr/dns/default.nix92
-rw-r--r--hosts/surtr/dns/zones/email.nights.soa38
-rw-r--r--hosts/surtr/dns/zones/li.141.soa50
-rw-r--r--hosts/surtr/dns/zones/li.kleen.soa40
-rw-r--r--hosts/surtr/dns/zones/li.xmpp.soa40
-rw-r--r--hosts/surtr/dns/zones/li.yggdrasil.soa58
-rw-r--r--hosts/surtr/dns/zones/org.dirty-haskell.soa32
-rw-r--r--hosts/surtr/dns/zones/org.praseodym.soa45
-rw-r--r--hosts/surtr/dns/zones/org.rheperire.soa25
-rw-r--r--hosts/surtr/tls.nix70
-rw-r--r--hosts/surtr/zfs.nix101
-rw-r--r--installer.nix18
-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/knot.nix126
-rw-r--r--modules/luksroot.nix1075
-rw-r--r--modules/networkd.nix297
-rw-r--r--modules/tinc-networkmanager.nix36
-rw-r--r--modules/uucp.nix391
-rw-r--r--modules/yggdrasil/default.nix50
-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/clevis.nix37
-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/core.nix2
-rw-r--r--system-profiles/default-locale.nix7
-rw-r--r--system-profiles/initrd-all-crypto-modules.nix17
-rw-r--r--system-profiles/openssh/default.nix41
-rw-r--r--system-profiles/openssh/host-keys/sif.yaml34
-rw-r--r--system-profiles/openssh/host-keys/surtr.yaml37
-rw-r--r--system-profiles/openssh/known-hosts/sif.nix16
-rw-r--r--system-profiles/openssh/known-hosts/surtr.nix28
-rw-r--r--system-profiles/openssh/known-hosts/ymir.nix16
-rw-r--r--system-profiles/qemu-guest.nix10
-rw-r--r--system-profiles/rebuild-machines/default.nix111
-rw-r--r--system-profiles/rebuild-machines/rebuild-machine.zsh10
-rw-r--r--system-profiles/rebuild-machines/ssh-pub/git.yggdrasil.li-ed25519.pub1
-rw-r--r--system-profiles/rebuild-machines/ssh-pub/git.yggdrasil.li-rsa.pub1
-rw-r--r--system-profiles/rebuild-machines/ssh/sif/private26
-rw-r--r--system-profiles/rebuild-machines/ssh/sif/public1
-rw-r--r--system-profiles/rebuild-machines/ssh/surtr/private26
-rw-r--r--system-profiles/rebuild-machines/ssh/surtr/public1
-rw-r--r--system-profiles/sudo.nix39
-rw-r--r--user-profiles/core.nix1
-rw-r--r--user-profiles/direnv.nix9
-rw-r--r--user-profiles/mpv/default.nix85
-rw-r--r--user-profiles/tmux/default.nix26
-rw-r--r--user-profiles/tmux/tmux.conf25
-rw-r--r--user-profiles/utils.nix25
-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.nix46
-rw-r--r--users/mherold.nix9
-rw-r--r--users/mkleen.nix9
-rw-r--r--users/mwagner.nix9
-rw-r--r--users/root.nix52
-rw-r--r--users/some.nix9
-rw-r--r--users/tkleen.nix9
-rw-r--r--users/vkleen.nix9
126 files changed, 9962 insertions, 11 deletions
diff --git a/accounts/gkleen@installer.nix b/accounts/gkleen@installer.nix
new file mode 100644
index 00000000..64629674
--- /dev/null
+++ b/accounts/gkleen@installer.nix
@@ -0,0 +1 @@
{...}: {}
diff --git a/accounts/gkleen@sif/alacritty.nix b/accounts/gkleen@sif/alacritty.nix
new file mode 100644
index 00000000..3d69d292
--- /dev/null
+++ b/accounts/gkleen@sif/alacritty.nix
@@ -0,0 +1,43 @@
1{
2 font.size = 5.5;
3
4 window.dynamic_padding = true;
5
6 colors = {
7 primary.foreground = "#d9d9d9";
8 primary.background = "#000000";
9 cursor.text = "#000000";
10 cursor.cursor = "#d9d9d9";
11
12 normal = {
13 black = "#000000";
14 red = "#bf4949";
15 green = "#9fb346";
16 yellow = "#e69650";
17 blue = "#759fbf";
18 magenta = "#9b79a6";
19 cyan = "#79a69b";
20 white = "#d9d9d9";
21 };
22
23 bright = {
24 black = "#757a80";
25 red = "#e66e6e";
26 green = "#cbd676";
27 yellow = "#ffa74f";
28 blue = "#98b8d9";
29 magenta = "#ceadd9";
30 cyan = "#a3d9ce";
31 white = "#ffffff";
32 };
33 };
34
35 scrolling.history = 0;
36
37 bell = {
38 duration = 50;
39 color = "#000000";
40 };
41
42 hints.alphabet = "uhetonas";
43}
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..a318a8ee
--- /dev/null
+++ b/accounts/gkleen@sif/default.nix
@@ -0,0 +1,236 @@
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.nixImport { 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 remote.cip.ifi.lmu.de
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 alacritty = {
90 enable = true;
91 settings = import ./alacritty.nix;
92 };
93
94 zathura = {
95 enable = true;
96 package = pkgs.zathura.override { useMupdf = false; };
97 };
98
99 mpv.config = {
100 demuxer-max-bytes = 1073741824;
101 demuxer-max-back-bytes = 268435456;
102 };
103
104 autorandr = {
105 enable = true;
106 hooks.postswitch = {
107 # "restart-compton" = "${pkgs.systemd}/bin/systemctl --user try-restart picom";
108 "restart-trays" = ''
109 ${pkgs.coreutils}/bin/sleep 5
110 ${pkgs.systemd}/bin/systemctl --user try-restart trayer xmobar
111 '';
112 };
113 profiles = customUtils.nixImport { dir = ./autorandr-profiles; };
114 };
115
116 zsh.initExtra = "source ${./zshrc}";
117 zsh.dirHashes = {
118 u2w = "$HOME/projects/uni2work";
119 docs = "$HOME/documents";
120 dl = "$HOME/Downloads";
121 flk = "$HOME/config/nixos-flakes";
122 fsk-timi = "$HOME/projects/21s/fsk-timi";
123 };
124
125 obs-studio = {
126 enable = true;
127 plugins = with pkgs; [obs-v4l2sink];
128 };
129 };
130
131 services = {
132 dunst = {
133 settings = import ./dunst-settings.nix inputs;
134 iconTheme = cfg.gtk.iconTheme;
135 enable = true;
136 };
137 emacs.enable = true;
138 gpg-agent = {
139 enable = true;
140 enableSshSupport = true;
141 extraConfig = ''
142 pinentry-program ${pkgs.pinentry-gtk2}/bin/pinentry
143 grab
144 '';
145 };
146 pasystray.enable = true;
147 udiskie = {
148 enable = true;
149 automount = false;
150 };
151 unclutter = {
152 enable = true;
153 timeout = 5;
154 };
155 network-manager-applet.enable = true;
156 blueman-applet.enable = true;
157
158 sxhkd = {
159 enable = true;
160 keybindings = {
161 "button8" = "${muteScript}/bin/mute unmute";
162 "@button8" = "${muteScript}/bin/mute mute";
163 "button9" = "${pkgs.pulseaudio}/bin/pacmd set-sink-mute @DEFAULT_SINK@ 1";
164 "@button9" = "${pkgs.pulseaudio}/bin/pacmd set-sink-mute @DEFAULT_SINK@ 0";
165 };
166 };
167 };
168
169 gtk = {
170 enable = true;
171 font.name = "DejaVu Sans 6";
172 theme = {
173 package = pkgs.equilux-theme;
174 name = "Equilux-compact";
175 };
176 iconTheme = {
177 package = pkgs.paper-icon-theme;
178 name = "Paper";
179 };
180 };
181
182 xsession = {
183 enable = true;
184
185 windowManager.command = "${xmonad}/bin/xmonad";
186
187 initExtra = let
188 lockScript = pkgs.writeScript "lock" ''
189 #!${pkgs.stdenv.shell}
190 ${pkgs.playerctl}/bin/playerctl -a pause
191 exec ${pkgs.xsecurelock}/bin/xsecurelock
192 '';
193 in ''
194 ${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} &
195 ${pkgs.xorg.xinput}/bin/xinput disable 'Synaptics TM3512-010'
196 ${pkgs.xorg.xset}/bin/xset s 590 10
197 '';
198 };
199
200 xresources.properties = import ./xresources.nix;
201
202 home = {
203 packages = with pkgs; [
204 fira-code powerline-fonts nerdfonts pavucontrol keepassxc
205 youtube-dl sxiv xclip mumble pulseaudio-ctl libnotify synergy
206 xorg.xbacklight screen-message pidgin-with-plugins
207 google-play-music-desktop-player qt5ct playerctl evince
208 thunderbird zulip zoom-us steam steam-run wireshark skype
209 virt-manager rclone cached-nix-shell xournal discord xmonad
210 worktime fira-code-symbols emacsclientDesktopItem libreoffice
211 xournalpp
212 ];
213
214 file = {
215 ".emacs".source = ./emacs.el;
216 ".backup-munin".source = ./backup-patterns;
217 ".mozilla/firefox/default/chrome/userChrome.css".source = ./firefox-chrome.css;
218 ".mozilla/firefox/default/chrome/userContent.css".source = ./firefox-content.css;
219 };
220
221 sessionVariables = {
222 GDK_SCALE = 96.0 / 282.0;
223 QT_AUTO_SCREEN_SCALE_FACTOR = 1;
224 QT_QPA_PLATFORMTHEME = "qt5ct";
225 };
226
227 extraProfileCommands = ''
228 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}}"
229 '';
230 };
231
232 fonts.fontconfig.enable = true;
233
234 systemd.user = import ./systemd.nix inputs;
235 };
236}
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..b22c00f5
--- /dev/null
+++ b/accounts/gkleen@sif/emacs.el
@@ -0,0 +1,178 @@
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 '(("u2w-dev1" . "/ssh:uni2work-dev1:/home/gkleen/projects/uni2work")))
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 tramp-default-method "ssh")
132(customize-set-variable 'tramp-use-ssh-controlmaster-options nil)
133
134(setq direnv-enabled-hosts '("uni2work-dev1"))
135
136(defun tramp-sh-handle-start-file-process@my-direnv (args)
137 "Enable Direnv for hosts in `direnv-enabled-hosts'."
138 (with-parsed-tramp-file-name (expand-file-name default-directory) nil
139 (if (member host direnv-enabled-hosts)
140 (pcase-let ((`(,name ,buffer ,program . ,args) args))
141 `(,name
142 ,buffer
143 "direnv"
144 "exec"
145 ,localname
146 ,program
147 ,@args))
148 args)))
149
150(with-eval-after-load "tramp-sh"
151 (advice-add 'tramp-sh-handle-start-file-process
152 :filter-args #'tramp-sh-handle-start-file-process@my-direnv))
153
154(setq mail-host-address "sif.midgard.yggdrasil")
155(setq user-full-name "Gregor Kleen")
156
157(defun tell-emacsclients-for-buffer-to-die ()
158 "Sends error exit command to every client for the current buffer."
159 (interactive)
160 (dolist (proc server-buffer-clients)
161 (server-send-string proc "-error die")))
162
163(defun kill-buffer-with-special-emacsclient-handling ()
164 "Wrapper around kill-buffer that ensures tell-emacsclients-for-buffer-to-die is on the hooks"
165 (interactive)
166 (add-hook 'kill-buffer-hook 'tell-emacsclients-for-buffer-to-die nil t)
167 (kill-buffer))
168
169;; (global-set-key (kbd "C-x k") 'kill-buffer)
170
171(defun install-emacsclient-wrapped-kill-buffer ()
172 "Installs wrapped kill-buffer with special emacsclient handling.
173Best not to install it unconditionally because the server is not
174necessarily running."
175 (interactive)
176 (global-set-key (kbd "C-x k") 'kill-buffer-with-special-emacsclient-handling))
177
178(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..1b9fb842
--- /dev/null
+++ b/accounts/gkleen@sif/ssh-hosts.nix
@@ -0,0 +1,237 @@
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 "surtr" =
31 { hostname = "surtr.yggdrasil.li";
32 identityFile = "~/.ssh/gkleen@sif.midgard.yggdrasil";
33 };
34 "odin" =
35 { hostname = "odin.asgard.yggdrasil";
36 identityFile = "~/.ssh/gkleen@sif.midgard.yggdrasil";
37 };
38 "init.odin" =
39 { hostname = "odin.asgard.yggdrasil";
40 user = "root";
41 identityFile = "~/.ssh/rsa.gkleen@hel.midgard.yggdrasil";
42 extraOptions = {
43 StrictHostKeyChecking = "off";
44 };
45 };
46 "heimdallr" =
47 { hostname = "heimdallr.asgard.yggdrasil";
48 user = "root";
49 identityFile = "~/.ssh/gkleen@sif.midgard.yggdrasil";
50 };
51 "gitlab2.rz.ifi.lmu.de" =
52 { user = "git";
53 identityFile = "~/.ssh/gkleen@gitlab2.rz.ifi.lmu.de";
54 };
55 "gitlab2.cip.ifi.lmu.de" =
56 { user = "git";
57 identityFile = "~/.ssh/gkleen@sif.midgard.yggdrasil";
58 };
59 "hel".hostname = "hel.midgard.yggdrasil";
60 "blackbeard" =
61 { hostname = "blackbeard.tcs.ifi.lmu.de";
62 user = "pi";
63 identityFile = "~/.ssh/blackbeard";
64 };
65 "github.com" =
66 { user = "git";
67 identityFile = "~/.ssh/gkleen@github.com";
68 };
69 "ullr.playat.ch" =
70 { hostname = "ullr.playat.ch";
71 user = "minecraft";
72 identityFile = "~/.ssh/minecraft@ullr.playat.ch";
73 };
74 "ullr" =
75 { hostname = "185.170.112.70";
76 identityFile = "~/.ssh/gkleen@sif.midgard.yggdrasil";
77 };
78 "testworx" =
79 { hostname = "testworx.tcs.ifi.lmu.de";
80 user = "root";
81 port = 30363;
82 identityFile = "~/.ssh/testworx";
83 };
84 "remote.cip.ifi.lmu.de" =
85 { user = "kleen";
86 identityFile = "~/.ssh/gkleen@sif.midgard.yggdrasil";
87 };
88 "uniworx3" =
89 { hostname = "uniworx3.ifi.lmu.de";
90 user = "root";
91 identityFile = "~/.ssh/uni2work";
92 };
93 "uniworx4" =
94 { hostname = "uniworx4.ifi.lmu.de";
95 user = "root";
96 identityFile = "~/.ssh/uni2work";
97 };
98 "jump.uniworx4" =
99 { hostname = "uniworx4.ifi.lmu.de";
100 user = "sshjump";
101 identityFile = "~/.ssh/sshjump.uni2work";
102 };
103 "uni2workgw" =
104 { hostname = "uni2workgw.ifi.lmu.de";
105 user = "root";
106 identityFile = "~/.ssh/uni2work";
107 };
108 "uniworxdb2" =
109 { hostname = "uniworxdb2";
110 proxyJump = "uniworx4";
111 user = "root";
112 identityFile = "~/.ssh/uni2work";
113 };
114 "uniworx5" =
115 { hostname = "uniworx5.ifi.lmu.de";
116 user = "root";
117 identityFile = "~/.ssh/uni2work";
118 };
119 "gate2" =
120 { hostname = "gate2.tcs.ifi.lmu.de";
121 user = "gkleen";
122 identityFile = "~/.ssh/tcs";
123 serverAliveInterval = 0;
124 };
125 "proxy.gate2" =
126 { hostname = "gate2.tcs.ifi.lmu.de";
127 user = "gkleen";
128 identityFile = "~/.ssh/proxy.gkleen@tcs.ifi.lmu.de";
129 dynamicForwards = [ { port = 8118; } ];
130 serverAliveInterval = 0;
131 extraOptions = {
132 ExitOnForwardFailure = "yes";
133 };
134 };
135 "jump.gate2" =
136 { hostname = "gate2.tcs.ifi.lmu.de";
137 user = "gkleen";
138 identityFile = "~/.ssh/proxy.gkleen@tcs.ifi.lmu.de";
139 serverAliveInterval = 0;
140 extraOptions = {
141 ExitOnForwardFailure = "yes";
142 };
143 };
144 "gate" =
145 { hostname = "gate.tcs.ifi.lmu.de";
146 user = "gkleen";
147 identityFile = "~/.ssh/tcs";
148 };
149 "proxy.gate" =
150 { hostname = "gate.tcs.ifi.lmu.de";
151 user = "gkleen";
152 identityFile = "~/.ssh/proxy.gkleen@tcs.ifi.lmu.de";
153 dynamicForwards = [ { port = 8118; } ];
154 extraOptions = {
155 ExitOnForwardFailure = "yes";
156 };
157 };
158 "jump.gate" =
159 { hostname = "gate.tcs.ifi.lmu.de";
160 user = "gkleen";
161 identityFile = "~/.ssh/proxy.gkleen@tcs.ifi.lmu.de";
162 extraOptions = {
163 ExitOnForwardFailure = "yes";
164 };
165 };
166 "oregon" =
167 { hostname = "oregon.tcs.ifi.lmu.de";
168 user = "root";
169 identityFile = "~/.ssh/tcs";
170 };
171 "proxy.oregon" =
172 { hostname = "oregon.tcs.ifi.lmu.de";
173 user = "root";
174 identityFile = "~/.ssh/tcs";
175 dynamicForwards = [ { port = 8113; } ];
176 extraOptions = {
177 ExitOnForwardFailure = "yes";
178 };
179 };
180 "witbank" =
181 { hostname = "witbank.tcs.ifi.lmu.de";
182 user = "uni2work";
183 identityFile = "~/.ssh/letz";
184 };
185 "git.odin" =
186 { hostname = "odin.asgard.yggdrasil";
187 user = "gitolite";
188 };
189 "notmuch.odin" =
190 { hostname = "odin.asgard.yggdrasil";
191 identityFile = "~/.ssh/notmuch.odin.asgard.yggdrasil";
192 };
193 "status.odin" =
194 { hostname = "odin.asgard.yggdrasil";
195 identityFile = "~/.ssh/status.odin.asgard.yggdrasil";
196 extraOptions.ControlPath = "~/.ssh/status-%r@%n:%p";
197 };
198 "moden" =
199 { hostname = "oristano.tcs.ifi.lmu.de";
200 user = "gkleen";
201 port = 30363;
202 identityFile = "~/.ssh/gkleen@oristano.tcs.ifi.lmu.de";
203 };
204 "ubuntu1804" =
205 { hostname = "192.168.122.30";
206 identityFile = "~/.ssh/gkleen@sif.midgard.yggdrasil";
207 forwardAgent = true;
208 };
209 "gitlab.haskell.org" =
210 { hostname = "gitlab.haskell.org";
211 identityFile = "~/.ssh/gkleen@gitlab.haskell.org";
212 };
213 "gitlab.lrz.de" =
214 { hostname = "gitlab.lrz.de";
215 user = "git";
216 identityFile = "~/.ssh/gkleen@gitlab.lrz.de";
217 };
218 "uni2work-dev1" =
219 { hostname = "uni2work-dev1.ifi.lmu.de";
220 user = "gkleen";
221 identityFile = "~/.ssh/uni2work";
222 proxyJump = "jump.uniworx4";
223 localForwards = [
224 { bind = { address = "localhost"; port = 3940; };
225 host = { address = "localhost"; port = 3940; };
226 }
227 { bind = { address = "localhost"; port = 9020; };
228 host = { address = "localhost"; port = 9020; };
229 }
230 ];
231 remoteForwards = [
232 { host = { address = "/run/user/1000/emacs/server"; };
233 bind = { address = "/home/gkleen/.ssh/emacs-server"; };
234 }
235 ];
236 };
237}
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..7faef31a
--- /dev/null
+++ b/accounts/gkleen@sif/systemd.nix
@@ -0,0 +1,80 @@
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 emacs = {
15 Unit = {
16 After = ["graphical-session-pre.target"];
17 };
18 };
19 trayer = {
20 Service = {
21 Type = "simple";
22 WorkingDirectory = "~";
23 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";
24 Restart = "always";
25 };
26 Install = {
27 WantedBy = ["graphical-session.target"];
28 };
29 };
30 xmobar = {
31 Service = {
32 Type = "simple";
33 WorkingDirectory = "~";
34 ExecStart = "${xmobar}/bin/xmobar";
35 Restart = "always";
36 Environment = "PATH=${pkgs.worktime}/bin:${pkgs.openssh}/bin";
37
38 };
39 Install = {
40 WantedBy = ["graphical-session.target"];
41 };
42 };
43 dunst = {
44 Service = {
45 Restart = "always";
46 };
47 Install = {
48 WantedBy = ["graphical-session.target"];
49 };
50 };
51 xiccd = {
52 Service = {
53 Type = "simple";
54 WorkingDirectory = "~";
55 ExecStart = "${pkgs.xiccd}/bin/xiccd";
56 Restart = "always";
57 };
58 };
59 };
60 timers = {
61 sync-keepass = {
62 Timer = {
63 OnActiveSec = "1m";
64 OnUnitActiveSec = "1m";
65 };
66
67 Install = {
68 WantedBy = ["default.target"];
69 };
70 };
71 };
72 targets = {
73 graphical-session = {
74 Unit = {
75 BindsTo = ["default.target"];
76 After = ["basic.target"];
77 };
78 };
79 };
80}
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..729941aa
--- /dev/null
+++ b/accounts/gkleen@sif/xmonad/lib/XMonad/Prompt/MySsh.hs
@@ -0,0 +1,246 @@
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 Nothing c
84 | null $ cCommand c = c { cCommand = "tmux new-session" }
85 | otherwise = c { cCommand = "tmux new-session \"" ++ (cCommand c) ++ "\"" }
86inTmux (Just h) c
87 | null $ cCommand c = c { cCommand = "tmux new-session -As " <> h }
88 | otherwise = c { cCommand = "tmux new-session \"" ++ (cCommand c) ++ "\"" }
89withEnv :: [(String, String)] -> Conn -> Conn
90withEnv envs c = c { cCommand = "env" ++ (concat $ map (\(n, v) -> ' ' : (n ++ "=" ++ v)) envs) ++ " " ++ (cCommand c) }
91
92data Conn = Conn
93 { cUser :: Maybe String
94 , cHost :: String
95 , cPort :: Maybe Int
96 , cCommand :: String
97 } deriving (Eq, Show, Read)
98
99data Ssh = Ssh
100
101instance XPrompt Ssh where
102 showXPrompt Ssh = "SSH to: "
103 commandToComplete _ c = c
104 nextCompletion _ = getNextCompletion
105
106toConn :: String -> Maybe Conn
107toConn = toConn' . parse connParser "(unknown)"
108toConn' :: Either ParseError Conn -> Maybe Conn
109toConn' (Left _) = Nothing
110toConn' (Right a) = Just a
111
112connParser :: Parser Conn
113connParser = do
114 spaces
115 user' <- optionMaybe $ try $ do
116 str <- many1 $ satisfy (\c -> (not $ isSpace c) && (c /= '@'))
117 char '@'
118 return str
119 host' <- many1 $ satisfy (not . isSpace)
120 port' <- optionMaybe $ try $ do
121 space
122 string "-p"
123 spaces
124 int <- many1 digit
125 (space >> return ()) <|> eof
126 return $ (read int :: Int)
127 spaces
128 command' <- many anyChar
129 eof
130 return $ Conn
131 { cHost = host'
132 , cUser = user'
133 , cPort = port'
134 , cCommand = command'
135 }
136
137sshPrompt :: [Override] -> XPConfig -> X ()
138sshPrompt o c = do
139 sc <- io sshComplList
140 mkXPrompt Ssh c (mkComplFunFromList sc) $ ssh o
141
142ssh :: [Override] -> String -> X ()
143ssh overrides str = do
144 let cmd = applyOverrides overrides str
145 liftIO $ putStr "SSH Command: "
146 liftIO $ putStrLn cmd
147 runInTerm "" cmd
148
149applyOverrides :: [Override] -> String -> String
150applyOverrides [] str = "ssh " ++ str
151applyOverrides (o:os) str = case (applyOverride o str) of
152 Just str -> str
153 Nothing -> applyOverrides os str
154
155applyOverride :: Override -> String -> Maybe String
156applyOverride o str = let
157 conn = toConn str
158 in
159 if isNothing conn then Nothing else
160 case (fromJust conn) `matches` o of
161 True -> Just $ (oCommand o) (fromJust conn)
162 False -> Nothing
163
164matches :: Conn -> Override -> Bool
165a `matches` b = and
166 [ justBool (cUser a) (oUser b) (==)
167 , (cHost a) == (oHost b)
168 , justBool (cPort a) (oPort b) (==)
169 ]
170
171justBool :: Eq a => Maybe a -> Maybe a -> (a -> a -> Bool) -> Bool
172justBool Nothing _ _ = True
173justBool _ Nothing _ = True
174justBool (Just a) (Just b) match = a `match` b
175
176sshComplList :: IO [String]
177sshComplList = uniqSort `fmap` liftM2 (++) sshComplListLocal sshComplListGlobal
178
179sshComplListLocal :: IO [String]
180sshComplListLocal = do
181 h <- getEnv "HOME"
182 s1 <- sshComplListFile $ h ++ "/.ssh/known_hosts"
183 s2 <- sshComplListConf $ h ++ "/.ssh/config"
184 return $ s1 ++ s2
185
186sshComplListGlobal :: IO [String]
187sshComplListGlobal = do
188 env <- getEnv "SSH_KNOWN_HOSTS" `E.catch` econst "/nonexistent"
189 fs <- mapM fileExists [ env
190 , "/usr/local/etc/ssh/ssh_known_hosts"
191 , "/usr/local/etc/ssh_known_hosts"
192 , "/etc/ssh/ssh_known_hosts"
193 , "/etc/ssh_known_hosts"
194 ]
195 case catMaybes fs of
196 [] -> return []
197 (f:_) -> sshComplListFile' f
198
199sshComplListFile :: String -> IO [String]
200sshComplListFile kh = do
201 f <- doesFileExist kh
202 if f then sshComplListFile' kh
203 else return []
204
205sshComplListFile' :: String -> IO [String]
206sshComplListFile' kh = do
207 l <- readFile kh
208 return $ map (getWithPort . takeWhile (/= ',') . concat . take 1 . words)
209 $ filter nonComment
210 $ lines l
211
212sshComplListConf :: String -> IO [String]
213sshComplListConf kh = do
214 f <- doesFileExist kh
215 if f then sshComplListConf' kh
216 else return []
217
218sshComplListConf' :: String -> IO [String]
219sshComplListConf' kh = do
220 l <- readFile kh
221 return $ map (!!1)
222 $ filter isHost
223 $ map words
224 $ lines l
225 where
226 isHost ws = take 1 ws == ["Host"] && length ws > 1
227
228fileExists :: String -> IO (Maybe String)
229fileExists kh = do
230 f <- doesFileExist kh
231 if f then return $ Just kh
232 else return Nothing
233
234nonComment :: String -> Bool
235nonComment [] = False
236nonComment ('#':_) = False
237nonComment ('|':_) = False -- hashed, undecodeable
238nonComment _ = True
239
240getWithPort :: String -> String
241getWithPort ('[':str) = host ++ " -p " ++ port
242 where (host,p) = break (==']') str
243 port = case p of
244 ']':':':x -> x
245 _ -> "22"
246getWithPort 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..d21debdf
--- /dev/null
+++ b/accounts/gkleen@sif/xmonad/xmonad.hs
@@ -0,0 +1,895 @@
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" $ stringProperty "WM_WINDOW_ROLE" =? "presentation"
176 , assign "read" $ stringProperty "WM_WINDOW_ROLE" =? "presenter"
177 , assign "mpv" $ className =? "factorio"
178 , assign "web" $ className =? "chromium-browser"
179 , assign "web" $ className =? "Google-chrome"
180 , assign "work" $ (appName =? "Devtools" <&&> className =? "Firefox")
181 , assign "work" $ className =? "Postman"
182 , assign "web" $ className =? "Firefox"
183 , assign "comm" $ (className =? "Emacs" <&&> title =? "Mail")
184 , assign "comm" $ className =? "Zulip"
185 , assign "comm" $ className =? "Discord"
186 , assign "media" $ (className =? "Alacritty" <&&> resource =? "media")
187 , assign "monitor" $ className =? "Grafana"
188 , Just $ (className =? "Alacritty" <&&> resource =? "htop") -?> centerFloat
189 , Just $ (className =? "Scp-dbus-service.py") -?> centerFloat
190 , Just $ (className =? "Alacritty" <&&> resource =? "log") -?> centerFloat
191 , assign "work" $ className =? "Alacritty"
192 , assign' ["work", "uni"] $ (className =? "Emacs" <&&> appName /=? "Edit_with_Emacs_FRAME")
193 , assign' ["work", "uni"] $ className =? "jetbrains-idea-ce"
194 , assign "read" $ className =? "llpp"
195 , assign "read" $ className =? "Evince"
196 , assign "read" $ className =? "Zathura"
197 , assign "read" $ className =? "MuPDF"
198 , assign "read" $ className =? "Xournal"
199 , assign "read" $ appName =? "com-trollworks-gcs-app-GCS"
200 , assign "read" $ appName =? "Tux.py"
201 , assign "read" $ className =? "Gnucash"
202 , assign "comm" $ className =? "Skype"
203 , assign "comm" $ className =? "Daily"
204 , assign "comm" $ className =? "Pidgin"
205 , assign "comm" $ className =? "Slack"
206 , Just $ (resource =? "xvkbd") -?> doRectFloat $ RationalRect (1 % 8) (3 % 8) (6 % 8) (4 % 8)
207 , Just $ (stringProperty "_NET_WM_WINDOW_TYPE" =? "_NET_WM_WINDOW_TYPE_DIALOG") -?> doFloat
208 , Just $ (className =? "Dunst") -?> doFloat
209 , Just $ (className =? "Xmessage") -?> doCenterFloat
210 , Just $ (className =? "Nm-openconnect-auth-dialog") -?> centerFloat
211 , Just $ (className =? "Pinentry") -?> doCenterFloat
212 , Just $ (className =? "pinentry") -?> doCenterFloat
213 , Just $ (appName =? "Edit_with_Emacs_FRAME") -?> centerFloat
214 , Just $ (stringProperty "WM_WINDOW_ROLE" =? "GtkFileChooseDialog") -?> centerFloatSmall
215 , Just $ (className =? "Nvidia-settings") -?> doCenterFloat
216 , Just $ fmap ("Minetest" `isInfixOf`) title -?> doIgnore
217 , Just $ fmap ("Automachef" `isInfixOf`) title -?> doIgnore
218 , assign "call" $ className =? "zoom"
219 ])
220 , hWsp = hWsp
221 , hCoWsp = hCoWsp
222 , hKeysMod = \conf -> Map.union $ (Map.fromList $ join $ map (spawnBindings conf) [ (xK_e, ["emacsclient -c"])
223 , (xK_d, [fromString browser, fromString $ browser ++ " $(xclip -o)", fromString $ "notmuch-links"])
224 , (xK_c, [ inputPrompt xPConfig "dc" ?+ dc ])
225 , (xK_g, ["pidgin"])
226 , (xK_s, ["skype"])
227 -- , (xK_p, [mkPassPrompt "Type password" pwType xPConfig, mkPassPrompt "Show password" pwShow xPConfig, mkPassPrompt "Copy password" pwClip xPConfig])
228 , (xK_w, ["sudo rewacom"])
229 , (xK_y, [ "tmux new-window -dt media /var/media/link.hs $(xclip -o)"
230 , "tmux new-window -dt media /var/media/download.hs $(xclip -o)"
231 , "alacritty --class media -e tmuxp load /var/media"
232 ])
233 , (xK_l, [ "tmux new-window -dt media mpv $(xclip -o)"
234 , "tmux new-window -dt media streamlink --retry-open 10 $(xclip -o)"
235 ])
236 , (xK_m, [ "emacsclient -c -F \"'(title . \\\"Mail\\\")\" -e '(notmuch)'"
237 , "emacsclient -c -F \"'(title . \\\"Mail\\\")\" -e '(notmuch-mua-new-mail)'"
238 , "emacsclient -c -F \"'(title . \\\"Mail\\\")\" -e \"(browse-url-mail \"$(xclip -o)\")\""
239 ])
240 , (xK_Return, ["keynav start,windowzoom", "keynav start"])
241 , (xK_t, [inputPrompt xPConfig "fuzzytime timer" ?+ fuzzytime, fuzzytime "unset", work_fuzzytime])
242 , (xK_a, [inputPrompt xPConfig "adjmix" ?+ adjmix])
243 , (xK_s, [ inputPromptWithCompl xPConfig "start synergy" synergyCompl ?+ synergyStart
244 , inputPromptWithCompl xPConfig "stop synergy" synergyCompl ?+ synergyStop
245 ])
246 , (xK_h, [ "alacritty --class htop -e htop"
247 , "alacritty --class log -e journalctl -xef"
248 ])
249 , (xK_x, [ "autorandr -c"
250 , "autorandr -fl def"
251 ])
252 , (xK_z, [ "zulip -- --force-device-scale-factor=2"
253 ])
254 ])
255 `Map.union`
256 ( Map.fromList [ ((XMonad.modMask conf .|. controlMask, xK_Return), namedScratchpadAction scratchpads "term")
257 , ((XMonad.modMask conf .|. controlMask, xK_a), namedScratchpadAction scratchpads "pavucontrol")
258 , ((XMonad.modMask conf .|. controlMask, xK_w), namedScratchpadAction scratchpads "alarms")
259 , ((XMonad.modMask conf .|. controlMask, xK_b), namedScratchpadAction scratchpads "blueman")
260 , ((XMonad.modMask conf .|. controlMask, xK_p), namedScratchpadAction scratchpads "keepassxc")
261 , ((XMonad.modMask conf .|. controlMask, xK_t), namedScratchpadAction scratchpads "toggl")
262 , ((XMonad.modMask conf .|. controlMask, xK_e), namedScratchpadAction scratchpads "emacs")
263 , ((XMonad.modMask conf .|. controlMask, xK_m), namedScratchpadAction scratchpads "calendar")
264 , ((XMonad.modMask conf .|. controlMask, xK_f), namedScratchpadAction scratchpads "music")
265 , ((XMonad.modMask conf .|. mod1Mask, xK_Up), rotate U)
266 , ((XMonad.modMask conf .|. mod1Mask, xK_Down), rotate D)
267 , ((XMonad.modMask conf .|. mod1Mask, xK_Left), rotate L)
268 , ((XMonad.modMask conf .|. mod1Mask, xK_Right), rotate R)
269 -- , ((XMonad.modMask conf .|. shiftMask, xK_a), startMute "hel")
270 ] )
271 , hKeyUpKeys = \conf -> Map.fromList [ -- ((XMonad.modMask conf .|. shiftMask, xK_a), stopMute "hel")
272 ]
273 , hScreens = hScreens defaultHost
274 , hCmds = return [ ("prev-workspace", prevWS)
275 , ("next-workspace", nextWS)
276 , ("prev-window", rotAllDown)
277 , ("next-window", rotAllUp)
278 , ("banish", banishScreen LowerRight)
279 , ("update-gpg-tty", safeSpawn "gpg-connect-agent" ["UPDATESTARTUPTTY", "/bye"])
280 , ("rescreen", rescreen)
281 , ("repanel", do
282 spawn "nm-applet"
283 spawn "blueman-applet"
284 spawn "pasystray"
285 spawn "kdeconnect-indicator"
286 spawn "dunst -print"
287 spawn "udiskie"
288 spawn "autocutsel -s PRIMARY"
289 spawn "autocutsel -s CLIPBOARD"
290 )
291 , ("pause", mediaMpv $ MpvSetProperty "pause" True)
292 , ("unpause", mediaMpv $ MpvSetProperty "pause" False)
293 , ("exit", io $ exitWith ExitSuccess)
294 ]
295 }
296 where
297 withGdkScale act = void . xfork $ setEnv "GDK_SCALE" "2" >> act
298 workspaceNames = Map.fromList [ (1, "comm")
299 , (2, "web")
300 , (3, "work")
301 , (4, "read")
302 , (5, "monitor")
303 , (6, "uni")
304 , (8, "call")
305 , (9, "media")
306 , (10, "mpv")
307 ]
308 scratchpads = [ NS "term" "alacritty --class scratchpad --title scratchpad -e tmux new-session -AD -s scratch" (resource =? "scratchpad") centerFloat
309 , NS "pavucontrol" "pavucontrol" (resource =? "pavucontrol") centerFloat
310 , NS "alarms" "alarm-clock-applet" (className =? "Alarm-clock-applet" <&&> title =? "Alarms") centerFloat
311 , NS "blueman" "blueman-manager" (className =? ".blueman-manager-wrapped") centerFloat
312 , NS "keepassxc" "keepassxc" (className =? "KeePassXC") centerFloat
313 , NS "toggl" "toggldesktop" (className =? "Toggl Desktop") centerFloat
314 , NS "calendar" "minetime -- --force-device-scale-factor=1.6" (className =? "MineTime") centerFloat
315 , NS "emacs" "emacsclient -c -F \"'(title . \\\"Scratchpad\\\")\"" (className =? "Emacs" <&&> title =? "Scratchpad") centerFloat
316 , NS "music" "google-play-music-desktop-player --force-device-scale-factor=1.6" (className =? "Google Play Music Desktop Player") centerFloat
317 ]
318 centerFloat = customFloating $ RationalRect (1 % 16) (1 % 16) (7 % 8) (7 % 8)
319 centerFloatSmall = customFloating $ RationalRect (1 % 4) (1 % 4) (1 % 2) (1 % 2)
320 hWsp = wspFromMap workspaceNames
321 hCoWsp = coWspFromMap workspaceNames
322 assign wsp test = (\wsp -> test -?> doShift wsp) <$> hCoWsp wsp
323 assign' :: [String] -> Query Bool -> Maybe MaybeManageHook
324 assign' wsps test = do
325 wsIds <- mapM hCoWsp wsps
326 return $ test -?> go wsIds
327 where
328 go :: [WorkspaceId] -> ManageHook
329 go wsps = do
330 visWsps <- liftX $ (\wset -> W.tag . W.workspace <$> W.current wset : W.visible wset) <$> gets windowset
331 case (filter (`elem` visWsps) wsps, wsps) of
332 (wsp : _, _) -> doShift wsp
333 (_, wsp : _) -> doShift wsp
334 ([], []) -> return mempty
335 rotate rot = do
336 safeSpawn "xrandr" ["--output", "eDP-1", "--rotate", xrandrDir]
337 mapM_ rotTouch touchscreens
338 where
339 xrandrDir = case rot of
340 U -> "normal"
341 L -> "left"
342 R -> "right"
343 D -> "inverted"
344 matrix = case rot of
345 U -> [ [ 1, 0, 0]
346 , [ 0, 1, 0]
347 , [ 0, 0, 1]
348 ]
349 L -> [ [ 0, -1, 1]
350 , [ 1, 0, 0]
351 , [ 0, 0, 1]
352 ]
353 R -> [ [ 0, 1, 0]
354 , [-1, 0, 1]
355 , [ 0, 0, 1]
356 ]
357 D -> [ [-1, 0, 1]
358 , [ 0, -1, 1]
359 , [ 0, 0, 1]
360 ]
361 touchscreens = [ "Wacom Co.,Ltd. Pen and multitouch sensor Finger touch"
362 , "Wacom Co.,Ltd. Pen and multitouch sensor Pen stylus"
363 , "Wacom Co.,Ltd. Pen and multitouch sensor Pen eraser"
364 ]
365 rotTouch screen = do
366 safeSpawn "xinput" $ ["set-prop", screen, "Coordinate Transformation Matrix"] ++ map (\n -> show n ++ ",") (concat matrix)
367 safeSpawn "xinput" ["map-to-output", screen, "eDP-1"]
368 withPw f label = io . void . forkProcess $ do
369 uninstallSignalHandlers
370 void $ createSession
371 (dropWhileEnd isSpace -> pw) <- readCreateProcess (proc "pass" ["show", label]) ""
372 void $ f pw
373 pwType :: String -> X ()
374 pwType = withPw $ readCreateProcess (proc "xdotool" ["type", "--clearmodifiers", "--file", "-"])
375 pwClip label = safeSpawn "pass" ["show", "--clip", label]
376 pwShow :: String -> X ()
377 pwShow = withPw $ \pw -> do
378 xmessage <- fromMaybe "xmessage" <$> liftIO (lookupEnv "XMONAD_XMESSAGE")
379 readCreateProcess (proc xmessage ["-file", "-"]) pw
380 fuzzytime str = safeSpawn "fuzzytime" $ "timer" : words str
381 work_fuzzytime = io . void . forkProcess $ do
382 readCreateProcess (proc "worktime" []) "" >>= safeSpawn "fuzzytime" . ("timer" : ) . pure
383 adjmix str = safeSpawn "adjmix" $ words str
384 dc expr = void . xfork $ do
385 result <- readProcess "dc" [] $ expr ++ "f"
386 let
387 (first : rest) = filter (not . null) $ lines result
388 notification = Notify.summary first <> Notify.body (unlines rest) <> Notify.timeout Infinite <> Notify.urgency Normal <> Notify.appName "dc"
389 void $ Notify.display notification
390 synergyCompl = mkComplFunFromList' ["mathw86"]
391 synergyStart host = safeSpawn "systemctl" ["--user", "start", "synergy-rtunnel@" ++ host ++ ".service"]
392 synergyStop host = safeSpawn "systemctl" ["--user", "stop", "synergy-rtunnel@" ++ host ++ ".service"]
393
394hostFromName _ = defaultHost
395
396-- muteRef :: IORef (Maybe (String, Notification))
397-- {-# NOINLINE muteRef #-}
398-- muteRef = unsafePerformIO $ newIORef Nothing
399
400-- startMute, stopMute :: String -> X ()
401-- startMute sink = liftIO $ do
402-- muted <- isJust <$> readIORef muteRef
403-- when (not muted) $ do
404-- let
405-- notification = Notify.summary "Muted" <> Notify.timeout Infinite <> Notify.urgency Normal
406-- level = "0.0dB"
407-- -- level <- runProcessWithInput "ssh" ["bragi", "cat", "/dev/shm/mix/" ++ sink ++ "/level"] ""
408-- -- callProcess "ssh" ["bragi", "adjmix", "-t", sink, "-o", "0"]
409-- hPutStrLn stderr "Mute"
410-- writeIORef muteRef . Just . (level, ) =<< Notify.display notification
411-- stopMute sink = liftIO $ do
412-- let
413-- unmute (Just (level, notification)) = do
414-- hPutStrLn stderr "Unmute"
415-- -- callProcess "ssh" ["bragi", "adjmix", "-t", sink, "-o", level]
416-- Notify.close notification
417-- unmute Nothing = return ()
418-- muted <- isJust <$> readIORef muteRef
419-- when muted . join . atomicModifyIORef muteRef $ (Nothing, ) . unmute
420
421wspFromMap workspaceNames = \i -> case Map.lookup i workspaceNames of
422 Just str -> show i ++ " " ++ str
423 Nothing -> show i
424
425coWspFromMap workspaceNames = \str -> case filter ((== str) . snd) $ Map.toList workspaceNames of
426 [] -> Nothing
427 [(i, _)] -> Just $ wspFromMap workspaceNames i
428 _ -> Nothing
429
430spawnModifiers = [0, controlMask, shiftMask .|. controlMask]
431spawnBindings :: XConfig layout -> (KeySym, [X ()]) -> [((KeyMask, KeySym), X ())]
432spawnBindings conf (k, cmds) = zipWith (\m cmd -> ((modm .|. mod1Mask .|. m, k), cmd)) spawnModifiers cmds
433 where
434 modm = XMonad.modMask conf
435
436manageScratchTerm = (resource =? "scratchpad" <||> resource =? "keysetup") -?> doRectFloat $ RationalRect (1 % 16) (1 % 16) (7 % 8) (7 % 8)
437
438tabbedLayout t = renamed [Replace "Tabbed"] $ reflectHoriz $ t CustomShrink $ tabbedTheme
439tabbedLayoutHoriz t = renamed [Replace "Tabbed Horiz"] $ reflectVert $ t CustomShrink $ tabbedTheme
440tabbedTheme = def
441 { activeColor = "black"
442 , inactiveColor = "black"
443 , urgentColor = "black"
444 , activeBorderColor = "grey"
445 , inactiveBorderColor = "#202020"
446 , urgentBorderColor = "#bb0000"
447 , activeTextColor = "grey"
448 , inactiveTextColor = "grey"
449 , urgentTextColor = "grey"
450 , decoHeight = 32
451 , fontName = "xft:Fira Mono for Powerline:style=Medium:pixelsize=22.5"
452 }
453
454main :: IO ()
455main = do
456 arguments <- either (const []) id <$> tryIOError getArgs
457 case arguments of
458 ["--command", s] -> do
459 d <- openDisplay ""
460 rw <- rootWindow d $ defaultScreen d
461 a <- internAtom d "XMONAD_COMMAND" False
462 m <- internAtom d s False
463 allocaXEvent $ \e -> do
464 setEventType e clientMessage
465 setClientMessageEvent e rw a 32 m currentTime
466 sendEvent d rw False structureNotifyMask e
467 sync d False
468 _ -> do
469 -- batteryMon <- xfork $ monitorBattery Nothing Nothing
470 hostname <- getHostName
471 let
472 host = hostFromName hostname
473 setEnv "HOST" hostname
474 let myConfig = withHostUrgency . ewmh $ docks def
475 { manageHook = hManageHook host
476 , terminal = "alacritty"
477 , layoutHook = smartBorders . avoidStruts $ windowNavigation layout'
478 , logHook = do
479 dynamicLogString xmobarPP' >>= writeProps
480 updatePointer (99 % 100, 98 % 100) (0, 0)
481 , modMask = mod4Mask
482 , keys = \conf -> hKeysMod host conf $ myKeys' conf host
483 , workspaces = take (length numKeys) $ map wsp [1..]
484 , startupHook = setDefaultCursor xC_left_ptr
485 , normalBorderColor = "#202020"
486 , focusedBorderColor = "grey"
487 , handleEventHook = fullscreenEventHook <+> (serverModeEventHookCmd' $ hCmds host) <+> keyUpEventHook
488 }
489 writeProps str = do
490 let encodeCChar = map $ fromIntegral . fromEnum
491 atoms = [ "_XMONAD_WORKSPACES"
492 , "_XMONAD_LAYOUT"
493 , "_XMONAD_TITLE"
494 ]
495 (flip mapM_) (zip atoms (lines str)) $ \(atom', content) -> do
496 ustring <- getAtom "UTF8_STRING"
497 atom <- getAtom atom'
498 withDisplay $ \dpy -> io $ do
499 root <- rootWindow dpy $ defaultScreen dpy
500 changeProperty8 dpy root atom ustring propModeReplace $ encodeCChar content
501 sync dpy True
502 wsp = hWsp host
503 -- We can´t define per-host layout modifiers because we lack dependent types
504 layout' = onHost "skadhi" ( onWorkspace (wsp 1) (Full ||| withIM (1%5) (Title "Buddy List") tabbedLayout') $
505 onWorkspace (wsp 10) Full $
506 onWorkspace (wsp 2) (Full ||| tabbedLayout') $
507 onWorkspace (wsp 5) tabbedLayout' $
508 onWorkspace (wsp 8) (withIM (1%5) (Title "Friends") tabbedLayout') $
509 defaultLayouts
510 ) $
511 onHost "vali" ( onWorkspace (wsp 2) (Full ||| tabbedLayout' ||| combineTwo (TwoPane 0.01 0.57) Full tabbedLayout') $
512 onWorkspace (wsp 3) workLayouts $
513 defaultLayouts
514 ) $
515 onHost "hel" ( onWorkspace (wsp 1) (withIM (1 % 8) (Title "Buddy List") $ trackFloating tabbedLayout') $
516 onWorkspace (wsp 2) (tabbedLayout''' ||| Dwindle R CW 1 (5 % 100)) $
517 onWorkspace (wsp 3) workLayouts $
518 onWorkspace (wsp 6) workLayouts $
519 onWorkspace (wsp 4) (tabbedLayout' ||| tabbedLayoutHoriz' ||| Dwindle R CW 1 (5 % 100)) $
520 onWorkspace (wsp 5) (tabbedLayout''' ||| Dwindle R CW 1 (5 % 100)) $
521 onWorkspace (wsp 10) (tabbedLayout''' ||| combineTwoP (TwoPane (1 % 100) (3 % 4)) tabbedLayout''' tabbedLayout''' (ClassName "mpv") ||| Dwindle R CW 1 (5 % 100)) $
522 defaultLayouts
523 ) $
524 onHost "sif" ( onWorkspace (wsp 1) (withIM (1 % 8) (Title "Buddy List") $ trackFloating tabbedLayout') $
525 onWorkspace (wsp 2) (tabbedLayout''' ||| Dwindle R CW 1 (5 % 100)) $
526 onWorkspace (wsp 3) workLayouts $
527 onWorkspace (wsp 6) workLayouts $
528 onWorkspace (wsp 4) (tabbedLayout' ||| tabbedLayoutHoriz' ||| Dwindle R CW 1 (5 % 100)) $
529 onWorkspace (wsp 5) (tabbedLayout''' ||| Dwindle R CW 1 (5 % 100)) $
530 onWorkspace (wsp 8) tabbedLayout''' $
531 onWorkspace (wsp 10) (tabbedLayout''' ||| combineTwoP (TwoPane (1 % 100) (3 % 4)) tabbedLayout''' tabbedLayout''' (ClassName "mpv") ||| Dwindle R CW 1 (5 % 100)) $
532 defaultLayouts
533 ) $
534 defaultLayouts
535 -- tabbedLayout''' = renamed [Replace "Tabbed'"] $ IfMax 1 (noBorders Full) (tabbedLayout tabbedBottomAlways)
536 tabbedLayout''' = tabbedLayout tabbedBottom
537 tabbedLayout' = tabbedLayout tabbedBottomAlways
538 tabbedLayoutHoriz' = tabbedLayoutHoriz tabbedLeftAlways
539 defaultLayouts = {- spiralWithDir East CW (1 % 2) -} Dwindle R CW 1 (5 % 100) ||| tabbedLayout' ||| Full
540 -- workLayouts = {- spiralWithDir East CW (1 % 2) -} Dwindle R CW (2 % 1) (5 % 100) ||| tabbedLayout' ||| Full
541 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)
542 sqrtTwo = approxRational (sqrt 2) (1 / 2560)
543 xmobarPP' = xmobarPP { ppTitle = shorten 80
544 , ppSort = (liftM2 (.)) getSortByIndex $ return scratchpadFilterOutWorkspace
545 , ppUrgent = wrap "(" ")" . xmobarColor "#800000" ""
546 , ppHiddenNoWindows = xmobarColor "#202020" "" . wrap "(" ")"
547 , ppVisible = wrap "(" ")" . xmobarColor "#808000" ""
548 , ppCurrent = wrap "(" ")" . xmobarColor "#008000" ""
549 , ppHidden = wrap "(" ")"
550 , ppWsSep = " "
551 , ppSep = "\n"
552 }
553 withHostUrgency = case hostname of
554 "hel" -> withUrgencyHookC urgencyHook' $ urgencyConfig { suppressWhen = U.Never, remindWhen = Dont }
555 "sif" -> withUrgencyHookC urgencyHook' $ urgencyConfig { suppressWhen = U.Never, remindWhen = Dont }
556 _ -> id
557 urgencyHook' window = do
558 runQuery ((resource =? "comm" <||> resource =? "Pidgin" <||> className =? "Gajim" <||> className =? "Skype") --> safeSpawn "thinklight" ["Blink", "100"]) window
559 urgencyHook (BorderUrgencyHook { urgencyBorderColor = "#bb0000" }) window
560 shutdown :: SomeException -> IO a
561 shutdown e = do
562 let pids = [ -- batteryMon
563 ]
564 mapM_ (signalProcess sigTERM) pids
565 mapM_ (getProcessStatus False False) pids
566 throw e
567 keyUpEventHook :: Event -> X All
568 keyUpEventHook event = handle event >> return (All True)
569 where
570 handle (KeyEvent { ev_event_type = t, ev_state = m, ev_keycode = code })
571 | t == keyRelease = withDisplay $ \dpy -> do
572 s <- io $ keycodeToKeysym dpy code 0
573 mClean <- cleanMask m
574 ks <- asks $ hKeyUpKeys host . config
575 userCodeDef () $ whenJust (Map.lookup (mClean, s) ks) id
576 | otherwise = return ()
577 handle _ = return ()
578 handle shutdown $ launch myConfig
579
580secs :: Int -> Int
581secs = (* 1000000)
582
583-- monitorBattery :: Maybe BatteryContext -> Maybe Notification -> IO ()
584-- monitorBattery Nothing n = do
585-- ctx <- batteryContextNew
586-- case ctx of
587-- Nothing -> threadDelay (secs 10) >> monitorBattery Nothing n
588-- Just _ -> monitorBattery ctx n
589-- monitorBattery ctx@(Just ctx') n = do
590-- batInfo <- getBatteryInfo ctx'
591-- case batInfo of
592-- Nothing -> threadDelay (secs 1) >> monitorBattery ctx n
593-- Just batInfo -> do
594-- let n'
595-- | batteryState batInfo == BatteryStateDischarging
596-- , timeLeft <= 1200
597-- , timeLeft > 0 = Just $ summary "Discharging" <> hint "value" percentage <> urgency u <> body (duz timeLeft ++ "left")
598-- | otherwise = Nothing
599-- u
600-- | timeLeft <= 600 = Critical
601-- | timeLeft <= 1800 = Normal
602-- | otherwise = Low
603-- timeLeft = batteryTimeToEmpty batInfo
604-- percentage :: Int32
605-- percentage = round $ batteryPercentage batInfo
606-- ts = [("s", 60), ("m", 60), ("h", 24), ("d", 365), ("y", 1)]
607-- duz ms = ss
608-- 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
609-- case n' of
610-- Just n' -> Notify.display (maybe mempty reuse n <> Notify.appName "monitorBattery" <> n') >>= (\n -> threadDelay (secs 2) >> monitorBattery ctx (Just n))
611-- Nothing -> threadDelay (secs 30) >> monitorBattery ctx n
612
613disableTouchpad, disableTrackpoint, enableTrackpoint, enableTouchpad :: X ()
614enableTouchpad = safeSpawn "xinput" ["enable", "SynPS/2 Synaptics TouchPad"]
615disableTouchpad = safeSpawn "xinput" ["disable", "SynPS/2 Synaptics TouchPad"]
616enableTrackpoint = safeSpawn "xinput" ["enable", "TPPS/2 IBM TrackPoint"]
617disableTrackpoint = safeSpawn "xinput" ["disable", "TPPS/2 IBM TrackPoint"]
618
619isDisabled :: String -> X Bool
620isDisabled str = do
621 out <- runProcessWithInput "xinput" ["list", str] ""
622 return $ "disabled" `isInfixOf` out
623
624
625spawnKeychain :: X ()
626spawnKeychain = do
627 home <- liftIO getHomeDirectory
628 let keys = (map ((home </>) . (".ssh/" ++)) ["id", "id-rsa"]) ++ ["6B13AA67"]
629 liftIO (maybe (return ()) (setEnv "SSH_ASKPASS") =<< findAskpass)
630 safeSpawn "keychain" . (["--agents", "gpg,ssh"] ++)=<< liftIO (filterM doesFileExist keys)
631 where
632 findAskpass = filter `liftM` readFile "/etc/zshrc"
633 filter = listToMaybe . catMaybes . map (stripPrefix "export SSH_ASKPASS=") . lines
634
635assimilateKeychain :: X ()
636assimilateKeychain = liftIO $ assimilateKeychain' >> return ()
637assimilateKeychain' = tryIOError $ do
638 -- pid <- getProcessID
639 -- tmpDir <- lookupEnv "TMPDIR"
640 -- let tmpDir' = fromMaybe "/tmp" tmpDir
641 -- tmpFile = tmpDir' </> "xmonad-keychain" ++ (show pid) ++ ".env"
642 env <- runProcessWithInput "sh" ["-c", "eval $(keychain --eval --noask --agents gpg,ssh); env"] "" -- > " ++ tmpFile] ""
643 -- env <- readFile tmpFile
644 let envVars = Map.fromList $ map (\(k, v) -> (k, tail' v)) $ map (span (/= '=')) $ envLines
645 envVars' = Map.filterWithKey (\k _ -> k `elem` transfer) envVars
646 transfer = ["SSH_AUTH_SOCK", "SSH_AGENT_PID", "GPG_AGENT_INFO"]
647 envLines = filter (elem '=') $ lines env :: [String]
648 sequence $ map (\(k, c) -> setEnv k c) $ Map.toList envVars'
649 -- removeFile tmpFile
650 where
651 tail' [] = []
652 tail' (x:xs) = xs
653
654
655numKeys = [xK_parenleft, xK_parenright, xK_braceright, xK_plus, xK_braceleft, xK_bracketright, xK_bracketleft, xK_exclam, xK_equal, xK_asterisk]
656
657instance Shrinker CustomShrink where
658 shrinkIt _ "" = [""]
659 shrinkIt s cs
660 | length cs >= 4 = cs : shrinkIt s ((reverse . drop 4 . reverse $ cs) ++ "...")
661 | otherwise = cs : shrinkIt s (init cs)
662
663xPConfig :: XPConfig
664xPConfig = def
665 { font = "xft:Fira Mono for Powerline:style=Medium:pixelsize=22.5"
666 , height = 32
667 , bgColor = "black"
668 , fgColor = "grey"
669 , fgHLight = "green"
670 , bgHLight = "black"
671 , borderColor = "grey"
672 , searchPredicate = (\needle haystack -> all (`isInfixOf` map toLower haystack) . map (map toLower) $ words needle)
673 , position = Top
674 }
675
676sshOverrides host = map (\h -> mkOverride { oHost = h, oCommand = moshCmd . inTmux host} )
677 [ "odin"
678 , "ymir"
679 , "surtr"
680 ]
681 ++
682 map (\h -> mkOverride { oHost = h, oCommand = moshCmd' "/run/current-system/sw/bin/mosh-server" . withEnv [("TERM", "xterm")] . inTmux host} )
683 [ "bragi", "bragi.asgard.yggdrasil"
684 ]
685 ++
686 map (\h -> mkOverride { oHost = h, oCommand = sshCmd . inTmux host } )
687 [ "uni2work-dev1"
688 ]
689 ++
690 map (\h -> mkOverride { oHost = h, oCommand = sshCmd . withEnv [("TERM", "xterm")] . inTmux host } )
691 [ "remote.cip.ifi.lmu.de"
692 , "uniworx3", "uniworx4", "uniworx5", "uniworxdb2"
693 , "testworx"
694 ]
695
696backlight :: (Rational -> Rational) -> X ()
697backlight f = void . xfork . liftIO $ do
698 [ _device
699 , _class
700 , read . Text.unpack -> currentBright
701 , _currentPercentage
702 , read . Text.unpack -> maximumBright
703 ] <- Text.splitOn "," . Text.pack <$> readProcess "brightnessctl" ["-m"] ""
704 let current = currentBright % maximumBright
705 new' = f current * fromIntegral maximumBright
706 new :: Integer
707 new | floor new' < 0 = 0
708 | ceiling new' > maximumBright = maximumBright
709 | new' >= maximumBright % 2 = ceiling new'
710 | otherwise = floor new'
711 callProcess "brightnessctl" ["-m", "s", show new]
712
713cycleThrough :: [Rational] -> (Rational -> Rational)
714cycleThrough opts current = fromMaybe currentOpt $ listToMaybe next'
715 where currentOpt = minimumBy (comparing $ abs . subtract current) opts
716 (_, _ : next') = break (== currentOpt) opts
717
718cycleKbLayout :: [(String, Maybe String)] -> X ()
719cycleKbLayout [] = return ()
720cycleKbLayout layouts = liftIO $ do
721 next <- (getNext . extract) `liftM` runProcessWithInput "setxkbmap" ["-query"] ""
722 let
723 args = case next of
724 (l, Just v) -> [l, v]
725 (l, Nothing) -> [l]
726 safeSpawn "setxkbmap" args
727 where
728 extract :: String -> Maybe (String, Maybe String)
729 extract str = listToMaybe $ do
730 ["layout:", l] <- str'
731 [(l, Just v) | ["variant:", v] <- str'] ++ pure (l, Nothing)
732 where
733 str' = map words $ lines str
734 getNext :: Maybe (String, Maybe String) -> (String, Maybe String)
735 getNext = maybe (head layouts) getNext'
736 getNext' x = case elemIndex x layouts of
737 Nothing -> getNext Nothing
738 Just i -> layouts !! ((i + 1) `mod` length layouts)
739
740mpvAll' :: MpvCommand -> IO [MpvResponse]
741mpvAll' = mpvAll "/var/media/.mpv-ipc"
742
743mpvOne' :: MpvCommand -> IO (Maybe MpvResponse)
744mpvOne' = mpvOne "/var/media/.mpv-ipc"
745
746mediaMpv :: MpvCommand -> X ()
747mediaMpv cmd = void . xfork $ print =<< mpvAll' cmd
748
749mediaMpvTogglePause :: X ()
750mediaMpvTogglePause = void . xfork $ do
751 paused <- mapM mpvResponse <=< mpvAll' $ MpvGetProperty "pause"
752 if
753 | and paused -> print <=< mpvAll' $ MpvSetProperty "pause" False
754 | otherwise -> print <=< mpvOne' $ MpvSetProperty "pause" True
755
756myKeys' conf host = Map.fromList $
757 -- launch a terminal
758 [ ((modm, xK_Return), spawn $ (XMonad.terminal conf) ++ " -e tmux")
759 , ((modm .|. shiftMask, xK_Return), spawn $ XMonad.terminal conf)
760
761 -- launch dmenu
762 --, ((modm, xK_d ), spawn "exe=`dmenu_path | dmenu` && eval \"exec $exe\"")
763 , ((modm, xK_d ), shellPrompt "Run: " xPConfig)
764 , ((modm .|. shiftMask, xK_d ), prompt "Run in Terminal: " ("alacritty" ++ " -e") xPConfig)
765 , ((modm, xK_at ), sshPrompt (sshOverrides . Just $ hName host) xPConfig)
766
767 -- close focused window
768 , ((modm .|. shiftMask, xK_q ), kill)
769 , ((modm .|. controlMask .|. shiftMask, xK_q ), spawn "xkill")
770
771 -- Rotate through the available layout algorithms
772 , ((modm, xK_space ), sendMessage NextLayout)
773
774 -- Reset the layouts on the current workspace to default
775 , ((modm .|. controlMask, xK_r ), (setLayout $ XMonad.layoutHook conf) >> refresh)
776
777 -- Resize viewed windows to the correct size
778 , ((modm, xK_r ), refresh)
779
780 -- Move focus to the next window
781 , ((modm, xK_t ), windows W.focusDown)
782
783 -- Move focus to the previous window
784 , ((modm, xK_n ), windows W.focusUp )
785
786 -- Move focus to the master window
787 , ((modm, xK_m ), windows W.focusMaster )
788
789 -- Swap the focused window and the master window
790 , ((modm .|. shiftMask, xK_m ), windows W.swapMaster)
791
792 -- Swap the focused window with the next window
793 , ((modm .|. shiftMask, xK_t ), windows W.swapDown )
794
795 -- Swap the focused window with the previous window
796 , ((modm .|. shiftMask, xK_n ), windows W.swapUp )
797
798 -- Swap the focused window with the previous window
799 , ((modm .|. shiftMask .|. controlMask, xK_m), sendMessage SwapWindow)
800
801 , ((modm, xK_Right), sendMessage $ Go R)
802 , ((modm, xK_Left ), sendMessage $ Go L)
803 , ((modm, xK_Up ), sendMessage $ Go U)
804 , ((modm, xK_Down ), sendMessage $ Go D)
805 , ((modm .|. shiftMask , xK_Right), sendMessage $ Move R)
806 , ((modm .|. shiftMask , xK_Left ), sendMessage $ Move L)
807 , ((modm .|. shiftMask , xK_Up ), sendMessage $ Move U)
808 , ((modm .|. shiftMask , xK_Down ), sendMessage $ Move D)
809 -- , ((modm .|. controlMask, xK_Right), withFocused $ keysMoveWindow (10, 0))
810 -- , ((modm .|. controlMask, xK_Left ), withFocused $ keysMoveWindow (-10, 0))
811 -- , ((modm .|. controlMask, xK_Up ), withFocused $ keysMoveWindow (0, -10))
812 -- , ((modm .|. controlMask, xK_Down ), withFocused $ keysMoveWindow (0, 10))
813 -- Shrink the master area
814 , ((modm, xK_h ), sendMessage Shrink)
815
816 -- Expand the master area
817 , ((modm, xK_s ), sendMessage Expand)
818
819 -- Push window back into tiling
820 , ((modm .|. shiftMask, xK_space ), withFocused $ windows . W.sink)
821 , ((modm, xK_BackSpace), focusUrgent)
822 , ((modm .|. shiftMask, xK_BackSpace), clearUrgents)
823
824 -- Increment the number of windows in the master area
825 , ((modm , xK_comma ), sendMessage (IncMasterN 1))
826
827 -- Deincrement the number of windows in the master area
828 , ((modm , xK_period), sendMessage (IncMasterN (-1)))
829
830 , ((0, xF86XK_AudioRaiseVolume), safeSpawn "pulseaudio-ctl" ["up", "2"])
831 , ((0, xF86XK_AudioLowerVolume), safeSpawn "pulseaudio-ctl" ["down", "2"])
832 , ((0, xF86XK_AudioMute), safeSpawn "pulseaudio-ctl" ["mute"])
833 , ((0, xF86XK_AudioPause), mediaMpv $ MpvSetProperty "pause" False)
834 , ((0, {-xF86XK_AudioMicMute-} 269025202), safeSpawn "pulseaudio-ctl" ["mute-input"])
835 , ((0, xF86XK_AudioPlay), mediaMpvTogglePause)
836 , ((modm .|. mod1Mask, xK_space), mediaMpvTogglePause)
837
838 , ((0, xF86XK_MonBrightnessDown), backlight (subtract 5))
839 , ((0, xF86XK_MonBrightnessUp), backlight (+ 5))
840
841 , ((modm , xK_Escape), cycleKbLayout (hKbLayouts host))
842 , ((modm .|. controlMask, xK_Escape), safeSpawn "setxkbmap" $ fst (head $ hKbLayouts host) : maybeToList (snd . head $ hKbLayouts host))
843
844 -- Toggle the status bar gap
845 -- Use this binding with avoidStruts from Hooks.ManageDocks.
846 -- See also the statusBar function from Hooks.DynamicLog.
847 --
848 , ((modm , xK_b ), sendMessage ToggleStruts)
849
850 , ((modm .|. shiftMask, xK_p ), safeSpawn "playerctl" ["-a", "pause"])
851
852 -- Quit xmonad
853 , ((modm .|. shiftMask, xK_e ), io (exitWith ExitSuccess))
854
855 -- Restart xmonad
856 -- , ((modm .|. shiftMask .|. controlMask, xK_r ), void . xfork $ recompile False >>= flip when (safeSpawn "xmonad" ["--restart"]))
857 , ((modm .|. shiftMask, xK_r ), void . liftIO $ executeFile "xmonad" True [] Nothing)
858 , ((modm .|. shiftMask, xK_l ), void . xfork $ do
859 sessId <- getEnv "XDG_SESSION_ID"
860 safeSpawn "loginctl" ["lock-session", sessId]
861 )
862 , ((modm .|. shiftMask, xK_s ), safeSpawn "systemctl" ["suspend"])
863 , ((modm .|. shiftMask, xK_h ), safeSpawn "systemctl" ["hibernate"])
864 , ((modm .|. shiftMask, xK_b ), backlight $ cycleThrough [1, 3 % 4, 1 % 2, 1 % 4, 1 % 10, 1 % 100, 0]
865 )
866 , ((modm .|. shiftMask .|. controlMask, xK_b), backlight $ cycleThrough [0, 1 % 100, 1 % 10, 1 % 4, 1 % 2, 3 % 4, 1]
867 )
868 , ((modm, xK_v ), windows copyToAll) -- @@ Make focused window always visible
869 , ((modm .|. shiftMask, xK_v ), killAllOtherCopies) -- @@ Toggle window state back
870 , ((modm .|. shiftMask, xK_g ), windowPrompt xPConfig Goto wsWindows)
871 , ((modm .|. shiftMask .|. controlMask, xK_g ), windowPrompt xPConfig Bring allWindows)
872 ]
873 ++
874
875 --
876 -- mod-[1..9], Switch to workspace N
877 --
878 -- mod-[1..9], Switch to workspace N
879 -- mod-shift-[1..9], Move client to workspace N
880 --
881 [((m .|. modm, k), windows $ f i)
882 | (i, k) <- zip (XMonad.workspaces conf) $ numKeys
883 , (f, m) <- [(W.greedyView, 0), (W.shift, shiftMask)]
884 ]
885 ++
886 [((m .|. modm .|. controlMask, k), void . runMaybeT $
887 MaybeT (P.getScreen def i) >>= MaybeT . screenWorkspace >>= lift . windows . f
888 )
889 | (i, k) <- zip (hScreens host) [xK_g, xK_c, xK_r, xK_l]
890 , (f, m) <- [(W.view, 0), (W.shift, shiftMask)]
891 ]
892 where
893 modm = XMonad.modMask conf
894
895
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/gkleen@surtr.nix b/accounts/gkleen@surtr.nix
new file mode 100644
index 00000000..64629674
--- /dev/null
+++ b/accounts/gkleen@surtr.nix
@@ -0,0 +1 @@
{...}: {}
diff --git a/accounts/mherold@surtr.nix b/accounts/mherold@surtr.nix
new file mode 100644
index 00000000..64629674
--- /dev/null
+++ b/accounts/mherold@surtr.nix
@@ -0,0 +1 @@
{...}: {}
diff --git a/accounts/mkleen@surtr.nix b/accounts/mkleen@surtr.nix
new file mode 100644
index 00000000..64629674
--- /dev/null
+++ b/accounts/mkleen@surtr.nix
@@ -0,0 +1 @@
{...}: {}
diff --git a/accounts/mwagner@surtr.nix b/accounts/mwagner@surtr.nix
new file mode 100644
index 00000000..64629674
--- /dev/null
+++ b/accounts/mwagner@surtr.nix
@@ -0,0 +1 @@
{...}: {}
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/accounts/some@surtr.nix b/accounts/some@surtr.nix
new file mode 100644
index 00000000..64629674
--- /dev/null
+++ b/accounts/some@surtr.nix
@@ -0,0 +1 @@
{...}: {}
diff --git a/accounts/tkleen@surtr.nix b/accounts/tkleen@surtr.nix
new file mode 100644
index 00000000..64629674
--- /dev/null
+++ b/accounts/tkleen@surtr.nix
@@ -0,0 +1 @@
{...}: {}
diff --git a/accounts/vkleen@surtr.nix b/accounts/vkleen@surtr.nix
new file mode 100644
index 00000000..64629674
--- /dev/null
+++ b/accounts/vkleen@surtr.nix
@@ -0,0 +1 @@
{...}: {}
diff --git a/flake.lock b/flake.lock
index 2a0a02da..0bbb15e0 100644
--- a/flake.lock
+++ b/flake.lock
@@ -7,11 +7,11 @@
7 ] 7 ]
8 }, 8 },
9 "locked": { 9 "locked": {
10 "lastModified": 1618469593, 10 "lastModified": 1622938142,
11 "narHash": "sha256-fNdt+Q3irnT3pId7PKSSVeR8/9inBrAEg4gpItoRowU=", 11 "narHash": "sha256-eNA2HPZI/iO4MCi/FCs+nRuFbpuMplM93Aj6YA2XCyY=",
12 "owner": "nix-community", 12 "owner": "nix-community",
13 "repo": "home-manager", 13 "repo": "home-manager",
14 "rev": "ebbbd4f2b50703409543941e7445138dc1e7392e", 14 "rev": "7591c8041d290d4bb99679e9fed2d8061a8f0435",
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": 1622984109,
27 "narHash": "sha256-fjiabnBl20D/jc3bU3K16jP21bf5l2fG1rHij0LoHVk=", 27 "narHash": "sha256-geVjAIToERcsjmHQo2tdD0UaLNk+k68nI5XCRmE3tHM=",
28 "owner": "NixOS", 28 "owner": "NixOS",
29 "repo": "nixpkgs", 29 "repo": "nixpkgs",
30 "rev": "c27aea5e879748c79acf3b6681194fcf184e3eba", 30 "rev": "690496c4e545e68482b5c162a03f0a4f97d35373",
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": 1622915462,
55 "narHash": "sha256-Hq/lcu48RhE3U6gYYkuT7v6hLHeu1PrasiaBiGkCX+w=", 55 "narHash": "sha256-Hr/DVKUnQt3BTR3o4vzux1Ed1mciKZOrCRWuwORzt4Y=",
56 "owner": "Mic92", 56 "owner": "Mic92",
57 "repo": "sops-nix", 57 "repo": "sops-nix",
58 "rev": "ade2f5c17184df9d4c42d36da18a6ed903b4c02d", 58 "rev": "7918c59b392f23665c0b726d4c640d14be4b0b8b",
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..af845bc1
--- /dev/null
+++ b/hosts/sif/default.nix
@@ -0,0 +1,355 @@
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 rebuild-machines
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 = lib.mkForce "dnsmasq";
66 extraConfig = ''
67 [connectivity]
68 uri=https://online.yggdrasil.li
69 '';
70 };
71
72 wlanInterfaces = {
73 wlan0 = {
74 device = "wlp82s0";
75 };
76 };
77
78 bonds = {
79 "lan" = {
80 interfaces = [ "wlan0" "enp0s31f6" "dock0" ];
81 driverOptions = {
82 miimon = "1000";
83 mode = "active-backup";
84 primary_reselect = "always";
85 };
86 };
87 };
88
89 dhcpcd.enable = false;
90 useDHCP = false;
91 useNetworkd = true;
92
93 interfaces.yggdrasil = {
94 virtual = true;
95 virtualType = config.services.tinc.networks.yggdrasil.interfaceType;
96 macAddress = "5c:93:21:c3:61:39";
97 };
98 };
99
100 systemd.services."NetworkManager-wait-online".enable = false;
101 systemd.services."systemd-networkd-wait-online".enable = false;
102
103 environment.etc."NetworkManager/dnsmasq.d/libvirtd_dnsmasq.conf" = {
104 text = ''
105 server=/sif.libvirt/192.168.122.1
106 '';
107 };
108
109 services.openssh.enable = true;
110
111 powerManagement = {
112 enable = true;
113
114 cpuFreqGovernor = "schedutil";
115 };
116
117 environment.systemPackages = with pkgs; [
118 nvtop brightnessctl config.boot.kernelPackages.v4l2loopback s-tui
119 ];
120
121 services = {
122 tinc.yggdrasil.enable = true;
123
124 uucp = {
125 enable = true;
126 nodeName = "sif";
127 remoteNodes = {
128 "ymir" = {
129 publicKeys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG6KNtsCOl5fsZ4rV7udTulGMphJweLBoKapzerWNoLY root@ymir"];
130 hostnames = ["ymir.yggdrasil.li" "ymir.niflheim.yggdrasil"];
131 };
132 };
133
134 defaultCommands = lib.mkForce [];
135 };
136
137 avahi.enable = true;
138
139 fwupd.enable = true;
140
141 fprintd.enable = true;
142
143 blueman.enable = true;
144
145 colord.enable = true;
146
147 vnstat.enable = true;
148
149 logind = {
150 lidSwitch = "suspend";
151 lidSwitchDocked = "lock";
152 lidSwitchExternalPower = "lock";
153 };
154
155 atd = {
156 enable = true;
157 allowEveryone = true;
158 };
159
160 xserver = {
161 enable = true;
162
163 layout = "us";
164 xkbVariant = "dvp";
165 xkbOptions = "compose:caps";
166
167 displayManager.lightdm = {
168 enable = true;
169 greeters.gtk = {
170 clock-format = "%H:%M %a %b %_d";
171 indicators = ["~host" "~spacer" "~clock" "~session" "~power"];
172 theme = {
173 package = pkgs.equilux-theme;
174 name = "Equilux-compact";
175 };
176 iconTheme = {
177 package = pkgs.paper-icon-theme;
178 name = "Paper";
179 };
180 extraConfig = ''
181 background = #000000
182 user-background = false
183 active-monitor = #cursor
184 hide-user-image = true
185
186 [monitor: DP-2]
187 laptop = true
188 '';
189 };
190 };
191
192 displayManager.setupCommands = ''
193 ${pkgs.xorg.xinput}/bin/xinput disable 'SynPS/2 Synaptics TouchPad'
194 '';
195
196 desktopManager.xterm.enable = true;
197 windowManager.twm.enable = true;
198 displayManager.defaultSession = "xterm+twm";
199
200 wacom.enable = true;
201 libinput.enable = true;
202
203 dpi = 282;
204
205 videoDrivers = [ "nvidia" ];
206
207 screenSection = ''
208 Option "metamodes" "nvidia-auto-select +0+0 { ForceCompositionPipeline = On }"
209 '';
210
211 deviceSection = ''
212 Option "AccelMethod" "SNA"
213 Option "TearFree" "True"
214 '';
215
216 exportConfiguration = true;
217 };
218 };
219
220 users = {
221 users.gkleen.extraGroups = [ "media" ];
222 groups.media = {};
223 };
224
225 hardware = {
226 pulseaudio = {
227 enable = true;
228 package = with pkgs; pulseaudioFull;
229 support32Bit = true;
230 };
231
232 bluetooth = {
233 enable = true;
234 settings = {
235 General = {
236 Enable = "Source,Sink,Media,Socket";
237 };
238 };
239 };
240
241 trackpoint = {
242 enable = true;
243 emulateWheel = true;
244 sensitivity = 255;
245 speed = 255;
246 };
247
248 nvidia = {
249 modesetting.enable = true;
250 prime = {
251 nvidiaBusId = "PCI:1:0:0";
252 intelBusId = "PCI:0:2:0";
253 sync.enable = true;
254 };
255 };
256
257 opengl = {
258 enable = true;
259 driSupport32Bit = true;
260 setLdLibraryPath = true;
261 };
262
263 firmware = [ pkgs.firmwareLinuxNonfree ];
264 };
265
266 sound.enable = true;
267
268 nix = {
269 autoOptimiseStore = true;
270 daemonNiceLevel = 10;
271 daemonIONiceLevel = 3;
272 };
273
274 environment.etc."X11/xorg.conf.d/50-wacom.conf".source = lib.mkForce ./wacom.conf;
275
276 systemd.services."ac-plugged" = {
277 description = "Inhibit handling of lid-switch and sleep";
278
279 path = with pkgs; [ systemd coreutils ];
280
281 script = ''
282 exec systemd-inhibit --what=handle-lid-switch --why="AC is connected" --mode=block sleep infinity
283 '';
284
285 serviceConfig = {
286 Type = "simple";
287 };
288 };
289
290 services.udev.extraRules = with pkgs; lib.mkAfter ''
291 SUBSYSTEM=="power_supply", ENV{POWER_SUPPLY_ONLINE}=="0", RUN+="${systemd}/bin/systemctl --no-block stop ac-plugged.service"
292 SUBSYSTEM=="power_supply", ENV{POWER_SUPPLY_ONLINE}=="1", RUN+="${systemd}/bin/systemctl --no-block start ac-plugged.service"
293 ACTION=="add", SUBSYSTEM=="net", DEVTYPE!="?*", ATTR{address}=="3c:e1:a1:b9:cd:e5", NAME="dock0"
294 '';
295
296 services.borgbackup = {
297 snapshots = "btrfs";
298 prefix = "yggdrasil.midgard.sif.";
299 targets = {
300 "munin" = {
301 repo = "borg.munin:borg";
302 paths = [ "/home/gkleen" ];
303 prune = {
304 "home" =
305 [ "--keep-within" "24H"
306 "--keep-daily" "31"
307 "--keep-monthly" "12"
308 "--keep-yearly" "-1"
309 ];
310 };
311 keyFile = "/run/secrets/borg-repokey--borg_munin__borg";
312 };
313 };
314 };
315 sops.secrets.borg-repokey--borg_munin__borg = {
316 sopsFile = /. + path + "/modules/borgbackup/repokeys/borg_munin__borg.yaml";
317 key = "key";
318 };
319
320 services.btrfs.autoScrub = {
321 enable = true;
322 fileSystems = [ "/" "/home" ];
323 interval = "weekly";
324 };
325
326 systemd.services."nix-daemon".serviceConfig = {
327 MemoryAccounting = true;
328 MemoryHigh = "50%";
329 MemoryMax = "75%";
330 };
331
332 services.journald.extraConfig = ''
333 SystemMaxUse=100M
334 '';
335
336 services.dbus.packages = with pkgs;
337 [ dbus gnome3.dconf
338 ];
339
340 programs = {
341 light.enable = true;
342 wireshark.enable = true;
343 };
344
345 virtualisation.libvirtd = {
346 enable = true;
347 };
348
349 zramSwap.enable = true;
350
351 services.pcscd.enable = true;
352
353 system.stateVersion = "20.03";
354 };
355}
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/hosts/surtr/default.nix b/hosts/surtr/default.nix
new file mode 100644
index 00000000..72ed81ae
--- /dev/null
+++ b/hosts/surtr/default.nix
@@ -0,0 +1,126 @@
1{ flake, pkgs, lib, ... }:
2{
3 imports = with flake.nixosModules.systemProfiles; [
4 qemu-guest openssh rebuild-machines ./zfs.nix ./dns ./tls.nix
5 ];
6
7 config = {
8 nixpkgs = {
9 system = "x86_64-linux";
10 };
11
12 networking.hostId = "a64cf4d7";
13 environment.etc."machine-id".text = "a64cf4d793ab0a0ed3892ead609fc0bc";
14
15 boot = {
16 loader.grub = {
17 enable = true;
18 version = 2;
19 device = "/dev/vda";
20 };
21
22 kernelPackages = pkgs.linuxPackages_latest;
23
24 tmpOnTmpfs = true;
25
26 supportedFilesystems = [ "zfs" ];
27 zfs = {
28 enableUnstable = true;
29 devNodes = "/dev"; # /dev/vda2 does not show up in /dev/disk/by-id
30 };
31
32 kernelModules = ["ptp_kvm"];
33 };
34
35 fileSystems = {
36 "/" = {
37 fsType = "tmpfs";
38 options = [ "mode=0755" ];
39 };
40
41 "/boot" =
42 { device = "/dev/disk/by-label/boot";
43 fsType = "vfat";
44 };
45 };
46
47 networking = {
48 hostName = "surtr";
49 domain = "muspelheim.yggdrasil";
50 search = [ "muspelheim.yggdrasil" "yggdrasil" ];
51
52 enableIPv6 = true;
53 dhcpcd.enable = false;
54 useDHCP = false;
55 useNetworkd = true;
56 defaultGateway = { address = "202.61.240.1"; };
57 defaultGateway6 = { address = "fe80::1"; };
58 interfaces."ens3" = {
59 ipv4.addresses = [
60 { address = "202.61.241.61"; prefixLength = 22; }
61 ];
62 ipv6.addresses = [
63 { address = "2a03:4000:52:ada::"; prefixLength = 64; }
64 ];
65 };
66
67 firewall = {
68 enable = true;
69 allowPing = true;
70 allowedTCPPorts = [
71 22 # ssh
72 ];
73 allowedUDPPortRanges = [
74 { from = 60000; to = 61000; } # mosh
75 ];
76 };
77 };
78
79 systemd.network.networks."40-ens3".networkConfig = {
80 Domains = lib.mkForce "~.";
81 DNS = [ "46.38.225.230" "46.38.252.230" "2a03:4000:0:1::e1e6" "2a03:4000:8000::fce6" ];
82 };
83
84 services.timesyncd.enable = false;
85 services.chrony = {
86 enable = true;
87 servers = [];
88 extraConfig = ''
89 pool time.cloudflare.com iburst nts
90 pool nts.ntp.se iburst nts
91 server nts.sth1.ntp.se iburst nts
92 server nts.sth2.ntp.se iburst nts
93 server ptbtime1.ptb.de iburst nts
94 server ptbtime2.ptb.de iburst nts
95 server ptbtime3.ptb.de iburst nts
96
97 refclock PHC /dev/ptp_kvm poll 2 dpoll -2 offset 0 stratum 3
98
99 makestep 0.1 3
100
101 cmdport 0
102 '';
103 };
104
105 services.openssh = {
106 enable = true;
107 passwordAuthentication = false;
108 challengeResponseAuthentication = false;
109 extraConfig = ''
110 AllowGroups ssh
111 '';
112 };
113 users.groups."ssh" = {
114 members = ["root"];
115 };
116
117 security.sudo.extraConfig = ''
118 Defaults lecture = never
119 '';
120
121 nix.gc = {
122 automatic = true;
123 options = "--delete-older-than 30d";
124 };
125 };
126}
diff --git a/hosts/surtr/dns/default.nix b/hosts/surtr/dns/default.nix
new file mode 100644
index 00000000..ce909b72
--- /dev/null
+++ b/hosts/surtr/dns/default.nix
@@ -0,0 +1,92 @@
1{...}:
2{
3 config = {
4 fileSystems."/var/lib/knot" =
5 { device = "surtr/safe/var-lib-knot";
6 fsType = "zfs";
7 };
8
9 systemd.services.knot.unitConfig.RequiresMountsFor = [ "/var/lib/knot" ];
10
11 networking.firewall = {
12 allowedTCPPorts = [
13 53 # DNS
14 ];
15 allowedUDPPorts = [
16 53 # DNS
17 ];
18 };
19
20 services.knot = {
21 enable = true;
22 extraConfig = ''
23 server:
24 listen: 127.0.0.1@53
25 listen: ::1@53
26 listen: 202.61.241.61@53
27 listen: 2a03:4000:52:ada::@53
28
29 remote:
30 - id: inwx_notify
31 address: 185.181.104.96@53
32
33 acl:
34 - id: inwx_acl
35 address: 185.181.104.96
36 action: transfer
37
38 template:
39 - id: inwx_zone
40 storage: /var/lib/knot
41 zonefile-sync: -1
42 zonefile-load: difference-no-serial
43 serial-policy: dateserial
44 journal-content: all
45 semantic-checks: on
46 dnssec-signing: on
47 notify: [inwx_notify]
48 acl: [inwx_acl]
49
50 policy:
51 - id: rsa
52 algorithm: rsasha256
53 ksk-size: 4096
54 zsk-size: 2048
55 zsk-lifetime: 30d
56
57 zone:
58 - domain: yggdrasil.li
59 template: inwx_zone
60 file: ${./zones/li.yggdrasil.soa}
61
62 - domain: nights.email
63 template: inwx_zone
64 file: ${./zones/email.nights.soa}
65
66 - domain: 141.li
67 template: inwx_zone
68 file: ${./zones/li.141.soa}
69
70 - domain: kleen.li
71 template: inwx_zone
72 file: ${./zones/li.kleen.soa}
73
74 - domain: xmpp.li
75 template: inwx_zone
76 file: ${./zones/li.xmpp.soa}
77
78 - domain: dirty-haskell.org
79 template: inwx_zone
80 file: ${./zones/org.dirty-haskell.soa}
81
82 - domain: praseodym.org
83 template: inwx_zone
84 file: ${./zones/org.praseodym.soa}
85
86 - domain: rheperire.org
87 template: inwx_zone
88 file: ${./zones/org.rheperire.soa}
89 '';
90 };
91 };
92}
diff --git a/hosts/surtr/dns/zones/email.nights.soa b/hosts/surtr/dns/zones/email.nights.soa
new file mode 100644
index 00000000..e0589dd3
--- /dev/null
+++ b/hosts/surtr/dns/zones/email.nights.soa
@@ -0,0 +1,38 @@
1$ORIGIN nights.email.
2$TTL 3600
3@ IN SOA ns.yggdrasil.li. root.yggdrasil.li. (
4 2021053002 ; serial
5 10800 ; refresh
6 3600 ; retry
7 604800 ; expire
8 3600 ; min TTL
9)
10 IN NS ns.yggdrasil.li.
11 IN NS ns.inwx.de.
12 IN NS ns2.inwx.de.
13 IN NS ns3.inwx.eu.
14
15@ IN A 188.68.51.254
16@ IN AAAA 2a03:4000:6:d004::
17@ IN MX 0 ymir.yggdrasil.li.
18@ IN TXT "v=spf1 redirect=yggdrasil.li"
19
20* IN A 188.68.51.254
21* IN AAAA 2a03:4000:6:d004::
22* IN MX 0 ymir.yggdrasil.li.
23* IN TXT "v=spf1 redirect=yggdrasil.li"
24
25_acme-challenge 30 IN TXT ""
26
27ymir._domainkey IN TXT (
28 "v=DKIM1;k=rsa;p=MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq3cCKlk+VPhyAanLZTM0BCzUT/+fmxHioZcFk0uJk1akBYj7BRofR7eVNcLKpm3rwYMQgE+9vJH9p8SV6tws9EcWc8SMCqqGZlREYM7PmLDiTSK/vjCzkygfgFCb0EBNsY2A/fpP4rTeoxrbcBSvMkq97iY5rwyw4wXZVZXLiDaCj23s8POoxTk1ClqUJZJQ5x2"
29 "qzrC0RfN5kLZ9A7Gq2jB09vNxpXHYqABA0bJv88JiZM7hfkp9IafJZ+yCVMaBcJs4DAxnTjNAuFD9gm+qSFVY8+yeXqL6Qjo5PbruhyZRBW8RgRYT8t5n07XRglMGKKGMwOGLanrltcyXqB+GsDZBD36RAAwjFadnxdpDyRv4SgRP7ff2tKRrORYpmpN+mKdqw5j3J/nP6bXV1oAkyh9XQkPEIDi81WT87EZziTElDzVp6A2qFOxqucAovoRk24"
30 "7vlsns1FApFRsp9mja0UZNObyKD1M6tP9Ep7lS76tFGMk+WDvXRJH5LEsyCpu7sSyl1r/O0M4K+KldRCqLlZd7rf8F5P8T0dn1azk05g7F4p0N/y9GNdzXbPZ9u0eZdI7SEdh8ZoOZp7NVZiBFfbWLSS5ZtyA2kbBa4i7GJ/cuAbEKOmqAkeQPiu96TGIcyjkXjS6mTPI+9UmKZYZC+OM8XdJ02y5KRoonCc19ZS8CAwEAAQ=="
31)
32
33_xmpp-client._tcp IN SRV 5 0 5222 ymir.yggdrasil.li.
34_xmpp-server._tcp IN SRV 5 0 5269 ymir.yggdrasil.li.
35
36_submission._tcp IN SRV 5 0 25 ymir.yggdrasil.li.
37_imap._tcp IN SRV 5 0 143 ymir.yggdrasil.li.
38_imaps._tcp IN SRV 5 0 993 ymir.yggdrasil.li.
diff --git a/hosts/surtr/dns/zones/li.141.soa b/hosts/surtr/dns/zones/li.141.soa
new file mode 100644
index 00000000..6f974439
--- /dev/null
+++ b/hosts/surtr/dns/zones/li.141.soa
@@ -0,0 +1,50 @@
1$ORIGIN 141.li.
2$TTL 3600
3@ IN SOA ns.yggdrasil.li. root.yggdrasil.li. (
4 2021053001 ; serial
5 10800 ; refresh
6 3600 ; retry
7 604800 ; expire
8 3600 ; min TTL
9)
10 IN NS ns.yggdrasil.li.
11 IN NS ns.inwx.de.
12 IN NS ns2.inwx.de.
13 IN NS ns3.inwx.eu.
14
15@ IN A 188.68.51.254
16@ IN AAAA 2a03:4000:6:d004::
17@ IN MX 0 ymir.yggdrasil.li.
18@ IN TXT "v=spf1 redirect=yggdrasil.li"
19
20* IN A 188.68.51.254
21* IN AAAA 2a03:4000:6:d004::
22* IN MX 0 ymir.yggdrasil.li.
23* IN TXT "v=spf1 redirect=yggdrasil.li"
24
25surtr IN A 202.61.241.61
26surtr IN AAAA 2a03:4000:52:ada::
27surtr IN MX 0 ymir.yggdrasil.li
28surtr IN TXT "v=spf1 redirect=ullr.yggdrasil.li"
29
30ymir IN A 188.68.51.254
31ymir IN AAAA 2a03:4000:6:d004::
32ymir IN MX 0 ymir.yggdrasil.li
33ymir IN TXT "v=spf1 redirect=ymir.yggdrasil.li"
34
35_acme-challenge 30 IN TXT ""
36
37ymir._domainkey IN TXT (
38 "v=DKIM1;k=rsa;p=MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq3cCKlk+VPhyAanLZTM0BCzUT/+fmxHioZcFk0uJk1akBYj7BRofR7eVNcLKpm3rwYMQgE+9vJH9p8SV6tws9EcWc8SMCqqGZlREYM7PmLDiTSK/vjCzkygfgFCb0EBNsY2A/fpP4rTeoxrbcBSvMkq97iY5rwyw4wXZVZXLiDaCj23s8POoxTk1ClqUJZJQ5x2"
39 "qzrC0RfN5kLZ9A7Gq2jB09vNxpXHYqABA0bJv88JiZM7hfkp9IafJZ+yCVMaBcJs4DAxnTjNAuFD9gm+qSFVY8+yeXqL6Qjo5PbruhyZRBW8RgRYT8t5n07XRglMGKKGMwOGLanrltcyXqB+GsDZBD36RAAwjFadnxdpDyRv4SgRP7ff2tKRrORYpmpN+mKdqw5j3J/nP6bXV1oAkyh9XQkPEIDi81WT87EZziTElDzVp6A2qFOxqucAovoRk24"
40 "7vlsns1FApFRsp9mja0UZNObyKD1M6tP9Ep7lS76tFGMk+WDvXRJH5LEsyCpu7sSyl1r/O0M4K+KldRCqLlZd7rf8F5P8T0dn1azk05g7F4p0N/y9GNdzXbPZ9u0eZdI7SEdh8ZoOZp7NVZiBFfbWLSS5ZtyA2kbBa4i7GJ/cuAbEKOmqAkeQPiu96TGIcyjkXjS6mTPI+9UmKZYZC+OM8XdJ02y5KRoonCc19ZS8CAwEAAQ=="
41)
42
43_xmpp-client._tcp IN SRV 5 0 5222 ymir.yggdrasil.li.
44_xmpp-server._tcp IN SRV 5 0 5269 ymir.yggdrasil.li.
45
46_infinoted._tcp IN SRV 5 0 6523 ymir.yggdrasil.li.
47
48_submission._tcp IN SRV 5 0 25 ymir.yggdrasil.li.
49_imap._tcp IN SRV 5 0 143 ymir.yggdrasil.li.
50_imaps._tcp IN SRV 5 0 993 ymir.yggdrasil.li.
diff --git a/hosts/surtr/dns/zones/li.kleen.soa b/hosts/surtr/dns/zones/li.kleen.soa
new file mode 100644
index 00000000..5a3d2a11
--- /dev/null
+++ b/hosts/surtr/dns/zones/li.kleen.soa
@@ -0,0 +1,40 @@
1$ORIGIN kleen.li.
2$TTL 3600
3@ IN SOA ns.yggdrasil.li. root.yggdrasil.li. (
4 2021053001 ; serial
5 10800 ; refresh
6 3600 ; retry
7 604800 ; expire
8 3600 ; min TTL
9)
10 IN NS ns.yggdrasil.li.
11 IN NS ns.inwx.de.
12 IN NS ns2.inwx.de.
13 IN NS ns3.inwx.eu.
14
15@ IN A 188.68.51.254
16@ IN AAAA 2a03:4000:6:d004::
17@ IN MX 0 ymir.yggdrasil.li.
18@ IN TXT "v=spf1 redirect=yggdrasil.li"
19
20* IN A 188.68.51.254
21* IN AAAA 2a03:4000:6:d004::
22* IN MX 0 ymir.yggdrasil.li.
23* IN TXT "v=spf1 redirect=yggdrasil.li"
24
25_acme-challenge 30 IN TXT ""
26
27ymir._domainkey IN TXT (
28 "v=DKIM1;k=rsa;p=MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq3cCKlk+VPhyAanLZTM0BCzUT/+fmxHioZcFk0uJk1akBYj7BRofR7eVNcLKpm3rwYMQgE+9vJH9p8SV6tws9EcWc8SMCqqGZlREYM7PmLDiTSK/vjCzkygfgFCb0EBNsY2A/fpP4rTeoxrbcBSvMkq97iY5rwyw4wXZVZXLiDaCj23s8POoxTk1ClqUJZJQ5x2"
29 "qzrC0RfN5kLZ9A7Gq2jB09vNxpXHYqABA0bJv88JiZM7hfkp9IafJZ+yCVMaBcJs4DAxnTjNAuFD9gm+qSFVY8+yeXqL6Qjo5PbruhyZRBW8RgRYT8t5n07XRglMGKKGMwOGLanrltcyXqB+GsDZBD36RAAwjFadnxdpDyRv4SgRP7ff2tKRrORYpmpN+mKdqw5j3J/nP6bXV1oAkyh9XQkPEIDi81WT87EZziTElDzVp6A2qFOxqucAovoRk24"
30 "7vlsns1FApFRsp9mja0UZNObyKD1M6tP9Ep7lS76tFGMk+WDvXRJH5LEsyCpu7sSyl1r/O0M4K+KldRCqLlZd7rf8F5P8T0dn1azk05g7F4p0N/y9GNdzXbPZ9u0eZdI7SEdh8ZoOZp7NVZiBFfbWLSS5ZtyA2kbBa4i7GJ/cuAbEKOmqAkeQPiu96TGIcyjkXjS6mTPI+9UmKZYZC+OM8XdJ02y5KRoonCc19ZS8CAwEAAQ=="
31)
32
33_xmpp-client._tcp IN SRV 5 0 5222 ymir.yggdrasil.li.
34_xmpp-server._tcp IN SRV 5 0 5269 ymir.yggdrasil.li.
35
36_infinoted._tcp IN SRV 5 0 6523 ymir.yggdrasil.li.
37
38_submission._tcp IN SRV 5 0 25 ymir.yggdrasil.li.
39_imap._tcp IN SRV 5 0 143 ymir.yggdrasil.li.
40_imaps._tcp IN SRV 5 0 993 ymir.yggdrasil.li.
diff --git a/hosts/surtr/dns/zones/li.xmpp.soa b/hosts/surtr/dns/zones/li.xmpp.soa
new file mode 100644
index 00000000..b123f4a5
--- /dev/null
+++ b/hosts/surtr/dns/zones/li.xmpp.soa
@@ -0,0 +1,40 @@
1$ORIGIN xmpp.li.
2$TTL 3600
3@ IN SOA ns.yggdrasil.li. root.yggdrasil.li. (
4 2021053001 ; serial
5 10800 ; refresh
6 3600 ; retry
7 604800 ; expire
8 3600 ; min TTL
9)
10 IN NS ns.yggdrasil.li.
11 IN NS ns.inwx.de.
12 IN NS ns2.inwx.de.
13 IN NS ns3.inwx.eu.
14
15@ IN A 188.68.51.254
16@ IN AAAA 2a03:4000:6:d004::
17@ IN MX 0 ymir.yggdrasil.li.
18@ IN TXT "v=spf1 redirect=yggdrasil.li"
19
20* IN A 188.68.51.254
21* IN AAAA 2a03:4000:6:d004::
22* IN MX 0 ymir.yggdrasil.li.
23* IN TXT "v=spf1 redirect=yggdrasil.li"
24
25_acme-challenge 30 IN TXT ""
26
27ymir._domainkey IN TXT (
28 "v=DKIM1;k=rsa;p=MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq3cCKlk+VPhyAanLZTM0BCzUT/+fmxHioZcFk0uJk1akBYj7BRofR7eVNcLKpm3rwYMQgE+9vJH9p8SV6tws9EcWc8SMCqqGZlREYM7PmLDiTSK/vjCzkygfgFCb0EBNsY2A/fpP4rTeoxrbcBSvMkq97iY5rwyw4wXZVZXLiDaCj23s8POoxTk1ClqUJZJQ5x2"
29 "qzrC0RfN5kLZ9A7Gq2jB09vNxpXHYqABA0bJv88JiZM7hfkp9IafJZ+yCVMaBcJs4DAxnTjNAuFD9gm+qSFVY8+yeXqL6Qjo5PbruhyZRBW8RgRYT8t5n07XRglMGKKGMwOGLanrltcyXqB+GsDZBD36RAAwjFadnxdpDyRv4SgRP7ff2tKRrORYpmpN+mKdqw5j3J/nP6bXV1oAkyh9XQkPEIDi81WT87EZziTElDzVp6A2qFOxqucAovoRk24"
30 "7vlsns1FApFRsp9mja0UZNObyKD1M6tP9Ep7lS76tFGMk+WDvXRJH5LEsyCpu7sSyl1r/O0M4K+KldRCqLlZd7rf8F5P8T0dn1azk05g7F4p0N/y9GNdzXbPZ9u0eZdI7SEdh8ZoOZp7NVZiBFfbWLSS5ZtyA2kbBa4i7GJ/cuAbEKOmqAkeQPiu96TGIcyjkXjS6mTPI+9UmKZYZC+OM8XdJ02y5KRoonCc19ZS8CAwEAAQ=="
31)
32
33_xmpp-client._tcp IN SRV 5 0 5222 ymir.yggdrasil.li.
34_xmpp-server._tcp IN SRV 5 0 5269 ymir.yggdrasil.li.
35
36_infinoted._tcp IN SRV 5 0 6523 ymir.yggdrasil.li.
37
38_submission._tcp IN SRV 5 0 25 ymir.yggdrasil.li.
39_imap._tcp IN SRV 5 0 143 ymir.yggdrasil.li.
40_imaps._tcp IN SRV 5 0 993 ymir.yggdrasil.li.
diff --git a/hosts/surtr/dns/zones/li.yggdrasil.soa b/hosts/surtr/dns/zones/li.yggdrasil.soa
new file mode 100644
index 00000000..a9b87b76
--- /dev/null
+++ b/hosts/surtr/dns/zones/li.yggdrasil.soa
@@ -0,0 +1,58 @@
1$ORIGIN yggdrasil.li.
2$TTL 3600
3@ IN SOA ns.yggdrasil.li. root.yggdrasil.li. (
4 2021053000 ; serial
5 10800 ; refresh
6 3600 ; retry
7 604800 ; expire
8 3600 ; min TTL
9)
10 IN NS ns.yggdrasil.li.
11 IN NS ns.inwx.de.
12 IN NS ns2.inwx.de.
13 IN NS ns3.inwx.eu.
14
15ns IN A 202.61.241.61
16ns IN AAAA 2a03:4000:52:ada::
17
18@ IN A 188.68.51.254
19@ IN AAAA 2a03:4000:6:d004::
20@ IN MX 0 ymir.yggdrasil.li.
21@ IN TXT "v=spf1 a:mailout.yggdrasil.li -all"
22
23* IN A 188.68.51.254
24* IN AAAA 2a03:4000:6:d004::
25* IN MX 0 ymir.yggdrasil.li.
26* IN TXT "v=spf1 redirect=yggdrasil.li"
27
28ymir IN A 188.68.51.254
29ymir IN AAAA 2a03:4000:6:d004::
30ymir IN MX 0 ymir.yggdrasil.li.
31ymir IN TXT "v=spf1 redirect=yggdrasil.li"
32
33surtr IN A 202.61.241.61
34surtr IN AAAA 2a03:4000:52:ada::
35surtr IN MX 0 ymir.yggdrasil.li
36surtr IN TXT "v=spf1 redirect=ullr.yggdrasil.li"
37
38mailout IN A 188.68.51.254
39mailout IN AAAA 2a03:4000:6:d004::
40mailout IN MX 0 ymir.yggdrasil.li
41mailout IN TXT "v=spf1 redirect=yggdrasil.li"
42
43_acme-challenge 30 IN TXT ""
44
45ymir._domainkey IN TXT (
46 "v=DKIM1;k=rsa;p=MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq3cCKlk+VPhyAanLZTM0BCzUT/+fmxHioZcFk0uJk1akBYj7BRofR7eVNcLKpm3rwYMQgE+9vJH9p8SV6tws9EcWc8SMCqqGZlREYM7PmLDiTSK/vjCzkygfgFCb0EBNsY2A/fpP4rTeoxrbcBSvMkq97iY5rwyw4wXZVZXLiDaCj23s8POoxTk1ClqUJZJQ5x2"
47 "qzrC0RfN5kLZ9A7Gq2jB09vNxpXHYqABA0bJv88JiZM7hfkp9IafJZ+yCVMaBcJs4DAxnTjNAuFD9gm+qSFVY8+yeXqL6Qjo5PbruhyZRBW8RgRYT8t5n07XRglMGKKGMwOGLanrltcyXqB+GsDZBD36RAAwjFadnxdpDyRv4SgRP7ff2tKRrORYpmpN+mKdqw5j3J/nP6bXV1oAkyh9XQkPEIDi81WT87EZziTElDzVp6A2qFOxqucAovoRk24"
48 "7vlsns1FApFRsp9mja0UZNObyKD1M6tP9Ep7lS76tFGMk+WDvXRJH5LEsyCpu7sSyl1r/O0M4K+KldRCqLlZd7rf8F5P8T0dn1azk05g7F4p0N/y9GNdzXbPZ9u0eZdI7SEdh8ZoOZp7NVZiBFfbWLSS5ZtyA2kbBa4i7GJ/cuAbEKOmqAkeQPiu96TGIcyjkXjS6mTPI+9UmKZYZC+OM8XdJ02y5KRoonCc19ZS8CAwEAAQ=="
49)
50
51_xmpp-client._tcp IN SRV 5 0 5222 ymir.yggdrasil.li.
52_xmpp-server._tcp IN SRV 5 0 5269 ymir.yggdrasil.li.
53
54_infinoted._tcp IN SRV 5 0 6523 ymir.yggdrasil.li.
55
56_submission._tcp IN SRV 5 0 25 ymir.yggdrasil.li.
57_imap._tcp IN SRV 5 0 143 ymir.yggdrasil.li.
58_imaps._tcp IN SRV 5 0 993 ymir.yggdrasil.li.
diff --git a/hosts/surtr/dns/zones/org.dirty-haskell.soa b/hosts/surtr/dns/zones/org.dirty-haskell.soa
new file mode 100644
index 00000000..74aed5fd
--- /dev/null
+++ b/hosts/surtr/dns/zones/org.dirty-haskell.soa
@@ -0,0 +1,32 @@
1$ORIGIN dirty-haskell.org.
2$TTL 3600
3@ IN SOA ns.yggdrasil.li. root.yggdrasil.li. (
4 2021053001 ; serial
5 10800 ; refresh
6 3600 ; retry
7 604800 ; expire
8 3600 ; min TTL
9)
10 IN NS ns.yggdrasil.li.
11 IN NS ns.inwx.de.
12 IN NS ns2.inwx.de.
13 IN NS ns3.inwx.eu.
14
15
16@ IN A 188.68.51.254
17@ IN AAAA 2a03:4000:6:d004::
18@ IN MX 10 ymir.yggdrasil.li.
19@ IN TXT "v=spf1 redirect=yggdrasil.li"
20
21* IN A 188.68.51.254
22* IN AAAA 2a03:4000:6:d004::
23* IN MX 0 ymir.yggdrasil.li.
24* IN TXT "v=spf1 redirect=yggdrasil.li"
25
26_acme-challenge 30 IN TXT ""
27
28ymir._domainkey IN TXT (
29 "v=DKIM1;k=rsa;p=MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq3cCKlk+VPhyAanLZTM0BCzUT/+fmxHioZcFk0uJk1akBYj7BRofR7eVNcLKpm3rwYMQgE+9vJH9p8SV6tws9EcWc8SMCqqGZlREYM7PmLDiTSK/vjCzkygfgFCb0EBNsY2A/fpP4rTeoxrbcBSvMkq97iY5rwyw4wXZVZXLiDaCj23s8POoxTk1ClqUJZJQ5x2"
30 "qzrC0RfN5kLZ9A7Gq2jB09vNxpXHYqABA0bJv88JiZM7hfkp9IafJZ+yCVMaBcJs4DAxnTjNAuFD9gm+qSFVY8+yeXqL6Qjo5PbruhyZRBW8RgRYT8t5n07XRglMGKKGMwOGLanrltcyXqB+GsDZBD36RAAwjFadnxdpDyRv4SgRP7ff2tKRrORYpmpN+mKdqw5j3J/nP6bXV1oAkyh9XQkPEIDi81WT87EZziTElDzVp6A2qFOxqucAovoRk24"
31 "7vlsns1FApFRsp9mja0UZNObyKD1M6tP9Ep7lS76tFGMk+WDvXRJH5LEsyCpu7sSyl1r/O0M4K+KldRCqLlZd7rf8F5P8T0dn1azk05g7F4p0N/y9GNdzXbPZ9u0eZdI7SEdh8ZoOZp7NVZiBFfbWLSS5ZtyA2kbBa4i7GJ/cuAbEKOmqAkeQPiu96TGIcyjkXjS6mTPI+9UmKZYZC+OM8XdJ02y5KRoonCc19ZS8CAwEAAQ=="
32)
diff --git a/hosts/surtr/dns/zones/org.praseodym.soa b/hosts/surtr/dns/zones/org.praseodym.soa
new file mode 100644
index 00000000..6f2c676f
--- /dev/null
+++ b/hosts/surtr/dns/zones/org.praseodym.soa
@@ -0,0 +1,45 @@
1$ORIGIN praseodym.org.
2$TTL 3600
3@ IN SOA ns.yggdrasil.li. root.yggdrasil.li. (
4 2021053000 ; serial
5 10800 ; refresh
6 3600 ; retry
7 604800 ; expire
8 3600 ; min TTL
9)
10 IN NS ns.yggdrasil.li.
11 IN NS ns.inwx.de.
12 IN NS ns2.inwx.de.
13 IN NS ns3.inwx.eu.
14
15@ IN A 188.68.51.254
16@ IN AAAA 2a03:4000:6:d004::
17@ IN MX 0 ymir.yggdrasil.li.
18@ IN TXT "v=spf1 redirect=yggdrasil.li"
19
20* IN A 188.68.51.254
21* IN AAAA 2a03:4000:6:d004::
22* IN MX 0 ymir.yggdrasil.li.
23* IN TXT "v=spf1 redirect=yggdrasil.li"
24
25surtr IN A 202.61.241.61
26surtr IN AAAA 2a03:4000:52:ada::
27surtr IN MX 0 ymir.yggdrasil.li
28surtr IN TXT "v=spf1 redirect=ullr.yggdrasil.li"
29
30_acme-challenge 30 IN TXT ""
31
32ymir._domainkey IN TXT (
33 "v=DKIM1;k=rsa;p=MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq3cCKlk+VPhyAanLZTM0BCzUT/+fmxHioZcFk0uJk1akBYj7BRofR7eVNcLKpm3rwYMQgE+9vJH9p8SV6tws9EcWc8SMCqqGZlREYM7PmLDiTSK/vjCzkygfgFCb0EBNsY2A/fpP4rTeoxrbcBSvMkq97iY5rwyw4wXZVZXLiDaCj23s8POoxTk1ClqUJZJQ5x2"
34 "qzrC0RfN5kLZ9A7Gq2jB09vNxpXHYqABA0bJv88JiZM7hfkp9IafJZ+yCVMaBcJs4DAxnTjNAuFD9gm+qSFVY8+yeXqL6Qjo5PbruhyZRBW8RgRYT8t5n07XRglMGKKGMwOGLanrltcyXqB+GsDZBD36RAAwjFadnxdpDyRv4SgRP7ff2tKRrORYpmpN+mKdqw5j3J/nP6bXV1oAkyh9XQkPEIDi81WT87EZziTElDzVp6A2qFOxqucAovoRk24"
35 "7vlsns1FApFRsp9mja0UZNObyKD1M6tP9Ep7lS76tFGMk+WDvXRJH5LEsyCpu7sSyl1r/O0M4K+KldRCqLlZd7rf8F5P8T0dn1azk05g7F4p0N/y9GNdzXbPZ9u0eZdI7SEdh8ZoOZp7NVZiBFfbWLSS5ZtyA2kbBa4i7GJ/cuAbEKOmqAkeQPiu96TGIcyjkXjS6mTPI+9UmKZYZC+OM8XdJ02y5KRoonCc19ZS8CAwEAAQ=="
36)
37
38_xmpp-client._tcp IN SRV 5 0 5222 ymir.yggdrasil.li.
39_xmpp-server._tcp IN SRV 5 0 5269 ymir.yggdrasil.li.
40
41_infinoted._tcp IN SRV 5 0 6523 ymir.yggdrasil.li.
42
43_submission._tcp IN SRV 5 0 25 ymir.yggdrasil.li.
44_imap._tcp IN SRV 5 0 143 ymir.yggdrasil.li.
45_imaps._tcp IN SRV 5 0 993 ymir.yggdrasil.li.
diff --git a/hosts/surtr/dns/zones/org.rheperire.soa b/hosts/surtr/dns/zones/org.rheperire.soa
new file mode 100644
index 00000000..43b1e862
--- /dev/null
+++ b/hosts/surtr/dns/zones/org.rheperire.soa
@@ -0,0 +1,25 @@
1$ORIGIN rheperire.org.
2$TTL 3600
3@ IN SOA ns.yggdrasil.li. root.yggdrasil.li. (
4 2021053010 ; serial
5 10800 ; refresh
6 3600 ; retry
7 604800 ; expire
8 3600 ; min TTL
9)
10 IN NS ns.yggdrasil.li.
11 IN NS ns.inwx.de.
12 IN NS ns2.inwx.de.
13 IN NS ns3.inwx.eu.
14
15@ IN A 188.68.51.254
16@ IN AAAA 2a03:4000:6:d004::
17@ IN MX 0 ymir.yggdrasil.li.
18@ IN TXT "v=spf1 redirect=yggdrasil.li"
19
20* IN A 188.68.51.254
21* IN AAAA 2a03:4000:6:d004::
22* IN MX 0 ymir.yggdrasil.li.
23* IN TXT "v=spf1 redirect=yggdrasil.li"
24
25_acme-challenge 30 IN TXT ""
diff --git a/hosts/surtr/tls.nix b/hosts/surtr/tls.nix
new file mode 100644
index 00000000..9581dd60
--- /dev/null
+++ b/hosts/surtr/tls.nix
@@ -0,0 +1,70 @@
1{ config, pkgs, ... }:
2let
3 knotCfg = config.services.knot;
4
5 knotDNSCredentials = zone: pkgs.writeText "lego-credentials" ''
6 EXEC_PATH=${knotDNSExec zone}/bin/update-dns.sh
7 EXEC_PROPAGATION_TIMEOUT=300
8 EXEC_POLLING_INTERVAL=5
9 '';
10 knotDNSExec = zone: pkgs.writeScriptBin "update-dns.sh" ''
11 #!${pkgs.zsh}/bin/zsh -xe
12
13 mode=$1
14 fqdn=$2
15 challenge=$3
16
17 owner=''${fqdn%".${zone}."}
18
19 commited=
20 function abort() {
21 [[ -n "''${commited}" ]] || ${knotCfg.cliWrappers}/bin/knotc zone-abort "${zone}"
22 }
23
24 ${knotCfg.cliWrappers}/bin/knotc zone-begin "${zone}"
25 trap abort EXIT
26
27 case "''${mode}" in
28 present)
29 ${knotCfg.cliWrappers}/bin/knotc zone-unset ${zone} "''${owner}" TXT '""'
30 ${knotCfg.cliWrappers}/bin/knotc zone-set ${zone} "''${owner}" 30 TXT "''${challenge}"
31 ;;
32 cleanup)
33 ${knotCfg.cliWrappers}/bin/knotc zone-unset ${zone} "''${owner}" TXT "''${challenge}"
34 ${knotCfg.cliWrappers}/bin/knotc zone-set ${zone} "''${owner}" 30 TXT '""'
35 ;;
36 *)
37 exit 2
38 ;;
39 esac
40
41 ${knotCfg.cliWrappers}/bin/knotc zone-commit "${zone}"
42 commited=yes
43 '';
44in {
45 config = {
46 fileSystems."/var/lib/acme" =
47 { device = "surtr/safe/var-lib-acme";
48 fsType = "zfs";
49 };
50
51 security.acme = {
52 server = "https://acme-staging-v02.api.letsencrypt.org/directory";
53
54 acceptTerms = true;
55 preliminarySelfsigned = false;
56 email = "phikeebaogobaegh@141.li";
57 certs = {
58 "rheperire.org" = {
59 domain = "rheperire.org";
60 extraDomainNames = [ "*.rheperire.org" ];
61 dnsProvider = "exec";
62 credentialsFile = knotDNSCredentials "rheperire.org";
63 dnsResolver = "1.1.1.1:53";
64 };
65 };
66 };
67
68 users.groups."knot".members = [ "acme" ];
69 };
70}
diff --git a/hosts/surtr/zfs.nix b/hosts/surtr/zfs.nix
new file mode 100644
index 00000000..3cbd0cf0
--- /dev/null
+++ b/hosts/surtr/zfs.nix
@@ -0,0 +1,101 @@
1{ pkgs, config, ... }:
2let
3 snapshotNames = ["frequent" "hourly" "daily" "monthly" "yearly"];
4 snapshotCount = {
5 frequent = 24;
6 hourly = 24;
7 daily = 30;
8 monthly = 12;
9 yearly = 5;
10 };
11 snapshotTimerConfig = {
12 frequent = { OnCalendar = "*:0/5 UTC"; Persistent = true; };
13 hourly = { OnCalendar = "hourly UTC"; Persistent = true; };
14 daily = { OnCalendar = "daily UTC"; Persistent = true; };
15 monthly = { OnCalendar = "monthly UTC"; Persistent = true; };
16 yearly = { OnCalendar = "yearly UTC"; Persistent = true; };
17 };
18 snapshotDescr = {
19 frequent = "few minutes";
20 hourly = "hour";
21 daily = "day";
22 monthly = "month";
23 yearly = "year";
24 };
25
26 zfs = config.boot.zfs.package;
27
28 autosnapPackage = pkgs.zfstools.override { inherit zfs; };
29in {
30 config = {
31 fileSystems = {
32 "/nix" =
33 { device = "surtr/local/nix";
34 fsType = "zfs";
35 };
36
37 "/root" =
38 { device = "surtr/safe/home-root";
39 fsType = "zfs";
40 neededForBoot = true;
41 };
42
43 "/var/lib/systemd" =
44 { device = "surtr/local/var-lib-systemd";
45 fsType = "zfs";
46 neededForBoot = true;
47 };
48
49 "/var/lib/nixos" =
50 { device = "surtr/local/var-lib-nixos";
51 fsType = "zfs";
52 neededForBoot = true;
53 };
54
55 "/var/log" =
56 { device = "surtr/local/var-log";
57 fsType = "zfs";
58 };
59
60 "/home" =
61 { device = "surtr/safe/home";
62 fsType = "zfs";
63 };
64 };
65
66 systemd.services =
67 let mkSnapService = snapName: {
68 name = "zfs-snapshot-${snapName}";
69 value = {
70 description = "ZFS auto-snapshot every ${snapshotDescr.${snapName}}";
71 after = [ "zfs-import.target" ];
72 serviceConfig = {
73 Type = "oneshot";
74 ExecStart = "${autosnapPackage}/bin/zfs-auto-snapshot -k -p -u ${snapName} ${toString snapshotCount.${snapName}}";
75 };
76 restartIfChanged = false;
77
78 preStart = ''
79 ${zfs}/bin/zfs set com.sun:auto-snapshot=true surtr/safe
80 '';
81 };
82 };
83 in builtins.listToAttrs (map mkSnapService snapshotNames);
84
85 systemd.timers =
86 let mkSnapTimer = snapName: {
87 name = "zfs-snapshot-${snapName}";
88 value = {
89 wantedBy = [ "timers.target" ];
90 timerConfig = snapshotTimerConfig.${snapName};
91 };
92 };
93 in builtins.listToAttrs (map mkSnapTimer snapshotNames);
94
95 services.zfs.trim.enable = false;
96 services.zfs.autoScrub = {
97 enable = true;
98 interval = "Sun *-*-1..7 04:00:00";
99 };
100 };
101}
diff --git a/installer.nix b/installer.nix
index 64629674..3987e8ab 100644
--- a/installer.nix
+++ b/installer.nix
@@ -1 +1,17 @@
1{...}: {} 1{ flake, ... }: {
2 imports = with flake.nixosModules.systemProfiles; [
3 default-locale
4 ];
5
6 config = {
7 networking.firewall = {
8 enable = true;
9 allowedTCPPorts = [ 22 # ssh
10 ];
11 };
12
13 systemd.services."sshd".wantedBy = ["multi-user.target"];
14
15 services.qemuGuest.enable = true;
16 };
17}
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/knot.nix b/modules/knot.nix
new file mode 100644
index 00000000..a4691324
--- /dev/null
+++ b/modules/knot.nix
@@ -0,0 +1,126 @@
1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.knot;
7
8 configFile = pkgs.writeTextFile {
9 name = "knot.conf";
10 text = (concatMapStringsSep "\n" (file: "include: ${file}") cfg.keyFiles) + "\n" +
11 cfg.extraConfig;
12 checkPhase = lib.optionalString (cfg.keyFiles == []) ''
13 ${cfg.package}/bin/knotc --config=$out conf-check
14 '';
15 };
16
17 socketFile = "/run/knot/knot.sock";
18
19 knot-cli-wrappers = pkgs.stdenv.mkDerivation {
20 name = "knot-cli-wrappers";
21 buildInputs = [ pkgs.makeWrapper ];
22 buildCommand = ''
23 mkdir -p $out/bin
24 makeWrapper ${cfg.package}/bin/knotc "$out/bin/knotc" \
25 --add-flags "--config=${configFile}" \
26 --add-flags "--socket=${socketFile}"
27 makeWrapper ${cfg.package}/bin/keymgr "$out/bin/keymgr" \
28 --add-flags "--config=${configFile}"
29 for executable in kdig khost kjournalprint knsec3hash knsupdate kzonecheck
30 do
31 ln -s "${cfg.package}/bin/$executable" "$out/bin/$executable"
32 done
33 mkdir -p "$out/share"
34 ln -s '${cfg.package}/share/man' "$out/share/"
35 '';
36 };
37in {
38 disabledModules = [ "services/networking/knot.nix" ];
39
40 options = {
41 services.knot = {
42 enable = mkEnableOption "Knot authoritative-only DNS server";
43
44 extraArgs = mkOption {
45 type = types.listOf types.str;
46 default = [];
47 description = ''
48 List of additional command line paramters for knotd
49 '';
50 };
51
52 keyFiles = mkOption {
53 type = types.listOf types.path;
54 default = [];
55 description = ''
56 A list of files containing additional configuration
57 to be included using the include directive. This option
58 allows to include configuration like TSIG keys without
59 exposing them to the nix store readable to any process.
60 Note that using this option will also disable configuration
61 checks at build time.
62 '';
63 };
64
65 extraConfig = mkOption {
66 type = types.lines;
67 default = "";
68 description = ''
69 Extra lines to be added verbatim to knot.conf
70 '';
71 };
72
73 package = mkOption {
74 type = types.package;
75 default = pkgs.knot-dns;
76 defaultText = "pkgs.knot-dns";
77 description = ''
78 Which Knot DNS package to use
79 '';
80 };
81
82 cliWrappers = mkOption {
83 readOnly = true;
84 type = types.package;
85 default = knot-cli-wrappers;
86 defaultText = "knot-cli-wrappers";
87 };
88 };
89 };
90
91 config = mkIf cfg.enable {
92 users.users.knot = {
93 isSystemUser = true;
94 group = "knot";
95 description = "Knot daemon user";
96 };
97
98 users.groups.knot.gid = null;
99 systemd.services.knot = {
100 unitConfig.Documentation = "man:knotd(8) man:knot.conf(5) man:knotc(8) https://www.knot-dns.cz/docs/${cfg.package.version}/html/";
101 description = cfg.package.meta.description;
102 wantedBy = [ "multi-user.target" ];
103 wants = [ "network.target" ];
104 after = ["network.target" ];
105
106 serviceConfig = {
107 Type = "notify";
108 ExecStart = "${cfg.package}/bin/knotd --config=${configFile} --socket=${socketFile} ${concatStringsSep " " cfg.extraArgs}";
109 ExecReload = "${cfg.cliWrappers}/bin/knotc reload";
110 CapabilityBoundingSet = "CAP_NET_BIND_SERVICE CAP_SETPCAP";
111 AmbientCapabilities = "CAP_NET_BIND_SERVICE CAP_SETPCAP";
112 NoNewPrivileges = true;
113 User = "knot";
114 RuntimeDirectory = "knot";
115 StateDirectory = "knot";
116 StateDirectoryMode = "0700";
117 PrivateDevices = true;
118 RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
119 SystemCallArchitectures = "native";
120 Restart = "on-abort";
121 };
122 };
123
124 environment.systemPackages = [ cfg.cliWrappers ];
125 };
126}
diff --git a/modules/luksroot.nix b/modules/luksroot.nix
new file mode 100644
index 00000000..abaee692
--- /dev/null
+++ b/modules/luksroot.nix
@@ -0,0 +1,1075 @@
1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 luks = config.boot.initrd.luks;
7 kernelPackages = config.boot.kernelPackages;
8
9 commonFunctions = ''
10 die() {
11 echo "$@" >&2
12 exit 1
13 }
14
15 dev_exist() {
16 local target="$1"
17 if [ -e $target ]; then
18 return 0
19 else
20 local uuid=$(echo -n $target | sed -e 's,UUID=\(.*\),\1,g')
21 blkid --uuid $uuid >/dev/null
22 return $?
23 fi
24 }
25
26 wait_target() {
27 local name="$1"
28 local target="$2"
29 local secs="''${3:-10}"
30 local desc="''${4:-$name $target to appear}"
31
32 if ! dev_exist $target; then
33 echo -n "Waiting $secs seconds for $desc..."
34 local success=false;
35 for try in $(seq $secs); do
36 echo -n "."
37 sleep 1
38 if dev_exist $target; then
39 success=true
40 break
41 fi
42 done
43 if [ $success == true ]; then
44 echo " - success";
45 return 0
46 else
47 echo " - failure";
48 return 1
49 fi
50 fi
51 return 0
52 }
53
54 wait_yubikey() {
55 local secs="''${1:-10}"
56
57 ykinfo -v 1>/dev/null 2>&1
58 if [ $? != 0 ]; then
59 echo -n "Waiting $secs seconds for YubiKey to appear..."
60 local success=false
61 for try in $(seq $secs); do
62 echo -n .
63 sleep 1
64 ykinfo -v 1>/dev/null 2>&1
65 if [ $? == 0 ]; then
66 success=true
67 break
68 fi
69 done
70 if [ $success == true ]; then
71 echo " - success";
72 return 0
73 else
74 echo " - failure";
75 return 1
76 fi
77 fi
78 return 0
79 }
80
81 wait_gpgcard() {
82 local secs="''${1:-10}"
83
84 gpg --card-status > /dev/null 2> /dev/null
85 if [ $? != 0 ]; then
86 echo -n "Waiting $secs seconds for GPG Card to appear"
87 local success=false
88 for try in $(seq $secs); do
89 echo -n .
90 sleep 1
91 gpg --card-status > /dev/null 2> /dev/null
92 if [ $? == 0 ]; then
93 success=true
94 break
95 fi
96 done
97 if [ $success == true ]; then
98 echo " - success";
99 return 0
100 else
101 echo " - failure";
102 return 1
103 fi
104 fi
105 return 0
106 }
107 '';
108
109 preCommands = ''
110 # A place to store crypto things
111
112 # A ramfs is used here to ensure that the file used to update
113 # the key slot with cryptsetup will never get swapped out.
114 # Warning: Do NOT replace with tmpfs!
115 mkdir -p /crypt-ramfs
116 mount -t ramfs none /crypt-ramfs
117
118 # Cryptsetup locking directory
119 mkdir -p /run/cryptsetup
120
121 # For YubiKey salt storage
122 mkdir -p /crypt-storage
123
124 ${optionalString luks.gpgSupport ''
125 export GPG_TTY=$(tty)
126 export GNUPGHOME=/crypt-ramfs/.gnupg
127
128 gpg-agent --daemon --scdaemon-program $out/bin/scdaemon > /dev/null 2> /dev/null
129 ''}
130
131 # Disable all input echo for the whole stage. We could use read -s
132 # instead but that would ocasionally leak characters between read
133 # invocations.
134 stty -echo
135 '';
136
137 postCommands = ''
138 stty echo
139 umount /crypt-storage 2>/dev/null
140 umount /crypt-ramfs 2>/dev/null
141 '';
142
143 openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, yubikey, gpgCard, fido2, clevis, dmi, fallbackToPassword, preOpenCommands, postOpenCommands, ... }: assert name' == name;
144 let
145 csopen = "cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} ${optionalString (header != null) "--header=${header}"}";
146 cschange = "cryptsetup luksChangeKey ${device} ${optionalString (header != null) "--header=${header}"}";
147 in ''
148 # Wait for luksRoot (and optionally keyFile and/or header) to appear, e.g.
149 # if on a USB drive.
150 wait_target "device" ${device} || die "${device} is unavailable"
151
152 ${optionalString (header != null) ''
153 wait_target "header" ${header} || die "${header} is unavailable"
154 ''}
155
156 do_open_passphrase() {
157 local passphrase
158
159 while true; do
160 echo -n "Passphrase for ${device}: "
161 passphrase=
162 while true; do
163 if [ -e /crypt-ramfs/passphrase ]; then
164 echo "reused"
165 passphrase=$(cat /crypt-ramfs/passphrase)
166 break
167 else
168 # ask cryptsetup-askpass
169 echo -n "${device}" > /crypt-ramfs/device
170
171 # and try reading it from /dev/console with a timeout
172 IFS= read -t 1 -r passphrase
173 if [ -n "$passphrase" ]; then
174 ${if luks.reusePassphrases then ''
175 # remember it for the next device
176 echo -n "$passphrase" > /crypt-ramfs/passphrase
177 '' else ''
178 # Don't save it to ramfs. We are very paranoid
179 ''}
180 echo
181 break
182 fi
183 fi
184 done
185 echo -n "Verifying passphrase for ${device}..."
186 echo -n "$passphrase" | ${csopen} --key-file=-
187 if [ $? == 0 ]; then
188 echo " - success"
189 ${if luks.reusePassphrases then ''
190 # we don't rm here because we might reuse it for the next device
191 '' else ''
192 rm -f /crypt-ramfs/passphrase
193 ''}
194 break
195 else
196 echo " - failure"
197 # ask for a different one
198 rm -f /crypt-ramfs/passphrase
199 fi
200 done
201 }
202
203 # LUKS
204 open_normally() {
205 ${if (keyFile != null) then ''
206 if wait_target "key file" ${keyFile}; then
207 ${csopen} --key-file=${keyFile} \
208 ${optionalString (keyFileSize != null) "--keyfile-size=${toString keyFileSize}"} \
209 ${optionalString (keyFileOffset != null) "--keyfile-offset=${toString keyFileOffset}"}
210 else
211 ${if fallbackToPassword then "echo" else "die"} "${keyFile} is unavailable"
212 echo " - failing back to interactive password prompt"
213 do_open_passphrase
214 fi
215 '' else ''
216 do_open_passphrase
217 ''}
218 }
219
220 ${optionalString (luks.yubikeySupport && (yubikey != null)) ''
221 # YubiKey
222 rbtohex() {
223 ( od -An -vtx1 | tr -d ' \n' )
224 }
225
226 hextorb() {
227 ( tr '[:lower:]' '[:upper:]' | sed -e 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI' | xargs printf )
228 }
229
230 do_open_yubikey() {
231 # Make all of these local to this function
232 # to prevent their values being leaked
233 local salt
234 local iterations
235 local k_user
236 local challenge
237 local response
238 local k_luks
239 local opened
240 local new_salt
241 local new_iterations
242 local new_challenge
243 local new_response
244 local new_k_luks
245
246 mount -t ${yubikey.storage.fsType} ${yubikey.storage.device} /crypt-storage || \
247 die "Failed to mount YubiKey salt storage device"
248
249 salt="$(cat /crypt-storage${yubikey.storage.path} | sed -n 1p | tr -d '\n')"
250 iterations="$(cat /crypt-storage${yubikey.storage.path} | sed -n 2p | tr -d '\n')"
251 challenge="$(echo -n $salt | openssl-wrap dgst -binary -sha512 | rbtohex)"
252 response="$(ykchalresp -${toString yubikey.slot} -x $challenge 2>/dev/null)"
253
254 for try in $(seq 3); do
255 ${optionalString yubikey.twoFactor ''
256 echo -n "Enter two-factor passphrase: "
257 k_user=
258 while true; do
259 if [ -e /crypt-ramfs/passphrase ]; then
260 echo "reused"
261 k_user=$(cat /crypt-ramfs/passphrase)
262 break
263 else
264 # Try reading it from /dev/console with a timeout
265 IFS= read -t 1 -r k_user
266 if [ -n "$k_user" ]; then
267 ${if luks.reusePassphrases then ''
268 # Remember it for the next device
269 echo -n "$k_user" > /crypt-ramfs/passphrase
270 '' else ''
271 # Don't save it to ramfs. We are very paranoid
272 ''}
273 echo
274 break
275 fi
276 fi
277 done
278 ''}
279
280 if [ ! -z "$k_user" ]; then
281 k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString yubikey.keyLength} $iterations $response | rbtohex)"
282 else
283 k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $iterations $response | rbtohex)"
284 fi
285
286 echo -n "$k_luks" | hextorb | ${csopen} --key-file=-
287
288 if [ $? == 0 ]; then
289 opened=true
290 ${if luks.reusePassphrases then ''
291 # We don't rm here because we might reuse it for the next device
292 '' else ''
293 rm -f /crypt-ramfs/passphrase
294 ''}
295 break
296 else
297 opened=false
298 echo "Authentication failed!"
299 fi
300 done
301
302 [ "$opened" == false ] && die "Maximum authentication errors reached"
303
304 echo -n "Gathering entropy for new salt (please enter random keys to generate entropy if this blocks for long)..."
305 for i in $(seq ${toString yubikey.saltLength}); do
306 byte="$(dd if=/dev/random bs=1 count=1 2>/dev/null | rbtohex)";
307 new_salt="$new_salt$byte";
308 echo -n .
309 done;
310 echo "ok"
311
312 new_iterations="$iterations"
313 ${optionalString (yubikey.iterationStep > 0) ''
314 new_iterations="$(($new_iterations + ${toString yubikey.iterationStep}))"
315 ''}
316
317 new_challenge="$(echo -n $new_salt | openssl-wrap dgst -binary -sha512 | rbtohex)"
318
319 new_response="$(ykchalresp -${toString yubikey.slot} -x $new_challenge 2>/dev/null)"
320
321 if [ ! -z "$k_user" ]; then
322 new_k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString yubikey.keyLength} $new_iterations $new_response | rbtohex)"
323 else
324 new_k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $new_iterations $new_response | rbtohex)"
325 fi
326
327 echo -n "$new_k_luks" | hextorb > /crypt-ramfs/new_key
328 echo -n "$k_luks" | hextorb | ${cschange} --key-file=- /crypt-ramfs/new_key
329
330 if [ $? == 0 ]; then
331 echo -ne "$new_salt\n$new_iterations" > /crypt-storage${yubikey.storage.path}
332 else
333 echo "Warning: Could not update LUKS key, current challenge persists!"
334 fi
335
336 rm -f /crypt-ramfs/new_key
337 umount /crypt-storage
338 }
339
340 open_with_hardware() {
341 if wait_yubikey ${toString yubikey.gracePeriod}; then
342 do_open_yubikey
343 else
344 echo "No YubiKey found, falling back to non-YubiKey open procedure"
345 open_normally
346 fi
347 }
348 ''}
349
350 ${optionalString (luks.gpgSupport && (gpgCard != null)) ''
351
352 do_open_gpg_card() {
353 # Make all of these local to this function
354 # to prevent their values being leaked
355 local pin
356 local opened
357
358 gpg --import /gpg-keys/${device}/pubkey.asc > /dev/null 2> /dev/null
359
360 gpg --card-status > /dev/null 2> /dev/null
361
362 for try in $(seq 3); do
363 echo -n "PIN for GPG Card associated with device ${device}: "
364 pin=
365 while true; do
366 if [ -e /crypt-ramfs/passphrase ]; then
367 echo "reused"
368 pin=$(cat /crypt-ramfs/passphrase)
369 break
370 else
371 # and try reading it from /dev/console with a timeout
372 IFS= read -t 1 -r pin
373 if [ -n "$pin" ]; then
374 ${if luks.reusePassphrases then ''
375 # remember it for the next device
376 echo -n "$pin" > /crypt-ramfs/passphrase
377 '' else ''
378 # Don't save it to ramfs. We are very paranoid
379 ''}
380 echo
381 break
382 fi
383 fi
384 done
385 echo -n "Verifying passphrase for ${device}..."
386 echo -n "$pin" | gpg -q --batch --passphrase-fd 0 --pinentry-mode loopback -d /gpg-keys/${device}/cryptkey.gpg 2> /dev/null | ${csopen} --key-file=- > /dev/null 2> /dev/null
387 if [ $? == 0 ]; then
388 echo " - success"
389 ${if luks.reusePassphrases then ''
390 # we don't rm here because we might reuse it for the next device
391 '' else ''
392 rm -f /crypt-ramfs/passphrase
393 ''}
394 break
395 else
396 echo " - failure"
397 # ask for a different one
398 rm -f /crypt-ramfs/passphrase
399 fi
400 done
401
402 [ "$opened" == false ] && die "Maximum authentication errors reached"
403 }
404
405 open_with_hardware() {
406 if wait_gpgcard ${toString gpgCard.gracePeriod}; then
407 do_open_gpg_card
408 else
409 echo "No GPG Card found, falling back to normal open procedure"
410 open_normally
411 fi
412 }
413 ''}
414
415 ${optionalString (luks.fido2Support && (fido2.credential != null)) ''
416
417 open_with_hardware() {
418 local passsphrase
419
420 ${if fido2.passwordLess then ''
421 export passphrase=""
422 '' else ''
423 read -rsp "FIDO2 salt for ${device}: " passphrase
424 echo
425 ''}
426 ${optionalString (lib.versionOlder kernelPackages.kernel.version "5.4") ''
427 echo "On systems with Linux Kernel < 5.4, it might take a while to initialize the CRNG, you might want to use linuxPackages_latest."
428 echo "Please move your mouse to create needed randomness."
429 ''}
430 echo "Waiting for your FIDO2 device..."
431 fido2luks open ${device} ${name} ${fido2.credential} --await-dev ${toString fido2.gracePeriod} --salt string:$passphrase
432 if [ $? -ne 0 ]; then
433 echo "No FIDO2 key found, falling back to normal open procedure"
434 open_normally
435 fi
436 }
437 ''}
438
439 ${optionalString (luks.clevisSupport && clevis) ''
440
441 open_with_hardware() {
442 mkdir -p /crypt-ramfs/clevis
443
444 TMPDIR=/crypt-ramfs/clevis clevis luks unlock -d ${device} -n ${name}
445
446 if [ $? -ne 0 ]; then
447 echo "Unlocking with clevis failed, falling back to normal open procedure"
448 open_normally
449 fi
450 }
451
452 ''}
453
454 ${optionalString (luks.dmiSupport && dmi) ''
455
456 open_with_hardware() {
457 dmidecode -s system-uuid > /crypt-ramfs/passphrase
458
459 ${csopen} --key-file=- < /crypt-ramfs/passphrase > /dev/null 2> /dev/null
460
461 if [ $? -ne 0 ]; then
462 echo "Unlocking with system-uuid failed, falling back to normal open procedure"
463 rm -f /crypt-ramfs/passphrase
464 open_normally
465 ${optionalString (!luks.reusePassphrases) ''
466 else
467 rm -f /crypt-ramfs/passphrase
468 ''}
469 fi
470 }
471
472 ''}
473
474 # commands to run right before we mount our device
475 ${preOpenCommands}
476
477 ${if (luks.yubikeySupport && (yubikey != null)) || (luks.gpgSupport && (gpgCard != null)) || (luks.fido2Support && (fido2.credential != null)) || (luks.clevisSupport && clevis) || (luks.dmiSupport && dmi) then ''
478 open_with_hardware
479 '' else ''
480 open_normally
481 ''}
482
483 # commands to run right after we mounted our device
484 ${postOpenCommands}
485 '';
486
487 askPass = pkgs.writeScriptBin "cryptsetup-askpass" ''
488 #!/bin/sh
489
490 ${commonFunctions}
491
492 while true; do
493 wait_target "luks" /crypt-ramfs/device 10 "LUKS to request a passphrase" || die "Passphrase is not requested now"
494 device=$(cat /crypt-ramfs/device)
495
496 echo -n "Passphrase for $device: "
497 IFS= read -rs passphrase
498 echo
499
500 rm /crypt-ramfs/device
501 echo -n "$passphrase" > /crypt-ramfs/passphrase
502 done
503 '';
504
505 preLVM = filterAttrs (n: v: v.preLVM) luks.devices;
506 postLVM = filterAttrs (n: v: !v.preLVM) luks.devices;
507
508in
509{
510 disabledModules = [ "system/boot/luksroot.nix" ];
511
512 imports = [
513 (mkRemovedOptionModule [ "boot" "initrd" "luks" "enable" ] "")
514 ];
515
516 options = {
517
518 boot.initrd.luks.mitigateDMAAttacks = mkOption {
519 type = types.bool;
520 default = true;
521 description = ''
522 Unless enabled, encryption keys can be easily recovered by an attacker with physical
523 access to any machine with PCMCIA, ExpressCard, ThunderBolt or FireWire port.
524 More information is available at <link xlink:href="http://en.wikipedia.org/wiki/DMA_attack"/>.
525
526 This option blacklists FireWire drivers, but doesn't remove them. You can manually
527 load the drivers if you need to use a FireWire device, but don't forget to unload them!
528 '';
529 };
530
531 boot.initrd.luks.cryptoModules = mkOption {
532 type = types.listOf types.str;
533 default =
534 [ "aes" "aes_generic" "blowfish" "twofish"
535 "serpent" "cbc" "xts" "lrw" "sha1" "sha256" "sha512"
536 "af_alg" "algif_skcipher"
537 ];
538 description = ''
539 A list of cryptographic kernel modules needed to decrypt the root device(s).
540 The default includes all common modules.
541 '';
542 };
543
544 boot.initrd.luks.forceLuksSupportInInitrd = mkOption {
545 type = types.bool;
546 default = false;
547 internal = true;
548 description = ''
549 Whether to configure luks support in the initrd, when no luks
550 devices are configured.
551 '';
552 };
553
554 boot.initrd.luks.reusePassphrases = mkOption {
555 type = types.bool;
556 default = true;
557 description = ''
558 When opening a new LUKS device try reusing last successful
559 passphrase.
560
561 Useful for mounting a number of devices that use the same
562 passphrase without retyping it several times.
563
564 Such setup can be useful if you use <command>cryptsetup
565 luksSuspend</command>. Different LUKS devices will still have
566 different master keys even when using the same passphrase.
567 '';
568 };
569
570 boot.initrd.luks.devices = mkOption {
571 default = { };
572 example = { luksroot.device = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08"; };
573 description = ''
574 The encrypted disk that should be opened before the root
575 filesystem is mounted. Both LVM-over-LUKS and LUKS-over-LVM
576 setups are supported. The unencrypted devices can be accessed as
577 <filename>/dev/mapper/<replaceable>name</replaceable></filename>.
578 '';
579
580 type = with types; attrsOf (submodule (
581 { name, ... }: { options = {
582
583 name = mkOption {
584 visible = false;
585 default = name;
586 example = "luksroot";
587 type = types.str;
588 description = "Name of the unencrypted device in <filename>/dev/mapper</filename>.";
589 };
590
591 device = mkOption {
592 example = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08";
593 type = types.str;
594 description = "Path of the underlying encrypted block device.";
595 };
596
597 header = mkOption {
598 default = null;
599 example = "/root/header.img";
600 type = types.nullOr types.str;
601 description = ''
602 The name of the file or block device that
603 should be used as header for the encrypted device.
604 '';
605 };
606
607 keyFile = mkOption {
608 default = null;
609 example = "/dev/sdb1";
610 type = types.nullOr types.str;
611 description = ''
612 The name of the file (can be a raw device or a partition) that
613 should be used as the decryption key for the encrypted device. If
614 not specified, you will be prompted for a passphrase instead.
615 '';
616 };
617
618 keyFileSize = mkOption {
619 default = null;
620 example = 4096;
621 type = types.nullOr types.int;
622 description = ''
623 The size of the key file. Use this if only the beginning of the
624 key file should be used as a key (often the case if a raw device
625 or partition is used as key file). If not specified, the whole
626 <literal>keyFile</literal> will be used decryption, instead of just
627 the first <literal>keyFileSize</literal> bytes.
628 '';
629 };
630
631 keyFileOffset = mkOption {
632 default = null;
633 example = 4096;
634 type = types.nullOr types.int;
635 description = ''
636 The offset of the key file. Use this in combination with
637 <literal>keyFileSize</literal> to use part of a file as key file
638 (often the case if a raw device or partition is used as a key file).
639 If not specified, the key begins at the first byte of
640 <literal>keyFile</literal>.
641 '';
642 };
643
644 # FIXME: get rid of this option.
645 preLVM = mkOption {
646 default = true;
647 type = types.bool;
648 description = "Whether the luksOpen will be attempted before LVM scan or after it.";
649 };
650
651 allowDiscards = mkOption {
652 default = false;
653 type = types.bool;
654 description = ''
655 Whether to allow TRIM requests to the underlying device. This option
656 has security implications; please read the LUKS documentation before
657 activating it.
658 '';
659 };
660
661 fallbackToPassword = mkOption {
662 default = false;
663 type = types.bool;
664 description = ''
665 Whether to fallback to interactive passphrase prompt if the keyfile
666 cannot be found. This will prevent unattended boot should the keyfile
667 go missing.
668 '';
669 };
670
671 gpgCard = mkOption {
672 default = null;
673 description = ''
674 The option to use this LUKS device with a GPG encrypted luks password by the GPG Smartcard.
675 If null (the default), GPG-Smartcard will be disabled for this device.
676 '';
677
678 type = with types; nullOr (submodule {
679 options = {
680 gracePeriod = mkOption {
681 default = 10;
682 type = types.int;
683 description = "Time in seconds to wait for the GPG Smartcard.";
684 };
685
686 encryptedPass = mkOption {
687 default = "";
688 type = types.path;
689 description = "Path to the GPG encrypted passphrase.";
690 };
691
692 publicKey = mkOption {
693 default = "";
694 type = types.path;
695 description = "Path to the Public Key.";
696 };
697 };
698 });
699 };
700
701 fido2 = {
702 credential = mkOption {
703 default = null;
704 example = "f1d00200d8dc783f7fb1e10ace8da27f8312d72692abfca2f7e4960a73f48e82e1f7571f6ebfcee9fb434f9886ccc8fcc52a6614d8d2";
705 type = types.nullOr types.str;
706 description = "The FIDO2 credential ID.";
707 };
708
709 gracePeriod = mkOption {
710 default = 10;
711 type = types.int;
712 description = "Time in seconds to wait for the FIDO2 key.";
713 };
714
715 passwordLess = mkOption {
716 default = false;
717 type = types.bool;
718 description = ''
719 Defines whatever to use an empty string as a default salt.
720
721 Enable only when your device is PIN protected, such as <link xlink:href="https://trezor.io/">Trezor</link>.
722 '';
723 };
724 };
725
726 yubikey = mkOption {
727 default = null;
728 description = ''
729 The options to use for this LUKS device in YubiKey-PBA.
730 If null (the default), YubiKey-PBA will be disabled for this device.
731 '';
732
733 type = with types; nullOr (submodule {
734 options = {
735 twoFactor = mkOption {
736 default = true;
737 type = types.bool;
738 description = "Whether to use a passphrase and a YubiKey (true), or only a YubiKey (false).";
739 };
740
741 slot = mkOption {
742 default = 2;
743 type = types.int;
744 description = "Which slot on the YubiKey to challenge.";
745 };
746
747 saltLength = mkOption {
748 default = 16;
749 type = types.int;
750 description = "Length of the new salt in byte (64 is the effective maximum).";
751 };
752
753 keyLength = mkOption {
754 default = 64;
755 type = types.int;
756 description = "Length of the LUKS slot key derived with PBKDF2 in byte.";
757 };
758
759 iterationStep = mkOption {
760 default = 0;
761 type = types.int;
762 description = "How much the iteration count for PBKDF2 is increased at each successful authentication.";
763 };
764
765 gracePeriod = mkOption {
766 default = 10;
767 type = types.int;
768 description = "Time in seconds to wait for the YubiKey.";
769 };
770
771 /* TODO: Add to the documentation of the current module:
772
773 Options related to the storing the salt.
774 */
775 storage = {
776 device = mkOption {
777 default = "/dev/sda1";
778 type = types.path;
779 description = ''
780 An unencrypted device that will temporarily be mounted in stage-1.
781 Must contain the current salt to create the challenge for this LUKS device.
782 '';
783 };
784
785 fsType = mkOption {
786 default = "vfat";
787 type = types.str;
788 description = "The filesystem of the unencrypted device.";
789 };
790
791 path = mkOption {
792 default = "/crypt-storage/default";
793 type = types.str;
794 description = ''
795 Absolute path of the salt on the unencrypted device with
796 that device's root directory as "/".
797 '';
798 };
799 };
800 };
801 });
802 };
803
804 clevis = mkOption {
805 type = types.bool;
806 default = false;
807 description = ''
808 Unlock device via clevis (e.g. with a tpm)
809 '';
810 };
811
812 dmi = mkOption {
813 type = types.bool;
814 default = false;
815 description = ''
816 Unlock device via system-uuid (via dmidecode)
817 '';
818 };
819
820 preOpenCommands = mkOption {
821 type = types.lines;
822 default = "";
823 example = ''
824 mkdir -p /tmp/persistent
825 mount -t zfs rpool/safe/persistent /tmp/persistent
826 '';
827 description = ''
828 Commands that should be run right before we try to mount our LUKS device.
829 This can be useful, if the keys needed to open the drive is on another partion.
830 '';
831 };
832
833 postOpenCommands = mkOption {
834 type = types.lines;
835 default = "";
836 example = ''
837 umount /tmp/persistent
838 '';
839 description = ''
840 Commands that should be run right after we have mounted our LUKS device.
841 '';
842 };
843 };
844 }));
845 };
846
847 boot.initrd.luks.gpgSupport = mkOption {
848 default = false;
849 type = types.bool;
850 description = ''
851 Enables support for authenticating with a GPG encrypted password.
852 '';
853 };
854
855 boot.initrd.luks.yubikeySupport = mkOption {
856 default = false;
857 type = types.bool;
858 description = ''
859 Enables support for authenticating with a YubiKey on LUKS devices.
860 See the NixOS wiki for information on how to properly setup a LUKS device
861 and a YubiKey to work with this feature.
862 '';
863 };
864
865 boot.initrd.luks.fido2Support = mkOption {
866 default = false;
867 type = types.bool;
868 description = ''
869 Enables support for authenticating with FIDO2 devices.
870 '';
871 };
872
873 boot.initrd.luks.clevisSupport = mkOption {
874 default = false;
875 type = types.bool;
876 description = ''
877 Enables support for unlocking luks volumes via clevis (e.g. with a tpm)
878 '';
879 };
880
881 boot.initrd.luks.dmiSupport = mkOption {
882 default = false;
883 type = types.bool;
884 description = ''
885 Enables support for unlocking luks volumes via system-uuid (via dmidecode)
886 '';
887 };
888
889 };
890
891 config = mkIf (luks.devices != {} || luks.forceLuksSupportInInitrd) {
892
893 assertions =
894 [ { assertion = !(luks.gpgSupport && luks.yubikeySupport);
895 message = "YubiKey and GPG Card may not be used at the same time.";
896 }
897
898 { assertion = !(luks.gpgSupport && luks.fido2Support);
899 message = "FIDO2 and GPG Card may not be used at the same time.";
900 }
901
902 { assertion = !(luks.gpgSupport && luks.clevisSupport);
903 message = "Clevis and GPG Card may not be used at the same time.";
904 }
905
906 { assertion = !(luks.gpgSupport && luks.dmiSupport);
907 message = "DMI and GPG Card may not be used at the same time.";
908 }
909
910 { assertion = !(luks.fido2Support && luks.yubikeySupport);
911 message = "FIDO2 and YubiKey may not be used at the same time.";
912 }
913
914 { assertion = !(luks.fido2Support && luks.clevisSupport);
915 message = "FIDO2 and Clevis may not be used at the same time.";
916 }
917
918 { assertion = !(luks.fido2Support && luks.dmiSupport);
919 message = "FIDO2 and DMI may not be used at the same time.";
920 }
921
922 { assertion = !(luks.yubikeySupport && luks.clevisSupport);
923 message = "Clevis and YubiKey may not be used at the same time.";
924 }
925
926 { assertion = !(luks.yubikeySupport && luks.dmiSupport);
927 message = "DMI and YubiKey may not be used at the same time.";
928 }
929
930 ];
931
932 # actually, sbp2 driver is the one enabling the DMA attack, but this needs to be tested
933 boot.blacklistedKernelModules = optionals luks.mitigateDMAAttacks
934 ["firewire_ohci" "firewire_core" "firewire_sbp2"];
935
936 # Some modules that may be needed for mounting anything ciphered
937 boot.initrd.availableKernelModules = [ "dm_mod" "dm_crypt" "cryptd" "input_leds" ]
938 ++ luks.cryptoModules
939 # workaround until https://marc.info/?l=linux-crypto-vger&m=148783562211457&w=4 is merged
940 # remove once 'modprobe --show-depends xts' shows ecb as a dependency
941 ++ (if builtins.elem "xts" luks.cryptoModules then ["ecb"] else []);
942
943 # copy the cryptsetup binary and it's dependencies
944 boot.initrd.extraUtilsCommands =
945 let
946 extraUtils = config.system.build.extraUtils;
947
948 ipkgs = pkgs.appendOverlays [
949 (final: prev: {
950 tpm2-tss = prev.tpm2-tss.overrideAttrs (oldAttrs: {
951 doCheck = false;
952 patches = [];
953 postPatch = ''
954 patchShebangs script
955 '';
956 configureFlags = [];
957 });
958 })
959 ];
960 in ''
961 copy_bin_and_libs ${pkgs.cryptsetup}/bin/cryptsetup
962 copy_bin_and_libs ${askPass}/bin/cryptsetup-askpass
963 sed -i s,/bin/sh,$out/bin/sh, $out/bin/cryptsetup-askpass
964
965 ${optionalString luks.yubikeySupport ''
966 copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykchalresp
967 copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykinfo
968 copy_bin_and_libs ${pkgs.openssl.bin}/bin/openssl
969
970 cc -O3 -I${pkgs.openssl.dev}/include -L${pkgs.openssl.out}/lib ${./pbkdf2-sha512.c} -o pbkdf2-sha512 -lcrypto
971 strip -s pbkdf2-sha512
972 copy_bin_and_libs pbkdf2-sha512
973
974 mkdir -p $out/etc/ssl
975 cp -pdv ${pkgs.openssl.out}/etc/ssl/openssl.cnf $out/etc/ssl
976
977 cat > $out/bin/openssl-wrap <<EOF
978 #!$out/bin/sh
979 export OPENSSL_CONF=$out/etc/ssl/openssl.cnf
980 $out/bin/openssl "\$@"
981 EOF
982 chmod +x $out/bin/openssl-wrap
983 ''}
984
985 ${optionalString luks.fido2Support ''
986 copy_bin_and_libs ${pkgs.fido2luks}/bin/fido2luks
987 ''}
988
989
990 ${optionalString luks.gpgSupport ''
991 copy_bin_and_libs ${pkgs.gnupg}/bin/gpg
992 copy_bin_and_libs ${pkgs.gnupg}/bin/gpg-agent
993 copy_bin_and_libs ${pkgs.gnupg}/libexec/scdaemon
994
995 ${concatMapStringsSep "\n" (x:
996 if x.gpgCard != null then
997 ''
998 mkdir -p $out/secrets/gpg-keys/${x.device}
999 cp -a ${x.gpgCard.encryptedPass} $out/secrets/gpg-keys/${x.device}/cryptkey.gpg
1000 cp -a ${x.gpgCard.publicKey} $out/secrets/gpg-keys/${x.device}/pubkey.asc
1001 ''
1002 else ""
1003 ) (attrValues luks.devices)
1004 }
1005 ''}
1006
1007 ${optionalString luks.clevisSupport ''
1008 for bin in ${ipkgs.tpm2-tools}/bin/* ${ipkgs.jose}/bin/* ${ipkgs.libpwquality}/bin/*; do
1009 if [ -L $bin ]; then
1010 cp -v $bin $out/bin
1011 else
1012 copy_bin_and_libs $bin
1013 fi
1014 done
1015
1016 copy_bin_and_libs ${ipkgs.bash}/bin/bash
1017 for bin in ${ipkgs.clevis}/bin/* ${ipkgs.clevis}/bin/.*; do
1018 [ -f $bin -o -L $bin ] || continue
1019
1020 substitute $bin $out/bin/$(basename $bin) \
1021 --replace ${ipkgs.bash}/bin $out/bin \
1022 --replace ${ipkgs.clevis}/bin $out/bin \
1023 --replace ${ipkgs.tpm2-tools}/bin $out/bin \
1024 --replace ${ipkgs.jose}/bin $out/bin \
1025 --replace ${ipkgs.libpwquality}/bin $out/bin \
1026 --replace ${ipkgs.coreutils}/bin $out/bin
1027
1028 [ -x $bin ] && chmod +x $out/bin/$(basename $bin)
1029 done
1030
1031 for lib in ${ipkgs.tpm2-tss}/lib/*.so*; do
1032 if [ -f $lib ]; then
1033 patchelf --output $out/lib/$(basename $lib) $lib \
1034 --set-rpath $out/lib
1035 else
1036 cp -pdv $lib $out/lib
1037 fi
1038 done
1039 ''}
1040
1041 ${optionalString luks.dmiSupport ''
1042 copy_bin_and_libs ${pkgs.dmidecode}/bin/dmidecode
1043 ''}
1044 '';
1045
1046 boot.initrd.extraUtilsCommandsTest = ''
1047 $out/bin/cryptsetup --version
1048 ${optionalString luks.yubikeySupport ''
1049 $out/bin/ykchalresp -V
1050 $out/bin/ykinfo -V
1051 $out/bin/openssl-wrap version
1052 ''}
1053 ${optionalString luks.gpgSupport ''
1054 $out/bin/gpg --version
1055 $out/bin/gpg-agent --version
1056 $out/bin/scdaemon --version
1057 ''}
1058 ${optionalString luks.fido2Support ''
1059 $out/bin/fido2luks --version
1060 ''}
1061 ${optionalString luks.clevisSupport ''
1062 $out/bin/jose alg
1063 ''}
1064 ${optionalString luks.dmiSupport ''
1065 $out/bin/dmidecode --version
1066 ''}
1067 '';
1068
1069 boot.initrd.preFailCommands = postCommands;
1070 boot.initrd.preLVMCommands = commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand preLVM) + postCommands;
1071 boot.initrd.postDeviceCommands = commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand postLVM) + postCommands;
1072
1073 environment.systemPackages = [ pkgs.cryptsetup ];
1074 };
1075}
diff --git a/modules/networkd.nix b/modules/networkd.nix
new file mode 100644
index 00000000..d0a48f98
--- /dev/null
+++ b/modules/networkd.nix
@@ -0,0 +1,297 @@
1{ config, lib, utils, pkgs, ... }:
2
3with utils;
4with lib;
5
6let
7
8 cfg = config.networking;
9 interfaces = attrValues cfg.interfaces;
10
11 interfaceIps = i:
12 i.ipv4.addresses
13 ++ optionals cfg.enableIPv6 i.ipv6.addresses;
14
15 dhcpStr = useDHCP: if useDHCP == true || useDHCP == null then "yes" else "no";
16
17 slaves =
18 concatLists (map (bond: bond.interfaces) (attrValues cfg.bonds))
19 ++ concatLists (map (bridge: bridge.interfaces) (attrValues cfg.bridges))
20 ++ map (sit: sit.dev) (attrValues cfg.sits)
21 ++ map (vlan: vlan.interface) (attrValues cfg.vlans)
22 # add dependency to physical or independently created vswitch member interface
23 # TODO: warn the user that any address configured on those interfaces will be useless
24 ++ concatMap (i: attrNames (filterAttrs (_: config: config.type != "internal") i.interfaces)) (attrValues cfg.vswitches);
25
26in
27
28{
29 disabledModules = [ "tasks/network-interfaces-systemd.nix" ];
30
31 config = mkIf cfg.useNetworkd {
32
33 assertions = [ {
34 assertion = cfg.defaultGatewayWindowSize == null;
35 message = "networking.defaultGatewayWindowSize is not supported by networkd.";
36 } {
37 assertion = cfg.vswitches == {};
38 message = "networking.vswitches are not supported by networkd.";
39 } {
40 assertion = cfg.defaultGateway == null || cfg.defaultGateway.interface == null;
41 message = "networking.defaultGateway.interface is not supported by networkd.";
42 } {
43 assertion = cfg.defaultGateway6 == null || cfg.defaultGateway6.interface == null;
44 message = "networking.defaultGateway6.interface is not supported by networkd.";
45 } {
46 assertion = cfg.useDHCP == false;
47 message = ''
48 networking.useDHCP is not supported by networkd.
49 Please use per interface configuration and set the global option to false.
50 '';
51 } ] ++ flip mapAttrsToList cfg.bridges (n: { rstp, ... }: {
52 assertion = !rstp;
53 message = "networking.bridges.${n}.rstp is not supported by networkd.";
54 });
55
56 networking.dhcpcd.enable = mkDefault false;
57
58 systemd.network =
59 let
60 domains = cfg.search ++ (optional (cfg.domain != null) cfg.domain);
61 genericNetwork = override:
62 let gateways = optional (cfg.defaultGateway != null && (cfg.defaultGateway.address or "") != "") cfg.defaultGateway.address
63 ++ optional (cfg.defaultGateway6 != null && (cfg.defaultGateway6.address or "") != "") cfg.defaultGateway6.address;
64 in optionalAttrs (gateways != [ ]) {
65 routes = override (map (gateway: {
66 routeConfig = {
67 Gateway = gateway;
68 GatewayOnLink = false;
69 };
70 }) gateways);
71 } // optionalAttrs (domains != [ ]) {
72 domains = override domains;
73 };
74 in mkMerge [ {
75 enable = true;
76 }
77 (mkMerge (forEach interfaces (i: {
78 netdevs = mkIf i.virtual ({
79 "40-${i.name}" = {
80 netdevConfig = {
81 Name = i.name;
82 Kind = i.virtualType;
83 };
84 "${i.virtualType}Config" = optionalAttrs (i.virtualOwner != null) {
85 User = i.virtualOwner;
86 };
87 };
88 });
89 networks."40-${i.name}" = mkMerge [ (genericNetwork mkDefault) {
90 name = mkDefault i.name;
91 DHCP = mkForce (dhcpStr
92 (if i.useDHCP != null then i.useDHCP else false));
93 address = forEach (interfaceIps i)
94 (ip: "${ip.address}/${toString ip.prefixLength}");
95 networkConfig.IPv6PrivacyExtensions = "kernel";
96 linkConfig = optionalAttrs (i.macAddress != null) {
97 MACAddress = i.macAddress;
98 } // optionalAttrs (i.mtu != null) {
99 MTUBytes = toString i.mtu;
100 };
101 }];
102 })))
103 (mkMerge (flip mapAttrsToList cfg.bridges (name: bridge: {
104 netdevs."40-${name}" = {
105 netdevConfig = {
106 Name = name;
107 Kind = "bridge";
108 };
109 };
110 networks = listToAttrs (forEach bridge.interfaces (bi:
111 nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) {
112 DHCP = mkOverride 0 (dhcpStr false);
113 networkConfig.Bridge = name;
114 } ])));
115 })))
116 (mkMerge (flip mapAttrsToList cfg.bonds (name: bond: {
117 netdevs."40-${name}" = {
118 netdevConfig = {
119 Name = name;
120 Kind = "bond";
121 };
122 bondConfig = let
123 # manual mapping as of 2017-02-03
124 # man 5 systemd.netdev [BOND]
125 # to https://www.kernel.org/doc/Documentation/networking/bonding.txt
126 # driver options.
127 driverOptionMapping = let
128 trans = f: optName: { valTransform = f; optNames = [optName]; };
129 simp = trans id;
130 ms = trans (v: v + "ms");
131 in {
132 Mode = simp "mode";
133 TransmitHashPolicy = simp "xmit_hash_policy";
134 LACPTransmitRate = simp "lacp_rate";
135 MIIMonitorSec = ms "miimon";
136 UpDelaySec = ms "updelay";
137 DownDelaySec = ms "downdelay";
138 LearnPacketIntervalSec = simp "lp_interval";
139 AdSelect = simp "ad_select";
140 FailOverMACPolicy = simp "fail_over_mac";
141 ARPValidate = simp "arp_validate";
142 # apparently in ms for this value?! Upstream bug?
143 ARPIntervalSec = simp "arp_interval";
144 ARPIPTargets = simp "arp_ip_target";
145 ARPAllTargets = simp "arp_all_targets";
146 PrimaryReselectPolicy = simp "primary_reselect";
147 ResendIGMP = simp "resend_igmp";
148 PacketsPerSlave = simp "packets_per_slave";
149 GratuitousARP = { valTransform = id;
150 optNames = [ "num_grat_arp" "num_unsol_na" ]; };
151 AllSlavesActive = simp "all_slaves_active";
152 MinLinks = simp "min_links";
153 };
154
155 do = bond.driverOptions;
156 assertNoUnknownOption = let
157 knownOptions = flatten (mapAttrsToList (_: kOpts: kOpts.optNames)
158 driverOptionMapping);
159 # options that apparently don’t exist in the networkd config
160 unknownOptions = [ "primary" ];
161 assertTrace = bool: msg: if bool then true else builtins.trace msg false;
162 in assert all (driverOpt: assertTrace
163 (elem driverOpt (knownOptions ++ unknownOptions))
164 "The bond.driverOption `${driverOpt}` cannot be mapped to the list of known networkd bond options. Please add it to the mapping above the assert or to `unknownOptions` should it not exist in networkd.")
165 (mapAttrsToList (k: _: k) do); "";
166 # get those driverOptions that have been set
167 filterSystemdOptions = filterAttrs (sysDOpt: kOpts:
168 any (kOpt: do ? ${kOpt}) kOpts.optNames);
169 # build final set of systemd options to bond values
170 buildOptionSet = mapAttrs (_: kOpts: with kOpts;
171 # we simply take the first set kernel bond option
172 # (one option has multiple names, which is silly)
173 head (map (optN: valTransform (do.${optN}))
174 # only map those that exist
175 (filter (o: do ? ${o}) optNames)));
176 in seq assertNoUnknownOption
177 (buildOptionSet (filterSystemdOptions driverOptionMapping));
178
179 };
180
181 networks = listToAttrs (forEach bond.interfaces (bi:
182 nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) {
183 DHCP = mkOverride 0 (dhcpStr false);
184 networkConfig.Bond = name;
185 } ])));
186 })))
187 (mkMerge (flip mapAttrsToList cfg.macvlans (name: macvlan: {
188 netdevs."40-${name}" = {
189 netdevConfig = {
190 Name = name;
191 Kind = "macvlan";
192 };
193 macvlanConfig = optionalAttrs (macvlan.mode != null) { Mode = macvlan.mode; };
194 };
195 networks."40-${macvlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
196 macvlan = [ name ];
197 } ]);
198 })))
199 (mkMerge (flip mapAttrsToList cfg.sits (name: sit: {
200 netdevs."40-${name}" = {
201 netdevConfig = {
202 Name = name;
203 Kind = "sit";
204 };
205 tunnelConfig =
206 (optionalAttrs (sit.remote != null) {
207 Remote = sit.remote;
208 }) // (optionalAttrs (sit.local != null) {
209 Local = sit.local;
210 }) // (optionalAttrs (sit.ttl != null) {
211 TTL = sit.ttl;
212 });
213 };
214 networks = mkIf (sit.dev != null) {
215 "40-${sit.dev}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
216 tunnel = [ name ];
217 } ]);
218 };
219 })))
220 (mkMerge (flip mapAttrsToList cfg.vlans (name: vlan: {
221 netdevs."40-${name}" = {
222 netdevConfig = {
223 Name = name;
224 Kind = "vlan";
225 };
226 vlanConfig.Id = vlan.id;
227 };
228 networks."40-${vlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) {
229 vlan = [ name ];
230 } ]);
231 })))
232 ];
233
234 # We need to prefill the slaved devices with networking options
235 # This forces the network interface creator to initialize slaves.
236 networking.interfaces = listToAttrs (map (i: nameValuePair i { }) slaves);
237
238 systemd.services = let
239 # We must escape interfaces due to the systemd interpretation
240 subsystemDevice = interface:
241 "sys-subsystem-net-devices-${escapeSystemdPath interface}.device";
242 # support for creating openvswitch switches
243 createVswitchDevice = n: v: nameValuePair "${n}-netdev"
244 (let
245 deps = map subsystemDevice (attrNames (filterAttrs (_: config: config.type != "internal") v.interfaces));
246 ofRules = pkgs.writeText "vswitch-${n}-openFlowRules" v.openFlowRules;
247 in
248 { description = "Open vSwitch Interface ${n}";
249 wantedBy = [ "network.target" (subsystemDevice n) ];
250 # and create bridge before systemd-networkd starts because it might create internal interfaces
251 before = [ "systemd-networkd.service" ];
252 # shutdown the bridge when network is shutdown
253 partOf = [ "network.target" ];
254 # requires ovs-vswitchd to be alive at all times
255 bindsTo = [ "ovs-vswitchd.service" ];
256 # start switch after physical interfaces and vswitch daemon
257 after = [ "network-pre.target" "ovs-vswitchd.service" ] ++ deps;
258 wants = deps; # if one or more interface fails, the switch should continue to run
259 serviceConfig.Type = "oneshot";
260 serviceConfig.RemainAfterExit = true;
261 path = [ pkgs.iproute2 config.virtualisation.vswitch.package ];
262 preStart = ''
263 echo "Resetting Open vSwitch ${n}..."
264 ovs-vsctl --if-exists del-br ${n} -- add-br ${n} \
265 -- set bridge ${n} protocols=${concatStringsSep "," v.supportedOpenFlowVersions}
266 '';
267 script = ''
268 echo "Configuring Open vSwitch ${n}..."
269 ovs-vsctl ${concatStrings (mapAttrsToList (name: config: " -- add-port ${n} ${name}" + optionalString (config.vlan != null) " tag=${toString config.vlan}") v.interfaces)} \
270 ${concatStrings (mapAttrsToList (name: config: optionalString (config.type != null) " -- set interface ${name} type=${config.type}") v.interfaces)} \
271 ${concatMapStrings (x: " -- set-controller ${n} " + x) v.controllers} \
272 ${concatMapStrings (x: " -- " + x) (splitString "\n" v.extraOvsctlCmds)}
273
274
275 echo "Adding OpenFlow rules for Open vSwitch ${n}..."
276 ovs-ofctl --protocols=${v.openFlowVersion} add-flows ${n} ${ofRules}
277 '';
278 postStop = ''
279 echo "Cleaning Open vSwitch ${n}"
280 echo "Shuting down internal ${n} interface"
281 ip link set ${n} down || true
282 echo "Deleting flows for ${n}"
283 ovs-ofctl --protocols=${v.openFlowVersion} del-flows ${n} || true
284 echo "Deleting Open vSwitch ${n}"
285 ovs-vsctl --if-exists del-br ${n} || true
286 '';
287 });
288 in mapAttrs' createVswitchDevice cfg.vswitches
289 // {
290 "network-local-commands" = {
291 after = [ "systemd-networkd.service" ];
292 bindsTo = [ "systemd-networkd.service" ];
293 };
294 };
295 };
296
297}
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..f4100e73
--- /dev/null
+++ b/modules/yggdrasil/default.nix
@@ -0,0 +1,50 @@
1{ config, lib, customUtils, ... }:
2let
3 cfg = config.services.tinc.yggdrasil;
4in {
5 options = {
6 services.tinc.yggdrasil = lib.mkOption {
7 default = {};
8 type = lib.types.submodule {
9 options = {
10 enable = lib.mkEnableOption "Yggdrasil tinc network";
11
12 connect = lib.mkOption {
13 default = true;
14 type = lib.types.bool;
15 description = ''
16 Connect to central server
17 '';
18 };
19 };
20 };
21 };
22 };
23
24 config = lib.mkIf cfg.enable {
25 services.tinc.networks.yggdrasil = {
26 name = config.networking.hostName;
27 hostSettings = customUtils.nixImport { dir = ./hosts; };
28 debugLevel = 2;
29 interfaceType = "tap";
30 settings = {
31 Mode = "switch";
32 PingTimeout = 30;
33 ConnectTo = lib.mkIf cfg.connect "ymir";
34 };
35 };
36
37 sops.secrets = {
38 tinc-yggdrasil-rsa = {
39 key = "rsa";
40 path = "/etc/tinc/yggdrasil/rsa_key.priv";
41 sopsFile = ./hosts + "/${config.services.tinc.networks.yggdrasil.name}/private-keys.yaml";
42 };
43 tinc-yggdrasil-ed25519 = {
44 key = "ed25519";
45 path = "/etc/tinc/yggdrasil/rsa_key.priv";
46 sopsFile = ./hosts + "/${config.services.tinc.networks.yggdrasil.name}/private-keys.yaml";
47 };
48 };
49 };
50}
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/clevis.nix b/overlays/clevis.nix
new file mode 100644
index 00000000..a786340c
--- /dev/null
+++ b/overlays/clevis.nix
@@ -0,0 +1,37 @@
1final: prev:
2{
3 clevis = prev.clevis.overrideAttrs (oldAttrs: {
4 buildInputs = (oldAttrs.buildInputs or []) ++ [final.tpm2-tools];
5 nativeBuildInputs = (oldAttrs.nativeBuildInputs or []) ++ [final.makeWrapper];
6
7 preFixup = ''
8 ${oldAttrs.preFixup or ""}
9
10 for bin in $out/bin/*; do
11 test -x $bin || continue
12
13 substituteInPlace $bin \
14 --replace /bin/cat ${final.coreutils}/bin/cat
15
16 wrapProgram $bin \
17 --prefix PATH : ${final.tpm2-tools}/bin \
18 --prefix PATH : ${final.jose}/bin \
19 --prefix PATH : ${final.libpwquality}/bin
20 done
21 '';
22 });
23
24 tpm2-tools = prev.tpm2-tools.overrideAttrs (oldAttrs: {
25 fixupPhase = ''
26 ${oldAttrs.fixupPhase or ""}
27
28 for wrapper in $out/bin/tpm2_*; do
29 symlink=.''${wrapper}-wrapped
30
31 test -h $symlink || continue
32
33 mv -v $symlink $wrapper
34 done
35 '';
36 });
37}
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/core.nix b/system-profiles/core.nix
index 14a1dae3..18c271bd 100644
--- a/system-profiles/core.nix
+++ b/system-profiles/core.nix
@@ -44,7 +44,7 @@ in {
44 nix = { 44 nix = {
45 package = pkgs.nixUnstable; 45 package = pkgs.nixUnstable;
46 useSandbox = true; 46 useSandbox = true;
47 allowedUsers = [ "@wheel" ]; 47 allowedUsers = [ "*" ];
48 trustedUsers = [ "root" "@wheel" ]; 48 trustedUsers = [ "root" "@wheel" ];
49 extraOptions = '' 49 extraOptions = ''
50 experimental-features = nix-command flakes ca-references 50 experimental-features = nix-command flakes ca-references
diff --git a/system-profiles/default-locale.nix b/system-profiles/default-locale.nix
new file mode 100644
index 00000000..4359bd9a
--- /dev/null
+++ b/system-profiles/default-locale.nix
@@ -0,0 +1,7 @@
1{...}:
2{
3 i18n.defaultLocale = "en_US.UTF-8";
4 console.keyMap = "dvorak-programmer";
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..ede68e9f
--- /dev/null
+++ b/system-profiles/initrd-all-crypto-modules.nix
@@ -0,0 +1,17 @@
1{ pkgs, config, ...}:
2let
3 moduleList = builtins.fromJSON (builtins.readFile (pkgs.runCommandCC "crypto-modules" { buildInputs = with pkgs; [ jq ]; } ''
4 echo "[]" > $out
5 while IFS= read -r -d $'\0' file; do
6 unpacked=$(basename "''${file}" .xz)
7 xz -cd "''${file}" > "''${unpacked}"
8
9 module=$(readelf -Wp .gnu.linkonce.this_module "''${unpacked}" | sed -rn '/\[\s*[0-9]+\] /{ s/^[^]]*\]\s*//; p; q; }')
10 jq '. + [ $name ]' $out --arg name "''${module}" > out.json && mv out.json $out
11 done < <(find ${config.system.modulesTree}/lib/modules/*/kernel{,/arch/*}/crypto -iname '*.ko.xz' -print0 | sort -z)
12 ''));
13in {
14 boot.initrd.luks.cryptoModules = moduleList ++ [
15 "encrypted_keys"
16 ];
17}
diff --git a/system-profiles/openssh/default.nix b/system-profiles/openssh/default.nix
new file mode 100644
index 00000000..09ff58f7
--- /dev/null
+++ b/system-profiles/openssh/default.nix
@@ -0,0 +1,41 @@
1{ customUtils, lib, config, hostName, pkgs, ... }:
2{
3 config = {
4 programs.ssh.knownHosts = lib.zipAttrsWith (_name: values: builtins.head values) (lib.mapAttrsToList (name: lib.mapAttrs' (type: value: lib.nameValuePair "${name}-${type}" value)) (customUtils.nixImport { dir = ./known-hosts; }));
5
6 systemd.user.services."ssh-agent".enable = lib.mkForce false; # ssh-agent should be done via home-manager
7
8 services.openssh = lib.mkIf config.services.openssh.enable {
9 hostKeys = [
10 { path = "/etc/ssh/ssh_host_rsa_key";
11 type = "rsa";
12 }
13 { path = "/etc/ssh/ssh_host_ed25519_key";
14 type = "ed25519";
15 }
16 ];
17 };
18
19 sops.secrets = lib.mkIf config.services.openssh.enable {
20 ssh_host_rsa_key = {
21 key = "rsa";
22 path = "/etc/ssh/ssh_host_rsa_key";
23 sopsFile = ./host-keys + "/${hostName}.yaml";
24 };
25 ssh_host_ed25519_key = {
26 key = "ed25519";
27 path = "/etc/ssh/ssh_host_ed25519_key";
28 sopsFile = ./host-keys + "/${hostName}.yaml";
29 };
30 };
31
32 environment.etc = lib.mkIf config.services.openssh.enable {
33 "ssh/ssh_host_rsa_key.pub".text = config.services.openssh.knownHosts."${hostName}-rsa".publicKey;
34 "ssh/ssh_host_ed25519_key.pub".text = config.services.openssh.knownHosts."${hostName}-ed25519".publicKey;
35 };
36
37 environment.systemPackages = lib.mkIf config.services.openssh.enable (with pkgs; [
38 rxvt_unicode.terminfo alacritty.terminfo
39 ]);
40 };
41}
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/host-keys/surtr.yaml b/system-profiles/openssh/host-keys/surtr.yaml
new file mode 100644
index 00000000..d31fda3c
--- /dev/null
+++ b/system-profiles/openssh/host-keys/surtr.yaml
@@ -0,0 +1,37 @@
1dsa: ENC[AES256_GCM,data:+2nv7zqZ47EhQnZ5x8no5J/0r1iWgmSxBMkiYig18JgdZa77lj9y05EubUlELJlGcqcQMD97xFKQdU9BRMGn2Bq0IeK3nBoENkDEp7ksmn6c+hh2GVCDDk5sQnNl1yQB0bo7TEHRddJe+xbAWZ2LGFRyc+ApBWCtqBnQ12+58dY2VarcogT0tNPaga76Mz6K31V6g9PAhiEL9hFl8F3p/ptgsdIArX6xI6+g/tRkE056EuPsqiAXbMagm2dBQOEUiicUEqpgMvnLhlre3FkRWWotrGtGsB0Sum4BY1mrTxaXJf/J5kt4DrPconjooSkzQPImTPtO1cXIHmmAWiQIOJ6xon3dXB2pq4KpWs4wVZ1a2OMXrgAkUCDsYwNQiyv85YREaMvo7J1nfbrP2VRRdPcP3XgbwXkcqzt0r9LYq17bYHxJmhrP9Si7wCwBkCkGxgyLhMTmXQjKairqRqM+5lnHa44bQ2AhMmq4iKCkSJo7fUXOdDNX0n61PFJSOT/UVsHiVDlqcpcSm/GcARcObdVWmXvjw81pmMTjMlAmLupU5yPiWTZr5lMTG42hDO1Yjq9fuA6tN+Op9duVaWc6YiA7qDSTEpGtGmb4wObAgelCfuUTTo+Wp77lqTAeL2eGfuSAKDMb6z3NfCpSpgvJMWhGJtqJfKd2Cu87P06UUgmnyyjVazVTUWmT/ALX6rosUmGChbx5SSQ8tin/jQdIoq4Qs3hR+7AtBil7DF9fpUfHy8OmP/BKbgihuZa1+n9fyiLnw4QfKGFWmmoh06upSrAu3fYofVe1iYuZUUTP/NNUxn/JxbYn2OrQ9PY7GVcFdRBtyU+R8aQ/bawlcwnaIIQOA04iip1P5tgQ8CWCzuHFyP+d9Eun9oD6wPvSoNWXAftochNU6Ol8MQ8ESe22hrO19TrirgxEGwOTVFVLIV1kLlRZ+eCcY3HI/Mq9hnu9KndrMcLsPnjPyzsPo/CmeIOxryT1lY+LKU2mJT0pLq4j1Mt3QrjOuruM4fSYrS5DdDWTXP1Gk5GiULyd12RQFQVznRlYMy6BcSUmRgTWezX+o0dt18aJGNsm0UpS3yjL8BQpatpxq8LxCp9pNhuIyfF3HyjyNeTX1be60XmblYPr+w6OK8sV83p7TTGYVe9xzp0HC87+SBRTWN6SUCkmVrdhzbEBaDZG1a5Ldvar1QbAZF8riUMHnGarh2ZX6Gv2acsnMV5qb8t/HlK3rCKtYk27ok8ENFQqNIpMvVtoxKNPLNBRMNz8KG77fSnYoGmDFVlY82mrDBqvfbHbZgd3wb6WO3xu0KYY8Vc+ymcFromXBxYOhliNAHxYWNRJPfbXEqGRmA2/P5H0yWPdqL29JLX7SeW6+xzWUY5LWDSHBd2qTl8FdWIddAl8jBs2IiNUIGAUayNLNCSjgbVXOsdk8Hx9aQs5RmwFzXX+jBCeYRXdkN5HLFP8o+9hkWbBv4vIRrJxrRvRAVEoP55ZLhFZakrQ52NQwA2Z2SlFnMgSbs9jzDuMRjhX6kkuiE4DdbCkEfkhaqWDY46PGpfhNJ1LTWJZjTLuwvSwb6toyhEzRdpZGHX9ZCGR39h8GtLD/NVvYx4+8rgvWEah8Aizh5PtmjxJD03dQBuFSpnniuxYyEzRiQPK0QcvDerBRzUFC45CCBOdvbf2EcaQygkRCyoAC4hQ5KE/0sWKlotg0zOtwh3Yd+yts4AA4Hmf8Mx3i4hTlG90hVccdPULr9M613V9VmD95+Vz1ugxM5QwTvMvqNh4WI86hX4RfpgulxVYrQOAKZjgTlGnUNu3wZK7X8P9PGOCt/QkfXIXMA==,iv:+x4eD9lw/b2GvLV8Wsp+UZY+lqCN1oknXCbTGwnQNqU=,tag:pU67QbCxEmUc/mT1gzTTsQ==,type:str]
2ecdsa: ENC[AES256_GCM,data:obC22x4LSu8YAbqioGx+6SMggFZyT5SDLN+XyOyoTP9OxP8OV73yWp4G36jJqxri9uRtdZbhy+sMXwNEErvN4p9H3pPv0H9KiqJf3Lbs/7pqGT5xbWD4f5A9BcXVwi7qk9XQIDtLfyL6YsQnyOFRO2/bWKY/CpvZOxhyRCkt3s4PsuTRRNvPfmuGbH4OgKDB8aIzNtracLpYakQtg9hI9izQetE4rf758QR6E76C4h5/J7SBzzPh2TfDYesVjjM8PwR7elN8ee8bfpQLKhyqA+FGSInLWrPho+7vBVLC2IYHOShbCXX19XXp9w/vefFGdBIKE7yEaKpCIavW4g1ZVt3UJJC9th/yVUDMpwp7/L72qgwgH7/tO4X5fT6NhUnhJA7xl+eiqe5/SpVCJwyYduTZHAlLlMAM4moF134vj3clsDb2fkV1AoZYeUtxCmbKVDy9lD12FqJ+mfTJ+bc3SZTCBR1ZeBMu/6u/xNzl3TLTfuJ5bW9VFa7vCy48di9mtcr5BhEU7u4C9T7RzoVNDjstxyoc5grqX4Q7GPOcmghV21QE49nsvdQTUxQejtZ/83lY5h/7nhzfd12mHR99cZhiZdwdJZoK8XKWwgjkXLXQF353+KShgFNmBs/8SyqAjNP66wUPO+YW/S7c7HoDiPIN9n/Za4erCg==,iv:agx+d66Pv5KOqzuzQFLMiywyh3REDzXrGW/F6lAm9tE=,tag:ozfiD2P6knpu+QQpSo+GCg==,type:str]
3ed25519: ENC[AES256_GCM,data:oFbZx5F797BF/lmKgfdX6ByB2/wBe8gfzJ2gc6m56vgBKFVIs1aIaOFHcUkeJ5wGfSVTZ4C6zv8bTPouIEDSEkFXT9rUXXUbnQ4tCw7BtpfJv40uZWEror5L8vLGzDV5kBs3TaD96ceAdzIF4psQpsyLhhJTtM8h6tJOs5JpDIG3BMXzVkaSAUCsYLIK9iqkyhVCDidqUm+A9Tke6NP5Oqq90n5q7hamPPwpzMFAFl6z5rf6jH3KTSOk+X98VT6vuInuoKc4ODLfGx1AQPJou4GGEOuFkbmsyA7CPZgNsgfnn13nR3CBqiioyzUJwBww3HAfyA2+ZvXhT4fQwAyM/o72F1gpL+LyPzHKPw4IR7f5+WGgoJjs/ORCvPbPG9jpkFEXKOUEgNFO881eIFizBYHvGc7zQS0rD5SspzSqETJ7zRqeB5EpANvZ2Fpv4adJKHZ/o0McMjr9meAxtV6Iunp2EG8tT1/QvuZZ+dFB1Uitpwt2fpd5iFX0uTeSB3gMPgWz3fvx207Bp/SprQdO,iv:vNu2tRvwkBUdgVSnkPli8NMlXNKfbrnf+MsPbnrDF58=,tag:oWYTDKymFM8YRSXwKdc5wg==,type:str]
4rsa: ENC[AES256_GCM,data:RBq1mV5XP4J7ETvRI/BLi1+MwqowUflN12h5T8csVejTLVjNDb03TbPMyH0mACPFK5fNDO+9vQ9HC6riIOiSZkXCjwkP//HdF8XqfAg4GrB+Y21fALDF4+Hmux/SrdJfL+JQ4iKWc1zI/bXZOX686/gdDC/dBFLbBD2Tc+n2yqRY6qDAqHZev7epAUtF0w5DTU+Oyt1tcuP73U+oqNs1C8o8zlKAfOgfSAS03sA0WDJJDWCwlnqAnP/Oh4MmUxAobi5afrJLLnmotCsvBCWN8a+zjR5PanGXINv8O1Fhi/ehKauM51zL7IZfKpjZxHS3IzjcTsGKJKP3QE6e8tYojKAPmZac/xcmsDTG/qZC4z7Tp0u6haENbZo+5kK8QyWLZr7b0yUqIYq4s7Y1/dMv3VO+EdmEKfVugg+W2+J9YsZNEkOuAptfKgM3eklry5YrkUVPy8U2nNyahVPzZExfFmjGGmKhdita8SLpD6s+ang0D5GIU8QmfAcrEqIqsCWn202BTdb77NMsfziTmRsJxdTtB5ZmeQL611uL2GI06Th6WqN96I+7eh0hze5TySaDRasKpnqWKnfg0UcoPaw8qW39CXWlZ1sqjlJP82lDs1rKkAeBDcB6YU+mtQnpfSPdGMOHnMNJ97tY4JhdJW4QCqR5Lr/m2aKxOwoc+BQqT4lSymBqPkolDYrBS+pHMHF6MS8TPsdBHt/6bFEiCzXBEmDe/Hx3eNVuT/YQJSBJzF9g98X0/79fkBMi+yI857d/a93cGzddQ1jLjS1yT6Qr+eegozNfeJPzdOSYbIh2xaYZ4MjUiO0LQr44DXOCA+y2viOpi44EvLqqqqDWHf5bcLfljVVF2bgzS54MThC0qlllNDaFpDSUdDQbVYgu8V2D8yCW/WuS+FPtvqIcSL3ZVsE1KgLplIXOdAOlw7YTisjOaXU1uwJNBYfxWlNjiiV/BtJ8rs53i9uPJN5oZ3cRh14mjAoJAhajamN3+xaXo85clthH9B3qSacduzsinsKiy4gL00Fi2WY3MKKbjaWL/JuELqvD37EibQ7HXffWPTBzc7zILRBetW57w44vMCX3kr0ESGzdtfVLsb7/WY937LACkkFIs7puWcgBJVwhAAEvTeOl11ynPKGDEjHah1wTNvWlpe6v4ko3zwNJOVp3lk9M0fwrNbS1jz4HKU+NGPrZS1/nOUd3YjJXCXzJTcq7hhhsBQYOw8+CT3EeWPURj0L0Ddo/a7MZgdTnN325iPqGErDx0F6UkAR8KcyT2M7AWqF0XRYsGDRs99siRF2aQ8UTq2IX5NE8lLG/QtyPrvVTV/tOX285Jt9C5yRufbNcGKUrXPB+SQM2eH0ip6QTvZ+CYfbGO2VIR7RISTUHKKNPMEMGOz5ZQaPc+8qMfFP7+1lz+fkZ7n+ZSVj9GI/GkbDHNERBos7JBSeG3hvVgraGKeanmHerRxlX42GEF8cQvMEjrCGp+PZPMcEmM7c5/5yJ0iYNyk3ES8LP2ax0XoGpWPxA67WNdzNiDd2JSLwHytxaPf3U5FO5rWLj4byrjDRoOZs0maIsVkEkABcCEyN7t20WzhBmnrx8fkrULXgeIcbQjFcL1x6UW2FS1AUWnHu23XoFxFu4HHIPYZdL70DdsoJr5MS0oZ92oBhaQAXE8nP3RLIMm8yZTqnYuTNzXo3ap9iXyFX+Srp4UvVPyHj8hO3LsXeX6TDQL5R93VMtv9EMQVksSLJU+kLeoKqkMivSAEtD9Zt/GYQdXoXjInY7NpDCha9NBSg+5N2vtVeojI7q/g6Oimq+uT2dRB+62lOd635xgnMRTlwwwkdNI9t1vLcuGStnzWMzYAQhf+TS3LPx7+JGeR8/TXMSoegFf+64hXFJRnxLJTjpfHAJkk0pQ02XAWObab6GVLj8uDcra9cDAKKDftVRX7gOC0k+Vt7vWxkfVPYb8F0EMa2MVm/qYUzcxtnCrxYljtzqXU4UDgw7jUb1yxzI1scU04MaQknfQammw0Lk+ICPEdEX2sJGiG9zH4acCdyqW77QL3tJouCaKPdd6cVq20cyfgjNYT6zbkdlmXTz960Nx9zT4mmk6ngoYwvR1g4nub6Fgo6JDA3AIU7e+uxbO83+I+0jmyOePqkbbJs3igIGekm4M5nl5MhnFyEOr0UrcEKqgp/suRZf/As4UqOHf2NAjP2CMtnHwB8Ug40j5blU/s38ILfdD/Vd87gwZFpx5QCsJjVC+ucuINl4V5WbRBkbIID6ZrLGXoVzYYVqY6oNAF93fl2xCSZykdkVdBaJvSbpz8wbzimjV7nUL2Bxu+sJN7809DTpgwyGutQJ4f9CgpAb0+g2TCGdvbvB+Uh5H62iQaZDsaD8guusrIEkxjW83V/38DYaWe/Wh3CTfTqRqgDxql22n6a33qlU+k0F/het07ZhqtQ8bwL8QATDYg9zxCfRgr4YiMFQ1evS1qCOF8KWnRa2ApOO0qPJJ49DxJeFMD29pXhmxtrBPJelSeZqDUYVx8Ns0n72hh6FxBkRItntU6HGAtC+aZCi3h5HnUKyRTb63tJwm7OGkVltP5QaFT0nE1Tv2FdQSKFcLvnMCcx+P9VJ0dPXShbCv6JMdxQuqRIR3RRI5D6cv4O4HZKxtMuiZmsZySRFbSG033TGDEJKShlT5CleowW+21dHZeAWGLR9d/ScRE2i1Z69O4SgoZPyDpwHTb5jB+47oh0HNvUIvskyHfp57HkcUCmszZHf4wPblUjr33hCpk0k48mQfVEjekdRxojE7FTbDIERFZPb6tMDuUnhM7p7CE96A2ka0/renFPA8czLGOTltyxQmaCc0ZOV4nlUJ0m6L5dcXSPjpfm81y23GJJVy0DCY0tZ5YKT2C+5EEhL1gx0pWZ7Tf844FmwCfXaUIkxNkAKV4c/hBlvdAev6unXUj6LdsHEg8GqlV3KCAeG0wG9CnspmUnSrgNDSfa3AkFjTKXzLtLXa+FYTq8GNEyvrMQq/cWrHNU6kDr6Q+CH86U8J7kVepwbSTi49TvTxgkY8DiOlufv7EPa6CmgEYGNybMWhvdZ+rghVyp7botR30Wab/T7eHnUd+0CQHKaQmJ/eHBxIfJToQrchyXTM+VrehI7EQqn2HzeuSt5SneMtgNS71p+jgdrAO7w1LhJy7TDutGGPsW5ln5aT509OhwSUF5ZzJTTVytwb0QsBeGq+2DyrGwxQoaBOgXOFyI4Slryeb1+BC2ednybYRpve75cWUJ1RBxq9TrD8HSesOUE4tTH0DLhe211NLDCSzQ8wk4jVaPr+us4YKEAiBjL6TSWMg3ZdHDIcQVO4o8iXSlM4mDpRU56JODuQLJtBAMVPDkj1Ybj18qIhJ1ZO5YZmUfTQPXtv9heGRQI4MB00RmF8VTXafyqu07DY3bM2RPj9r/1ygEXq2sNXFQ2v2Q0Hai+sl9xU8djdvL8zhTNUWHyewhYnA==,iv:aSdbpsJoDerPqWTSWp0bQIcLChCzeoeGSTKEzvfzafo=,tag:LeOxrwyXdJyj7M0FRn25QQ==,type:str]
5sops:
6 kms: []
7 gcp_kms: []
8 azure_kv: []
9 hc_vault: []
10 age: []
11 lastmodified: "2021-05-15T13:05:09Z"
12 mac: ENC[AES256_GCM,data:ATdT6u3dMOgaBVg7cS5tpaA0fyoQdlW/jSzwPjm1mi7j5rNkilIiqIR+C159MrI5eeApkyOpzQP2lIAlANjbO+TlO2YIYd0Ue8pdoEZGQvDyWv3AARLfdlaPzFAGAnBnjihVmKp2kQjfmcSJkASBQM8e89R1PsAKGhH5xS5b0zM=,iv:UyMsuxYWVs/Q9/HTfPtjDNf+tUOHSAqA3klFt7yewYQ=,tag:Vu8xY4NVdw6MvjDWZwiO4A==,type:str]
13 pgp:
14 - created_at: "2021-05-15T13:03:47Z"
15 enc: |
16 -----BEGIN PGP MESSAGE-----
17
18 hF4DXxoViZlp6dISAQdAr0a9IJdY95UvcmMkCS73pQZVdjqHnVTTcpCXYuqkmiYw
19 rTIqyEsqpoSrkR57LBNX98ix99H/hvj6x8+dsv+K/nJQ9Jjs921UW2HJ8hPMD44Q
20 0l4B2MyG+We3OClbt8BJmDo38/+/k9zSBdW2zbYEr4zhG7SCw0BryrPJwGAW54KT
21 1fdnNwzN5jdFRObhkq8I725IaU4d7GYrpVebw29HP2fd0Uf+62iBToraRJNj3sxL
22 =JRkx
23 -----END PGP MESSAGE-----
24 fp: 30D3453B8CD02FE2A3E7C78C0FB536FB87AE8F51
25 - created_at: "2021-05-15T13:03:47Z"
26 enc: |
27 -----BEGIN PGP MESSAGE-----
28
29 hF4DyFKFNkTVG5oSAQdAINIHQVygfLGVo2gdlKCoojmD5layNM6K/QlQR/CsaTsw
30 SY+3psZUwnwwe7QRnt2gHSOUgYrG6/nhiCAfxoZBQZ6zm+v0IUdbRKEJhhGJnHfV
31 0l4BUMxGLYHapIPjzTUwYQv9rF30zO7pJ3vU+4zkReNOcPzENLGX1uZu/1aULOcO
32 F33lTLP2B9B7pjvPoetJiuds3jO7JZrN3mFhIf7MTZyg5dMBbDSnUMJ6NIW+ug5F
33 =SAFL
34 -----END PGP MESSAGE-----
35 fp: 7ED22F4AA7BB55728B643DC5471B7D88E4EF66F8
36 unencrypted_suffix: _unencrypted
37 version: 3.7.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/surtr.nix b/system-profiles/openssh/known-hosts/surtr.nix
new file mode 100644
index 00000000..8d227b44
--- /dev/null
+++ b/system-profiles/openssh/known-hosts/surtr.nix
@@ -0,0 +1,28 @@
1let
2 hostNames = ["surtr.muspelheim.yggdrasil" "surtr.yggdrasil.li"];
3in {
4 dsa = {
5 inherit hostNames;
6 publicKey = ''
7 ssh-dss AAAAB3NzaC1kc3MAAACBAIgY3WWK/yD1QzQMako4FDkD22YODiCA/d7Ga14xpx7ujSPQJ0PqFd1aTPhrEZdy6pMnL82chGJD/oeurAacBxeuUouKvr10vtpaDJpcvqr/9m4zsx0OSeHl9M5PuSumjEXL/bsF/QSeo9Vp8uznLgHP/oglP8OI4Qsdh/0wisK1AAAAFQD04cH0JaWgJEUePcuYd02KW7t4aQAAAIBkFCGDPkJedRdJRy+l2OW6H7XdCQnA814cWOGaUItw5IVWz6KlVGPlETrBtRJhrgiwApy1Sk2rHxmuHCirAFS6FCZ5ct5wHKV2L/1CDphJzsYql9hUBTgevEpuAg9Kn1WjtcV+t+3LO9URD64BKHzpvtM2I5hAU4Zu6G2OV150MQAAAIB7B3lRZBxkDfb5x4cjitzFXN3FUzBcl0SD6/TJ+TH2lbTONMIXdT9zHw5BfEz3ObcScxCT2g99bvkxDwPNFRAorLAlEhCYB2zUV5QnJd8ZpIB3AFbokUxq+Q8fs5tU16Wv9TQ4oYmY3m9UAEqVz6ph562Ss+axO9qfSlNZX8feGA==
8 '';
9 };
10 ecdsa = {
11 inherit hostNames;
12 publicKey = ''
13 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKLjbW8GWc7dF8HD8QrFZpZJop2xvFgvZnYfIl/slFASvphD6MBOHq3jx0+Tuk51xd4mvByTwoh8eokLZJidkZQ=
14 '';
15 };
16 ed25519 = {
17 inherit hostNames;
18 publicKey = ''
19 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO2LOAbV3XuAqJpXVY+YUnLIbhRsmAUmVQT3MioXGGgj
20 '';
21 };
22 rsa = {
23 inherit hostNames;
24 publicKey = ''
25 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQClFn6IDsDjuLXpThBtrRj+HLkNAwuBc4BgNqqIXkSRXy1FhDVgdI2iXKnJJLT/MWBMz73+QEYI+nDV6cxMCu292sZal+EAkyXJG6gQ9/rboucTuMWosrifAYabY4jUY79vYOiQGHG3XMIVjTQE8dRoXASzPKcok7PHftuW2qUu6ti7s3tqxY89Ez0cUz7jIECR7zHpIHZQbPd7z9luWOwZZc/eUGGWSxxz6idSPi/Adjk4FS56kIBk/uq9bZ8ylE/nwuJFUV90GzIr2nIQAcg6UVjYkw22+tA8BKzkS5Kx9ur7jVAhgs1qavKGnkYBuE4MvfjDzrkxRtlIPOjUQ3uuqYXkkkdMCooDl6+oKvN8dug6+cMdXn3/Q63cA0ols5rJz8iAtBoPRI8b835BWZcYHCk2aF2xT5hmB+GVhnFRZP8p9cRlr0jhYRjJKp80gTT7BPlMAQ0Sfmz5jLPd7X9yInKXCXdzxLTWvGqDq4GpunWVR6rgDMq5AswIcNhcwCc=
26 '';
27 };
28}
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/qemu-guest.nix b/system-profiles/qemu-guest.nix
new file mode 100644
index 00000000..8654eba0
--- /dev/null
+++ b/system-profiles/qemu-guest.nix
@@ -0,0 +1,10 @@
1{ ... }:
2{
3 config = {
4 boot.initrd = {
5 kernelModules = [ "virtio_net" "virtio_pci" "virtio_mmio" "virtio_blk" "virtio_scsi" "9p" "9pnet_virtio" "virtio_balloon" "virtio_console" "virtio_rng" ];
6 };
7
8 services.qemuGuest.enable = true;
9 };
10}
diff --git a/system-profiles/rebuild-machines/default.nix b/system-profiles/rebuild-machines/default.nix
new file mode 100644
index 00000000..e2a15aae
--- /dev/null
+++ b/system-profiles/rebuild-machines/default.nix
@@ -0,0 +1,111 @@
1{ config, pkgs, hostName, lib, ... }:
2
3with lib;
4
5let
6 cfg = config.system.rebuild-machine;
7
8 sshConfig = pkgs.writeText "config" ''
9 UserKnownHostsFile ${knownHostsFile}
10
11 Host ${cfg.repoHost}
12 User ${cfg.repoUser}
13 IdentityFile ${if isNull cfg.sopsConfig then cfg.repoPrivkey else config.sops.secrets."${cfg.sopsName}".path}
14 IdentitiesOnly yes
15 '';
16
17 knownHostsFile = pkgs.writeText "known_hosts" (concatMapStringsSep "\n" (kPath: cfg.repoHost + " " + readFile kPath) (attrValues cfg.repoPubkeys));
18
19 rebuildScript = pkgs.stdenv.mkDerivation {
20 name = "rebuild-${hostName}";
21
22 src = ./rebuild-machine.zsh;
23
24 buildInputs = with pkgs; [ makeWrapper ];
25
26 phases = [ "buildPhase" "installPhase" ];
27
28 inherit (pkgs) zsh coreutils openssh;
29 inherit (cfg) flake scriptName;
30 nixosRebuild = config.system.build.nixos-rebuild;
31 inherit (config.security) wrapperDir;
32 inherit sshConfig;
33
34 buildPhase = ''
35 substituteAll $src rebuild-machine.zsh
36 '';
37
38 installPhase = ''
39 mkdir -p $out/bin
40 install -m 0755 rebuild-machine.zsh $out/bin/${cfg.scriptName}
41 '';
42 };
43in {
44 options = {
45 system.rebuild-machine = {
46 scriptName = mkOption {
47 type = types.str;
48 default = "rebuild-${hostName}";
49 description = ''
50 Name of the script wrapping <literal>nixos-rebuild</literal>
51 '';
52 };
53
54 flake = mkOption {
55 type = types.nullOr types.str;
56 default = "git+ssh://${cfg.repoHost}/nixos?ref=flakes#${hostName}";
57 description = ''
58 The Flake URI of the NixOS configuration to build.
59 '';
60 };
61
62 repoHost = mkOption {
63 type = types.str;
64 default = "git.yggdrasil.li";
65 };
66
67 repoUser = mkOption {
68 type = types.str;
69 default = "gitolite";
70 };
71
72 repoPubkeys = mkOption {
73 type = types.attrsOf types.path;
74 default = genAttrs ["rsa" "ed25519"] (kType: ./ssh-pub + "/${cfg.repoHost}-${kType}.pub");
75 };
76
77 repoPrivkey = mkOption {
78 type = types.path;
79 default = ./ssh + "/${hostName}/private";
80 };
81
82 sopsName = mkOption {
83 type = types.nullOr types.str;
84 default = "rebuild-machines";
85 };
86
87 sopsConfig = mkOption {
88 type = types.nullOr types.attrs;
89 default = {
90 format = "binary";
91 };
92 };
93 };
94 };
95
96 config = {
97 assertions = [
98 { assertion = isNull cfg.sopsConfig || (!(isNull cfg.sopsName));
99 message = "If option sopsConfig is not null option sopsName may not be null";
100 }
101 ];
102
103 sops.secrets = lib.mkIf (!(isNull cfg.sopsConfig)) {
104 "${cfg.sopsName}" = {
105 sopsFile = cfg.repoPrivkey;
106 } // cfg.sopsConfig;
107 };
108
109 environment.systemPackages = [ rebuildScript ];
110 };
111}
diff --git a/system-profiles/rebuild-machines/rebuild-machine.zsh b/system-profiles/rebuild-machines/rebuild-machine.zsh
new file mode 100644
index 00000000..6420a417
--- /dev/null
+++ b/system-profiles/rebuild-machines/rebuild-machine.zsh
@@ -0,0 +1,10 @@
1#!@zsh@/bin/zsh -e
2
3if [[ $(@coreutils@/bin/whoami) != "root" ]]; then
4 exec @wrapperDir@/sudo -H -- @out@/bin/@scriptName@ $@
5fi
6
7export NIX_SSHOPTS="-F @sshConfig@"
8export GIT_SSH_COMMAND="@openssh@/bin/ssh -F @sshConfig@"
9export GIT_CONFIG_COUNT=0 GIT_CONFIG_KEY_0="init.defaultBranch" GIT_CONFIG_VALUE_0=main
10exec -- @nixosRebuild@/bin/nixos-rebuild --refresh --flake '@flake@' ${@:-switch}
diff --git a/system-profiles/rebuild-machines/ssh-pub/git.yggdrasil.li-ed25519.pub b/system-profiles/rebuild-machines/ssh-pub/git.yggdrasil.li-ed25519.pub
new file mode 100644
index 00000000..aaf4b012
--- /dev/null
+++ b/system-profiles/rebuild-machines/ssh-pub/git.yggdrasil.li-ed25519.pub
@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDeBBux2bIXnS/RUv+Y/NCpzI/SCW0KOJSzf48KDiEZD
diff --git a/system-profiles/rebuild-machines/ssh-pub/git.yggdrasil.li-rsa.pub b/system-profiles/rebuild-machines/ssh-pub/git.yggdrasil.li-rsa.pub
new file mode 100644
index 00000000..7748d3a1
--- /dev/null
+++ b/system-profiles/rebuild-machines/ssh-pub/git.yggdrasil.li-rsa.pub
@@ -0,0 +1 @@
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==
diff --git a/system-profiles/rebuild-machines/ssh/sif/private b/system-profiles/rebuild-machines/ssh/sif/private
new file mode 100644
index 00000000..ffac520a
--- /dev/null
+++ b/system-profiles/rebuild-machines/ssh/sif/private
@@ -0,0 +1,26 @@
1{
2 "data": "ENC[AES256_GCM,data:8AhUuD0/SiwXAv3nD+nzJPUtf67O9/wcyY1Iu/RCMzqKOs6iRTLlcvxyOtirUUtKRc7QECpX/TQ/K6PFo/0FKrNWn/aVCb0TnZFucL5UXsjO6TDnLWV7x+Huvcgj3RmEi4l24S/NBDuo7RtBBDByfaegavBRkx9NuFiYD16YVrQgvqL8owzXBTSmwfnlTNmRv2tsZzEbaaxRzgRf8USbks7bHWeEMOJ1Lx7svPm5GgARm+6N+wwXmsyDCXiRCOu54bHftUPh0lOKLw6Nggnn8vDgv1OJLemwtrMr7vZ0wZ8NWwYDfHr0FyYI/0isCpDXzFvtFd8XsXCOQxD29QM1C1CdSbavnBcc4UMoKoJzBeBHFlqqMSLrHDmFSKet+rr8nTu2ZKEyHq0C8T/h4sBXdrOF2G7GGEdohL/9sy9yrNgvvYRBv/85uieV3qr8OQ8dhmR4Cj1DucLROslLW9/QOp12Z8wcDRbA185Vh2SnYEF+K+EPYlEV3SBLLl3GxOT2cjUHefBEl727lgNdh2gYxIpMTYxTMum7cosv,iv:y97GnUb2wyIHP3Nj+/VkHbC3nwegrKnAipp33tjFLE4=,tag:u01az3q8edvpQ52k9SQL1w==,type:str]",
3 "sops": {
4 "kms": null,
5 "gcp_kms": null,
6 "azure_kv": null,
7 "hc_vault": null,
8 "age": null,
9 "lastmodified": "2021-06-06T15:05:54Z",
10 "mac": "ENC[AES256_GCM,data:Cf8WbqV4bqkg+W84hRSjMsrqzV7QZqAJeU/DrlN94NRaLDbayXK/kbxz9gMWY6Eyv3D70ulc75EBojZF1SXfk/WpDHpVJ4DEizb28oIfE4x88MmQ7ZJuskqXQaFa4MohJVQ/7ukr9bTjNMm7RFtq+yNKkIy6mj2YBk6BYsPgwic=,iv:kq+FpwQEWJo18QEEqG1uZ3uJ1MpklqN7Oaj0fPw8/0k=,tag:FYHLHjzeD+28KHD7x5JwGA==,type:str]",
11 "pgp": [
12 {
13 "created_at": "2021-06-06T15:05:54Z",
14 "enc": "-----BEGIN PGP MESSAGE-----\n\nhF4DXxoViZlp6dISAQdAO0QzeTgAkvdr+w43Yk9a0X1AmwQd1b1CFPNbINQbvSww\noa85a30JfMy9r2LRfTd9S8sd7rAfOaRCaPrJVWHQBXd0s36Ux8gSktcAM+PzYBCE\n0l4BkVI6bLaO756h5ru+gANRuqMRKgpV8PB3PMmIlhinUAZFsmNJb1T1O13JkMsM\nMuygJ8cg8LukjEeXM7jnWO52cX1NcoquhJK7f0eVvFMNW3Iexf9pI0XC0iSYW69B\n=lQQZ\n-----END PGP MESSAGE-----\n",
15 "fp": "30D3453B8CD02FE2A3E7C78C0FB536FB87AE8F51"
16 },
17 {
18 "created_at": "2021-06-06T15:05:54Z",
19 "enc": "-----BEGIN PGP MESSAGE-----\n\nhF4Dgwm4NZSaLAcSAQdANW63iv/Mn2irKYdSZxX7iwIPyDGmGDUDUnbWEgel/jcw\nLILXuiWOkgfG2G4tvqdM4AHkYoKEA1mAfH9ybFJMhiS12WI60or6Z8e0cd23mteo\n0l4BU7FiVt9p8/96qJlVuGUS3GRlhnczFN9GIBaj9BkzuifFbC+S4iphvO6u59m1\nGodFjFZ5ayfvgSRLb93DN7cGUfhcZ80oQHSiuJxFC7I0xnTcg/LKxYvX49yHE6/I\n=63VB\n-----END PGP MESSAGE-----\n",
20 "fp": "F1AF20B9511B63F681A14E8D51AEFBCD1DEF68F8"
21 }
22 ],
23 "unencrypted_suffix": "_unencrypted",
24 "version": "3.7.1"
25 }
26} \ No newline at end of file
diff --git a/system-profiles/rebuild-machines/ssh/sif/public b/system-profiles/rebuild-machines/ssh/sif/public
new file mode 100644
index 00000000..fa3eeefb
--- /dev/null
+++ b/system-profiles/rebuild-machines/ssh/sif/public
@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINj5I8YSEwS+44YDksxm5JgrmOvz+Jhzj2tWFUWT9z+M rebuild-machines@sif
diff --git a/system-profiles/rebuild-machines/ssh/surtr/private b/system-profiles/rebuild-machines/ssh/surtr/private
new file mode 100644
index 00000000..40651674
--- /dev/null
+++ b/system-profiles/rebuild-machines/ssh/surtr/private
@@ -0,0 +1,26 @@
1{
2 "data": "ENC[AES256_GCM,data:6iBi0nQMKJccWxpWkJcWIdpQ4qitMgewFs0LAUU7PFH268hoCkmiWgfq+ZJ1E4hfgAMqTmiS+FJUnYyAGaHHghPCMjHLaNaoS25GjQM3S0pUFd56zGipP0xkii4HaiEGEpcxmXuGHnfrJgJZIHSsl8KRaLRJUKmIl7uorN0nGo6A2R+DKljExXRpH/ZSZj5dCoTu8tVCwnKxgDh9rDhEKCYIl1XnTKV8U8KeT9M7KJt6VWA5mmYhziv0No2U8g22T1ZQP+ATIqGXzbDUDEjY+kg7q6Om0ZsWBL2mJ7U88V/BBMymeYrJiawxrjXzG1VtsnMC7pn8F5BgHCz8x3lUIi2/wlWdqKEUp+TuKEMeuGLdaAifdeFKG/qnFBtNQLxcScvilXAH4w2L+yjCnJbdKoGk1GdJl1U/a2lEXmzbwSQ7Gxlq2rSDpTRW91Gr6I/JXJS6vpq8XRv1faHDGGW+AuNArP9P85/d/S7Fv/UeRQ7vNi30ePG6LjTN+0ajhqwv2YEocArFj+1I8vY3RSQ0PVj27Cwcc582BBmhRZNccgI7AL8=,iv:IwcMztAFDpOE23dYEzjJiv6qhk9E0/Qb/xgwbtt9xt0=,tag:qYMBaoUtkUDR1taehr7Y/g==,type:str]",
3 "sops": {
4 "kms": null,
5 "gcp_kms": null,
6 "azure_kv": null,
7 "hc_vault": null,
8 "age": null,
9 "lastmodified": "2021-05-15T17:58:53Z",
10 "mac": "ENC[AES256_GCM,data:N/CM/+4b02tRBFqFioX/FRPPj4bG3QGltIg7KZk7BYrl+5rJ/6QKL1g+CqsLTteRAbHiluBNFMT/dUBSmiQ+So95sUTc+rICRNKmxCX5GFxw3Kr5/y4r9W/sw/NOSXQD4+dctkhKmzg9NFR+T4pLM8W4KErtV384Wy3ccAW/g8g=,iv:Rr4rDloQRRsLTErUNbB1OIKbi5qyh2gU1y55sU7ecTY=,tag:sYHPOKcAWNfjz26X+w4r3g==,type:str]",
11 "pgp": [
12 {
13 "created_at": "2021-05-15T17:58:52Z",
14 "enc": "-----BEGIN PGP MESSAGE-----\n\nhF4DXxoViZlp6dISAQdAJOYE8FC5GREn7xoQfuSMvow0GwajGfi4bw+FEydrDhAw\n+F8ryseAyQPgVouzlO2aItBy20dYYNs6zkcfnuZemDdBSpQQmahtXBs5Dt3wGhvg\n0l4BPJeJ3cpuLDQMFnNfTOLJRdoR0kvxVHJBBYJ+Jn4ArPrpiMReJvyLl7i83wDb\nsb+WCcu83IFLM/oInb22cto3shATTLgr30hq65+RwAXlGBNmoAT0HH9MDsgq+VQw\n=nsV9\n-----END PGP MESSAGE-----\n",
15 "fp": "30D3453B8CD02FE2A3E7C78C0FB536FB87AE8F51"
16 },
17 {
18 "created_at": "2021-05-15T17:58:52Z",
19 "enc": "-----BEGIN PGP MESSAGE-----\n\nhF4DyFKFNkTVG5oSAQdA12ftTan1dZSX50t5H1/LdTse+nhePZS6RxqV7WcRi04w\nyiqJt+C6AFBZl4esCqHQjpPnmkb5pvI2/P9e8bvK8uszIF35KC+r55LAaB2RXkr2\n0l4BX0fPwE6XNtiBn2hQo7KYnci6s25itij+uppRyu6Cnc3Hi4Emro4MFBBJlot8\no773ulk8jmOeR2k9fLDSMQ0EO+3zZbm7zz/fK46SyFzBIAPvCx0fEpXi0ZdLES2k\n=rULf\n-----END PGP MESSAGE-----\n",
20 "fp": "7ED22F4AA7BB55728B643DC5471B7D88E4EF66F8"
21 }
22 ],
23 "unencrypted_suffix": "_unencrypted",
24 "version": "3.7.1"
25 }
26} \ No newline at end of file
diff --git a/system-profiles/rebuild-machines/ssh/surtr/public b/system-profiles/rebuild-machines/ssh/surtr/public
new file mode 100644
index 00000000..323e8398
--- /dev/null
+++ b/system-profiles/rebuild-machines/ssh/surtr/public
@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM5HJRwdwtmIqx8HRK0AKIq+vSCHvGv98rOmraSGwnTL rebuild-machines@surtr
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/core.nix b/user-profiles/core.nix
index 8611a0bd..fb80343d 100644
--- a/user-profiles/core.nix
+++ b/user-profiles/core.nix
@@ -7,6 +7,7 @@
7 7
8 config = { 8 config = {
9 manual.manpages.enable = true; 9 manual.manpages.enable = true;
10 home.stateVersion = "20.09";
10 }; 11 };
11 }; 12 };
12} 13}
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..6b0ea076
--- /dev/null
+++ b/user-profiles/mpv/default.nix
@@ -0,0 +1,85 @@
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 = "744c3ee6";
42 pname = "autosave";
43 name = "${pname}-${version}.lua";
44
45 src = pkgs.fetchzip {
46 url = "https://gist.github.com/CyberShadow/2f71a97fb85ed42146f6d9f522bc34ef/archive/744c3ee61d2f0a8e9bb4e308dec6897215ae4704.zip";
47 hash = "sha256-yxA8wgzdS7SyKLoNTWN87ShsBfPKUflbOu4Y0jS2G3I=";
48 # url = "https://gist.github.com/Hakkin/5489e511bd6c8068a0fc09304c9c5a82/archive/7a19f7cdb6dd0b1c6878b41e13b244e2503c15fc.zip";
49 # sha256 = "0bv9wjrqm2ragd7rp8vw768bja2ghascwlljd6rzzf2ybi10fxs2";
50 };
51
52 installPhase = ''
53 install -d $out/share/mpv/scripts
54 install -m 0644 autosave.lua $out/share/mpv/scripts/${passthru.scriptName}
55 '';
56
57 passthru.scriptName = "autosave.lua";
58 };
59 mpris = pkgs.stdenv.mkDerivation rec {
60 version = "0.4";
61 pname = "mpv-mpris";
62 name = "${pname}-${version}.so";
63
64 src = pkgs.fetchFromGitHub {
65 owner = "hoyon";
66 repo = "mpv-mpris";
67 rev = version;
68 sha256 = "1fr3jvja8s2gdpx8qyk9r17977flms3qpm8zci62nd9r5wjdvr5i";
69 };
70
71 installPhase = ''
72 install -d $out/share/mpv/scripts
73 install -m 0644 mpris.so $out/share/mpv/scripts/${passthru.scriptName}
74 '';
75
76 nativeBuildInputs = with pkgs; [ pkgconfig glib mpv ];
77
78 passthru.scriptName = "mpris.so";
79 };
80 in [ reload
81 autosave
82 mpris
83 ];
84 };
85}
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..f9a3e11f
--- /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 || ''${TERM} =~ alacritty ]]" '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..5407276a
--- /dev/null
+++ b/user-profiles/utils.nix
@@ -0,0 +1,25 @@
1{ userName, pkgs, ... }:
2{
3 home-manager.users.${userName} = {
4 programs = {
5 htop = {
6 enable = true;
7 settings = {
8 delay = 5;
9 highlightBaseName = true;
10 treeView = true;
11 };
12 };
13
14 jq.enable = true;
15 };
16
17 home.packages = with pkgs; [
18 autossh usbutils pciutils exa ag pwgen unzip magic-wormhole
19 qrencode tty-clock dnsutils openssl sshfs psmisc mosh tree
20 vnstat file pv bc fast-cli zip nmap aspell aspellDicts.de
21 aspellDicts.en borgbackup man-pages rsync socat telnet yq
22 cached-nix-shell persistent-nix-shell rage
23 ];
24 };
25}
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..7cf00b89
--- /dev/null
+++ b/users/gkleen/default.nix
@@ -0,0 +1,46 @@
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 createHome = true;
11 home = "/home/${userName}";
12 shell = "${pkgs.zsh}/bin/zsh";
13 isNormalUser = true;
14 openssh.authorizedKeys.keyFiles = let dir = ./authorized-keys; in lib.mapAttrsToList (n: _: dir + "/${n}") (builtins.readDir dir);
15 hashedPassword = "$6$rounds=500000$dOMgCU7DAk$yQFYGOURTEt12387LIYBnFKSWmtwXMUk1LJWnV0m7OFt.y2TnxQn2abdGA5dhwG9EmMB5wZGXf4J5F71c746C/";
16 };
17
18 home-manager.users.${userName} = {
19 programs = {
20 git = {
21 enable = true;
22 userEmail = "gkleen@yggdrasil.li";
23 userName = "Gregor Kleen";
24 delta.enable = true;
25 extraConfig = {
26 pull.rebase = false;
27 submodule.recurse = true;
28 };
29 };
30
31 ssh = {
32 enable = true;
33 controlMaster = "auto";
34 controlPersist = "30m";
35 serverAliveInterval = 6;
36 serverAliveCountMax = 10;
37 hashKnownHosts = true;
38 extraConfig = ''
39 IdentitiesOnly true
40 '';
41 };
42
43 gpg.enable = true;
44 };
45 };
46}
diff --git a/users/mherold.nix b/users/mherold.nix
new file mode 100644
index 00000000..54b11aab
--- /dev/null
+++ b/users/mherold.nix
@@ -0,0 +1,9 @@
1{ userName, ... }:
2{
3 users.users.${userName} = {
4 description = "Magdalena Kleen";
5 extraGroups = ["xmpp" "mail"];
6 hashedPassword = "$6$rounds=500000$y5qNae9r/U/7$HbSrmPcrPl9OQvRFMeo8PDYar32Y1i/C1R5di82rN4PPQZYxg/W.anHSI5Xws6fOQmDtvGsT0lCe4NFNxuTF41";
7 isNormalUser = true;
8 };
9}
diff --git a/users/mkleen.nix b/users/mkleen.nix
new file mode 100644
index 00000000..09e1e618
--- /dev/null
+++ b/users/mkleen.nix
@@ -0,0 +1,9 @@
1{ userName, ... }:
2{
3 users.users.${userName} = {
4 description = "Martin Kleen";
5 extraGroups = ["xmpp"];
6 hashedPassword = "$6$rounds=500000$bBm24zrmoktFD$kGLJA0I9q.jPgLSFej/1aqIyBo/KotXhA0pflByzF5LG/Vw9Vt0yfDvVYEHR6rTplJWYotdYaJXY3b4wnh9n.0";
7 isNormalUser = true;
8 };
9}
diff --git a/users/mwagner.nix b/users/mwagner.nix
new file mode 100644
index 00000000..258b4c32
--- /dev/null
+++ b/users/mwagner.nix
@@ -0,0 +1,9 @@
1{ userName, ... }:
2{
3 users.users.${userName} = {
4 description = "Max Wagner";
5 extraGroups = ["xmpp" "mail"];
6 hashedPassword = "$6$rounds=500000$5l71DQxKPXJ$UDUyiQur9QcmsXrV5r0gD5AWWpsJLe37bhV5NhZMjN4yh5aZjE4uqm8AXemuYTx3kKi.SWaRTglLIVvgDYf5l1";
7 isNormalUser = true;
8 };
9}
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}
diff --git a/users/some.nix b/users/some.nix
new file mode 100644
index 00000000..02742e86
--- /dev/null
+++ b/users/some.nix
@@ -0,0 +1,9 @@
1{ userName, ... }:
2{
3 users.users.${userName} = {
4 description = "SomeNights";
5 extraGroups = ["xmpp" "mail"];
6 hashedPassword = "$6$rounds=500000$ZOKcPFUxFCCxbS$PSjgCpHs5GfmmusjTVEBY89NFS.hvY21.iuscfiXW8R.B2UW6ScyrIWWWPJkL4ZfI.6pKwXuf01gxazmDjy251";
7 isNormalUser = true;
8 };
9}
diff --git a/users/tkleen.nix b/users/tkleen.nix
new file mode 100644
index 00000000..debe2138
--- /dev/null
+++ b/users/tkleen.nix
@@ -0,0 +1,9 @@
1{ userName, ... }:
2{
3 users.users.${userName} = {
4 description = "Tatjana Kleen";
5 extraGroups = ["xmpp"];
6 hashedPassword = "$6$rounds=500000$K/MclfEm/LEplrSt$jKakA8dwOggI7ZB5ZwnCHVjvTGw6/VJZGVIlQGmuLoDvz24.pCnrl29xeUGItFhy6vpbjeb9GoG76RwdgI33H/";
7 isNormalUser = true;
8 };
9}
diff --git a/users/vkleen.nix b/users/vkleen.nix
new file mode 100644
index 00000000..e9717816
--- /dev/null
+++ b/users/vkleen.nix
@@ -0,0 +1,9 @@
1{ userName, ... }:
2{
3 users.users.${userName} = {
4 description = "vkleen";
5 extraGroups = ["xmpp"];
6 hashedPassword = "$6$rounds=500000$wOgtkxOshMT$3wT9nRqSCZRr9MkZcDQjMM3YbAekqCxEF0TwYcmjLqUk8Z44jeWQS96H/r3SKCfKWWcIWcQNp/dAxQ/QlcKAv.";
7 isNormalUser = true;
8 };
9}