aboutsummaryrefslogtreecommitdiff
path: root/infra/libkookie
diff options
context:
space:
mode:
authorMx Kookie <kookie@spacekookie.de>2020-12-21 07:10:40 +0100
committerMx Kookie <kookie@spacekookie.de>2020-12-21 07:10:40 +0100
commit3f3e44beca1a6431c54372294c87ea226d2ea9e7 (patch)
treeffefe6460c8c233026f0f849bf762690f0146271 /infra/libkookie
parent53e4aeed9ab362fb286bd814307773dbaf08b16e (diff)
parent2b1892e646f2e48591c53b3293622720e1a3bdca (diff)
Add 'infra/libkookie/home-manager/' from commit '2b1892e646f2e48591c53b3293622720e1a3bdca'
git-subtree-dir: infra/libkookie/home-manager git-subtree-mainline: 53e4aeed9ab362fb286bd814307773dbaf08b16e git-subtree-split: 2b1892e646f2e48591c53b3293622720e1a3bdca
Diffstat (limited to 'infra/libkookie')
-rw-r--r--infra/libkookie/home-manager/.github/CODEOWNERS242
-rw-r--r--infra/libkookie/home-manager/.github/ISSUE_TEMPLATE.md38
-rw-r--r--infra/libkookie/home-manager/.github/PULL_REQUEST_TEMPLATE.md44
-rw-r--r--infra/libkookie/home-manager/.github/workflows/github_pages.yml28
-rw-r--r--infra/libkookie/home-manager/.github/workflows/test.yml23
-rw-r--r--infra/libkookie/home-manager/.gitignore2
-rw-r--r--infra/libkookie/home-manager/.gitlab-ci.yml43
l---------infra/libkookie/home-manager/CONTRIBUTING.adoc1
l---------infra/libkookie/home-manager/FAQ.adoc1
-rw-r--r--infra/libkookie/home-manager/LICENSE21
-rw-r--r--infra/libkookie/home-manager/README.md372
-rw-r--r--infra/libkookie/home-manager/default.nix18
-rw-r--r--infra/libkookie/home-manager/doc/contributing.adoc255
-rw-r--r--infra/libkookie/home-manager/doc/default.nix69
-rw-r--r--infra/libkookie/home-manager/doc/faq.adoc171
-rw-r--r--infra/libkookie/home-manager/doc/installation.adoc257
-rw-r--r--infra/libkookie/home-manager/doc/man-configuration.xml40
-rw-r--r--infra/libkookie/home-manager/doc/man-home-manager.xml539
-rw-r--r--infra/libkookie/home-manager/doc/man-pages.xml12
-rw-r--r--infra/libkookie/home-manager/doc/manual.xml46
-rw-r--r--infra/libkookie/home-manager/doc/release-notes/release-notes.adoc21
-rw-r--r--infra/libkookie/home-manager/doc/release-notes/rl-1809.adoc4
-rw-r--r--infra/libkookie/home-manager/doc/release-notes/rl-1903.adoc59
-rw-r--r--infra/libkookie/home-manager/doc/release-notes/rl-1909.adoc31
-rw-r--r--infra/libkookie/home-manager/doc/release-notes/rl-2003.adoc126
-rw-r--r--infra/libkookie/home-manager/doc/release-notes/rl-2009.adoc96
-rw-r--r--infra/libkookie/home-manager/doc/release-notes/rl-2103.adoc42
-rw-r--r--infra/libkookie/home-manager/doc/writing-modules.adoc187
-rw-r--r--infra/libkookie/home-manager/flake.nix24
-rwxr-xr-xinfra/libkookie/home-manager/format59
-rw-r--r--infra/libkookie/home-manager/home-manager/completion.bash357
-rw-r--r--infra/libkookie/home-manager/home-manager/default.nix40
-rw-r--r--infra/libkookie/home-manager/home-manager/home-manager583
-rw-r--r--infra/libkookie/home-manager/home-manager/home-manager.nix88
-rw-r--r--infra/libkookie/home-manager/home-manager/install.nix84
-rw-r--r--infra/libkookie/home-manager/modules/accounts/email.nix396
-rw-r--r--infra/libkookie/home-manager/modules/default.nix62
-rw-r--r--infra/libkookie/home-manager/modules/files.nix331
-rw-r--r--infra/libkookie/home-manager/modules/home-environment.nix577
-rwxr-xr-xinfra/libkookie/home-manager/modules/lib-bash/activation-init.sh85
-rw-r--r--infra/libkookie/home-manager/modules/lib-bash/color-echo.sh37
-rw-r--r--infra/libkookie/home-manager/modules/lib/dag.nix117
-rw-r--r--infra/libkookie/home-manager/modules/lib/default.nix26
-rw-r--r--infra/libkookie/home-manager/modules/lib/file-type.nix112
-rw-r--r--infra/libkookie/home-manager/modules/lib/gvariant.nix156
-rw-r--r--infra/libkookie/home-manager/modules/lib/maintainers.nix50
-rw-r--r--infra/libkookie/home-manager/modules/lib/shell.nix11
-rw-r--r--infra/libkookie/home-manager/modules/lib/stdlib-extended.nix7
-rw-r--r--infra/libkookie/home-manager/modules/lib/strings.nix22
-rw-r--r--infra/libkookie/home-manager/modules/lib/types-dag.nix99
-rw-r--r--infra/libkookie/home-manager/modules/lib/types.nix90
-rw-r--r--infra/libkookie/home-manager/modules/lib/zsh.nix30
-rw-r--r--infra/libkookie/home-manager/modules/manual.nix68
-rw-r--r--infra/libkookie/home-manager/modules/misc/dconf.nix73
-rw-r--r--infra/libkookie/home-manager/modules/misc/debug.nix26
-rw-r--r--infra/libkookie/home-manager/modules/misc/fontconfig.nix105
-rw-r--r--infra/libkookie/home-manager/modules/misc/gtk.nix164
-rw-r--r--infra/libkookie/home-manager/modules/misc/lib.nix14
-rw-r--r--infra/libkookie/home-manager/modules/misc/news.nix1787
-rw-r--r--infra/libkookie/home-manager/modules/misc/nixpkgs.nix152
-rw-r--r--infra/libkookie/home-manager/modules/misc/numlock.nix31
-rw-r--r--infra/libkookie/home-manager/modules/misc/pam.nix32
-rw-r--r--infra/libkookie/home-manager/modules/misc/qt.nix68
-rw-r--r--infra/libkookie/home-manager/modules/misc/submodule-support.nix32
-rw-r--r--infra/libkookie/home-manager/modules/misc/tmpfiles.nix49
-rw-r--r--infra/libkookie/home-manager/modules/misc/version.nix24
-rw-r--r--infra/libkookie/home-manager/modules/misc/vte.nix51
-rw-r--r--infra/libkookie/home-manager/modules/misc/xdg-mime-apps.nix88
-rw-r--r--infra/libkookie/home-manager/modules/misc/xdg-mime.nix55
-rw-r--r--infra/libkookie/home-manager/modules/misc/xdg-user-dirs.nix110
-rw-r--r--infra/libkookie/home-manager/modules/misc/xdg.nix114
-rw-r--r--infra/libkookie/home-manager/modules/modules.nix232
-rw-r--r--infra/libkookie/home-manager/modules/programs/abook.nix40
-rw-r--r--infra/libkookie/home-manager/modules/programs/afew.nix52
-rw-r--r--infra/libkookie/home-manager/modules/programs/alacritty.nix63
-rw-r--r--infra/libkookie/home-manager/modules/programs/alot-accounts.nix58
-rw-r--r--infra/libkookie/home-manager/modules/programs/alot.nix237
-rw-r--r--infra/libkookie/home-manager/modules/programs/aria2.nix61
-rw-r--r--infra/libkookie/home-manager/modules/programs/astroid-accounts.nix32
-rw-r--r--infra/libkookie/home-manager/modules/programs/astroid-config-template.json113
-rw-r--r--infra/libkookie/home-manager/modules/programs/astroid.nix127
-rw-r--r--infra/libkookie/home-manager/modules/programs/autojump.nix56
-rw-r--r--infra/libkookie/home-manager/modules/programs/autorandr.nix364
-rw-r--r--infra/libkookie/home-manager/modules/programs/bash.nix220
-rw-r--r--infra/libkookie/home-manager/modules/programs/bat.nix58
-rw-r--r--infra/libkookie/home-manager/modules/programs/beets.nix60
-rw-r--r--infra/libkookie/home-manager/modules/programs/broot.nix274
-rw-r--r--infra/libkookie/home-manager/modules/programs/browserpass.nix76
-rw-r--r--infra/libkookie/home-manager/modules/programs/chromium.nix95
-rw-r--r--infra/libkookie/home-manager/modules/programs/command-not-found/command-not-found.nix59
-rw-r--r--infra/libkookie/home-manager/modules/programs/command-not-found/command-not-found.pl44
-rw-r--r--infra/libkookie/home-manager/modules/programs/dircolors.nix223
-rw-r--r--infra/libkookie/home-manager/modules/programs/direnv.nix100
-rw-r--r--infra/libkookie/home-manager/modules/programs/eclipse.nix60
-rw-r--r--infra/libkookie/home-manager/modules/programs/emacs.nix73
-rw-r--r--infra/libkookie/home-manager/modules/programs/feh.nix81
-rw-r--r--infra/libkookie/home-manager/modules/programs/firefox.nix331
-rw-r--r--infra/libkookie/home-manager/modules/programs/fish.nix460
-rw-r--r--infra/libkookie/home-manager/modules/programs/fzf.nix146
-rw-r--r--infra/libkookie/home-manager/modules/programs/getmail-accounts.nix49
-rw-r--r--infra/libkookie/home-manager/modules/programs/getmail.nix63
-rw-r--r--infra/libkookie/home-manager/modules/programs/gh.nix53
-rw-r--r--infra/libkookie/home-manager/modules/programs/git.nix360
-rw-r--r--infra/libkookie/home-manager/modules/programs/gnome-terminal.nix332
-rw-r--r--infra/libkookie/home-manager/modules/programs/go.nix105
-rw-r--r--infra/libkookie/home-manager/modules/programs/gpg.nix61
-rw-r--r--infra/libkookie/home-manager/modules/programs/home-manager.nix37
-rw-r--r--infra/libkookie/home-manager/modules/programs/htop.nix416
-rw-r--r--infra/libkookie/home-manager/modules/programs/i3status-rust.nix265
-rw-r--r--infra/libkookie/home-manager/modules/programs/i3status.nix208
-rw-r--r--infra/libkookie/home-manager/modules/programs/info.nix63
-rw-r--r--infra/libkookie/home-manager/modules/programs/irssi.nix211
-rw-r--r--infra/libkookie/home-manager/modules/programs/jq.nix76
-rw-r--r--infra/libkookie/home-manager/modules/programs/kakoune.nix659
-rw-r--r--infra/libkookie/home-manager/modules/programs/keychain.nix115
-rw-r--r--infra/libkookie/home-manager/modules/programs/kitty.nix91
-rw-r--r--infra/libkookie/home-manager/modules/programs/lesspipe.nix19
-rw-r--r--infra/libkookie/home-manager/modules/programs/lf.nix219
-rw-r--r--infra/libkookie/home-manager/modules/programs/lieer-accounts.nix69
-rw-r--r--infra/libkookie/home-manager/modules/programs/lieer.nix93
-rw-r--r--infra/libkookie/home-manager/modules/programs/lsd.nix41
-rw-r--r--infra/libkookie/home-manager/modules/programs/man.nix72
-rw-r--r--infra/libkookie/home-manager/modules/programs/matplotlib.nix59
-rw-r--r--infra/libkookie/home-manager/modules/programs/mbsync-accounts.nix226
-rw-r--r--infra/libkookie/home-manager/modules/programs/mbsync.nix252
-rw-r--r--infra/libkookie/home-manager/modules/programs/mcfly.nix79
-rw-r--r--infra/libkookie/home-manager/modules/programs/mercurial.nix103
-rw-r--r--infra/libkookie/home-manager/modules/programs/mpv.nix158
-rw-r--r--infra/libkookie/home-manager/modules/programs/msmtp-accounts.nix48
-rw-r--r--infra/libkookie/home-manager/modules/programs/msmtp.nix73
-rw-r--r--infra/libkookie/home-manager/modules/programs/mu.nix57
-rw-r--r--infra/libkookie/home-manager/modules/programs/ncmpcpp.nix135
-rw-r--r--infra/libkookie/home-manager/modules/programs/ne.nix95
-rw-r--r--infra/libkookie/home-manager/modules/programs/neomutt-accounts.nix36
-rw-r--r--infra/libkookie/home-manager/modules/programs/neomutt.nix312
-rw-r--r--infra/libkookie/home-manager/modules/programs/neovim.nix266
-rw-r--r--infra/libkookie/home-manager/modules/programs/newsboat.nix123
-rw-r--r--infra/libkookie/home-manager/modules/programs/noti.nix50
-rw-r--r--infra/libkookie/home-manager/modules/programs/notmuch.nix198
-rw-r--r--infra/libkookie/home-manager/modules/programs/nushell.nix68
-rw-r--r--infra/libkookie/home-manager/modules/programs/obs-studio.nix47
-rw-r--r--infra/libkookie/home-manager/modules/programs/offlineimap-accounts.nix51
-rw-r--r--infra/libkookie/home-manager/modules/programs/offlineimap.nix178
-rw-r--r--infra/libkookie/home-manager/modules/programs/opam.nix50
-rw-r--r--infra/libkookie/home-manager/modules/programs/password-store.nix62
-rw-r--r--infra/libkookie/home-manager/modules/programs/pazi.nix55
-rw-r--r--infra/libkookie/home-manager/modules/programs/pet.nix88
-rw-r--r--infra/libkookie/home-manager/modules/programs/pidgin.nix34
-rw-r--r--infra/libkookie/home-manager/modules/programs/powerline-go.nix144
-rw-r--r--infra/libkookie/home-manager/modules/programs/qutebrowser.nix268
-rw-r--r--infra/libkookie/home-manager/modules/programs/readline.nix77
-rw-r--r--infra/libkookie/home-manager/modules/programs/rofi-pass.nix46
-rw-r--r--infra/libkookie/home-manager/modules/programs/rofi.nix338
-rw-r--r--infra/libkookie/home-manager/modules/programs/rtorrent.nix34
-rw-r--r--infra/libkookie/home-manager/modules/programs/skim.nix136
-rw-r--r--infra/libkookie/home-manager/modules/programs/ssh.nix492
-rw-r--r--infra/libkookie/home-manager/modules/programs/starship.nix109
-rw-r--r--infra/libkookie/home-manager/modules/programs/taskwarrior.nix112
-rw-r--r--infra/libkookie/home-manager/modules/programs/termite.nix387
-rw-r--r--infra/libkookie/home-manager/modules/programs/texlive.nix46
-rw-r--r--infra/libkookie/home-manager/modules/programs/tmux.nix344
-rw-r--r--infra/libkookie/home-manager/modules/programs/urxvt.nix156
-rw-r--r--infra/libkookie/home-manager/modules/programs/vim.nix171
-rw-r--r--infra/libkookie/home-manager/modules/programs/vscode.nix137
-rw-r--r--infra/libkookie/home-manager/modules/programs/vscode/haskell.nix62
-rw-r--r--infra/libkookie/home-manager/modules/programs/waybar.nix373
-rw-r--r--infra/libkookie/home-manager/modules/programs/z-lua.nix90
-rw-r--r--infra/libkookie/home-manager/modules/programs/zathura.nix65
-rw-r--r--infra/libkookie/home-manager/modules/programs/zoxide.nix79
-rw-r--r--infra/libkookie/home-manager/modules/programs/zplug.nix60
-rw-r--r--infra/libkookie/home-manager/modules/programs/zsh.nix541
-rw-r--r--infra/libkookie/home-manager/modules/programs/zsh/prezto.nix543
-rw-r--r--infra/libkookie/home-manager/modules/services/blueman-applet.nix36
-rw-r--r--infra/libkookie/home-manager/modules/services/caffeine.nix33
-rw-r--r--infra/libkookie/home-manager/modules/services/cbatticon.nix118
-rw-r--r--infra/libkookie/home-manager/modules/services/clipmenu.nix43
-rw-r--r--infra/libkookie/home-manager/modules/services/compton.nix43
-rw-r--r--infra/libkookie/home-manager/modules/services/dropbox.nix77
-rw-r--r--infra/libkookie/home-manager/modules/services/dunst.nix162
-rw-r--r--infra/libkookie/home-manager/modules/services/dwm-status.nix67
-rw-r--r--infra/libkookie/home-manager/modules/services/emacs.nix147
-rw-r--r--infra/libkookie/home-manager/modules/services/flameshot.nix39
-rw-r--r--infra/libkookie/home-manager/modules/services/fluidsynth.nix57
-rw-r--r--infra/libkookie/home-manager/modules/services/gammastep.nix166
-rw-r--r--infra/libkookie/home-manager/modules/services/getmail.nix55
-rw-r--r--infra/libkookie/home-manager/modules/services/gnome-keyring.nix46
-rw-r--r--infra/libkookie/home-manager/modules/services/gpg-agent.nix281
-rw-r--r--infra/libkookie/home-manager/modules/services/grobi.nix97
-rw-r--r--infra/libkookie/home-manager/modules/services/hound.nix76
-rw-r--r--infra/libkookie/home-manager/modules/services/imapnotify-accounts.nix33
-rw-r--r--infra/libkookie/home-manager/modules/services/imapnotify.nix90
-rw-r--r--infra/libkookie/home-manager/modules/services/kanshi.nix194
-rw-r--r--infra/libkookie/home-manager/modules/services/kbfs.nix67
-rw-r--r--infra/libkookie/home-manager/modules/services/kdeconnect.nix72
-rw-r--r--infra/libkookie/home-manager/modules/services/keepassx.nix27
-rw-r--r--infra/libkookie/home-manager/modules/services/keybase.nix37
-rw-r--r--infra/libkookie/home-manager/modules/services/keynav.nix29
-rw-r--r--infra/libkookie/home-manager/modules/services/lieer-accounts.nix25
-rw-r--r--infra/libkookie/home-manager/modules/services/lieer.nix66
-rw-r--r--infra/libkookie/home-manager/modules/services/lorri.nix60
-rw-r--r--infra/libkookie/home-manager/modules/services/mako.nix316
-rw-r--r--infra/libkookie/home-manager/modules/services/mbsync.nix104
-rw-r--r--infra/libkookie/home-manager/modules/services/mpd.nix183
-rw-r--r--infra/libkookie/home-manager/modules/services/mpdris2.nix101
-rw-r--r--infra/libkookie/home-manager/modules/services/muchsync.nix203
-rw-r--r--infra/libkookie/home-manager/modules/services/network-manager-applet.nix36
-rw-r--r--infra/libkookie/home-manager/modules/services/nextcloud-client.nix26
-rw-r--r--infra/libkookie/home-manager/modules/services/owncloud-client.nix26
-rw-r--r--infra/libkookie/home-manager/modules/services/parcellite.nix42
-rw-r--r--infra/libkookie/home-manager/modules/services/password-store-sync.nix71
-rw-r--r--infra/libkookie/home-manager/modules/services/pasystray.nix30
-rw-r--r--infra/libkookie/home-manager/modules/services/pbgopy.nix40
-rw-r--r--infra/libkookie/home-manager/modules/services/picom.nix311
-rw-r--r--infra/libkookie/home-manager/modules/services/polybar.nix135
-rw-r--r--infra/libkookie/home-manager/modules/services/pulseeffects.nix55
-rw-r--r--infra/libkookie/home-manager/modules/services/random-background.nix97
-rw-r--r--infra/libkookie/home-manager/modules/services/redshift.nix164
-rw-r--r--infra/libkookie/home-manager/modules/services/rsibreak.nix32
-rw-r--r--infra/libkookie/home-manager/modules/services/screen-locker.nix92
-rw-r--r--infra/libkookie/home-manager/modules/services/spotifyd.nix64
-rw-r--r--infra/libkookie/home-manager/modules/services/stalonetray.nix90
-rw-r--r--infra/libkookie/home-manager/modules/services/status-notifier-watcher.nix51
-rw-r--r--infra/libkookie/home-manager/modules/services/sxhkd.nix86
-rw-r--r--infra/libkookie/home-manager/modules/services/syncthing.nix70
-rw-r--r--infra/libkookie/home-manager/modules/services/taffybar.nix44
-rw-r--r--infra/libkookie/home-manager/modules/services/tahoe-lafs.nix19
-rw-r--r--infra/libkookie/home-manager/modules/services/taskwarrior-sync.nix50
-rw-r--r--infra/libkookie/home-manager/modules/services/udiskie.nix89
-rw-r--r--infra/libkookie/home-manager/modules/services/unclutter.nix61
-rw-r--r--infra/libkookie/home-manager/modules/services/unison.nix121
-rw-r--r--infra/libkookie/home-manager/modules/services/window-managers/awesome.nix52
-rw-r--r--infra/libkookie/home-manager/modules/services/window-managers/bspwm/default.nix74
-rw-r--r--infra/libkookie/home-manager/modules/services/window-managers/bspwm/options.nix214
-rw-r--r--infra/libkookie/home-manager/modules/services/window-managers/i3-sway/i3.nix272
-rw-r--r--infra/libkookie/home-manager/modules/services/window-managers/i3-sway/lib/functions.nix127
-rw-r--r--infra/libkookie/home-manager/modules/services/window-managers/i3-sway/lib/options.nix772
-rw-r--r--infra/libkookie/home-manager/modules/services/window-managers/i3-sway/sway.nix416
-rw-r--r--infra/libkookie/home-manager/modules/services/window-managers/xmonad.nix101
-rw-r--r--infra/libkookie/home-manager/modules/services/wlsunset.nix97
-rw-r--r--infra/libkookie/home-manager/modules/services/xcape.nix76
-rw-r--r--infra/libkookie/home-manager/modules/services/xembed-sni-proxy.nix45
-rw-r--r--infra/libkookie/home-manager/modules/services/xscreensaver.nix56
-rw-r--r--infra/libkookie/home-manager/modules/services/xsuspender.nix195
-rw-r--r--infra/libkookie/home-manager/modules/systemd-activate.rb216
-rw-r--r--infra/libkookie/home-manager/modules/systemd-activate.sh114
-rw-r--r--infra/libkookie/home-manager/modules/systemd.nix338
-rw-r--r--infra/libkookie/home-manager/modules/targets/darwin.nix16
-rw-r--r--infra/libkookie/home-manager/modules/targets/generic-linux.nix55
-rw-r--r--infra/libkookie/home-manager/modules/xcursor.nix83
-rw-r--r--infra/libkookie/home-manager/modules/xresources.nix92
-rw-r--r--infra/libkookie/home-manager/modules/xsession.nix168
-rw-r--r--infra/libkookie/home-manager/nix-darwin/default.nix107
-rw-r--r--infra/libkookie/home-manager/nixos/default.nix124
-rw-r--r--infra/libkookie/home-manager/overlay.nix3
-rw-r--r--infra/libkookie/home-manager/tests/default.nix108
-rw-r--r--infra/libkookie/home-manager/tests/lib/types/dag-merge-result.txt3
-rw-r--r--infra/libkookie/home-manager/tests/lib/types/dag-merge.nix32
-rw-r--r--infra/libkookie/home-manager/tests/lib/types/dag-submodule.nix43
-rw-r--r--infra/libkookie/home-manager/tests/lib/types/default.nix7
-rw-r--r--infra/libkookie/home-manager/tests/lib/types/gvariant-merge.nix62
-rw-r--r--infra/libkookie/home-manager/tests/lib/types/list-or-dag-merge-result.txt15
-rw-r--r--infra/libkookie/home-manager/tests/lib/types/list-or-dag-merge.nix34
-rw-r--r--infra/libkookie/home-manager/tests/modules/accounts/email-test-accounts.nix28
-rw-r--r--infra/libkookie/home-manager/tests/modules/files/.hidden1
-rw-r--r--infra/libkookie/home-manager/tests/modules/files/default.nix8
-rw-r--r--infra/libkookie/home-manager/tests/modules/files/executable.nix17
-rw-r--r--infra/libkookie/home-manager/tests/modules/files/hidden-source.nix19
-rw-r--r--infra/libkookie/home-manager/tests/modules/files/out-of-store-symlink.nix29
-rw-r--r--infra/libkookie/home-manager/tests/modules/files/source with spaces!1
-rw-r--r--infra/libkookie/home-manager/tests/modules/files/source-with-spaces.nix20
-rw-r--r--infra/libkookie/home-manager/tests/modules/files/target-with-shellvar.nix15
-rw-r--r--infra/libkookie/home-manager/tests/modules/files/text-expected.txt2
-rw-r--r--infra/libkookie/home-manager/tests/modules/files/text.nix18
-rw-r--r--infra/libkookie/home-manager/tests/modules/home-environment/default.nix4
-rw-r--r--infra/libkookie/home-manager/tests/modules/home-environment/session-path.nix27
-rw-r--r--infra/libkookie/home-manager/tests/modules/home-environment/session-variables-expected.txt9
-rw-r--r--infra/libkookie/home-manager/tests/modules/home-environment/session-variables.nix19
-rw-r--r--infra/libkookie/home-manager/tests/modules/misc/debug/default.nix25
-rw-r--r--infra/libkookie/home-manager/tests/modules/misc/fontconfig/default.nix22
-rw-r--r--infra/libkookie/home-manager/tests/modules/misc/fontconfig/multiple-font-packages.nix15
-rw-r--r--infra/libkookie/home-manager/tests/modules/misc/fontconfig/no-font-package.nix17
-rw-r--r--infra/libkookie/home-manager/tests/modules/misc/fontconfig/single-font-package.nix15
-rw-r--r--infra/libkookie/home-manager/tests/modules/misc/numlock/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/misc/numlock/numlock.nix18
-rw-r--r--infra/libkookie/home-manager/tests/modules/misc/pam/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/misc/pam/session-variables-expected.txt2
-rw-r--r--infra/libkookie/home-manager/tests/modules/misc/pam/session-variables.nix19
-rw-r--r--infra/libkookie/home-manager/tests/modules/misc/xdg/default.nix4
-rw-r--r--infra/libkookie/home-manager/tests/modules/misc/xdg/file-attr-names.nix26
-rw-r--r--infra/libkookie/home-manager/tests/modules/misc/xdg/mime-apps-basics-expected.ini9
-rw-r--r--infra/libkookie/home-manager/tests/modules/misc/xdg/mime-apps-basics.nix28
-rw-r--r--infra/libkookie/home-manager/tests/modules/misc/xsession/basic-setxkbmap-expected.service12
-rw-r--r--infra/libkookie/home-manager/tests/modules/misc/xsession/basic-xprofile-expected.txt16
-rw-r--r--infra/libkookie/home-manager/tests/modules/misc/xsession/basic-xsession-expected.txt18
-rw-r--r--infra/libkookie/home-manager/tests/modules/misc/xsession/basic.nix40
-rw-r--r--infra/libkookie/home-manager/tests/modules/misc/xsession/default.nix4
-rw-r--r--infra/libkookie/home-manager/tests/modules/misc/xsession/keyboard-without-layout-expected.service12
-rw-r--r--infra/libkookie/home-manager/tests/modules/misc/xsession/keyboard-without-layout.nix34
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/abook/default.nix4
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/abook/no-settings.nix16
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/abook/with-settings.cfg21
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/abook/with-settings.nix39
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/alacritty/default.nix5
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/alacritty/empty-settings.nix17
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/alacritty/example-settings-expected.yml1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/alacritty/example-settings.nix31
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/alacritty/settings-merging-expected.yml1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/alacritty/settings-merging.nix39
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/alot/alot-expected.conf37
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/alot/alot.nix36
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/alot/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/aria2/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/aria2/settings.nix41
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/autojump/default-settings.nix13
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/autojump/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/autorandr/basic-configuration.conf10
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/autorandr/basic-configuration.nix48
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/autorandr/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/bash/default.nix4
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/bash/logout-expected.txt4
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/bash/logout.nix22
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/bash/session-variables-expected.txt8
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/bash/session-variables.nix23
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/browserpass/browserpass.nix29
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/browserpass/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/dircolors/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/dircolors/settings-expected.conf133
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/dircolors/settings.nix27
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/direnv/bash.nix17
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/direnv/default.nix6
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/direnv/nix-direnv.nix18
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/direnv/stdlib-and-nix-direnv.nix23
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/direnv/stdlib.nix19
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/feh/default.nix4
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/feh/feh-bindings-expected-buttons4
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/feh/feh-bindings-expected-keys3
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/feh/feh-bindings.nix33
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/feh/feh-empty-settings.nix15
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/firefox/default.nix4
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/firefox/profile-settings-expected-user.js6
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/firefox/profile-settings.nix36
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/firefox/state-version-19_09.nix31
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/fish/default.nix5
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/fish/functions.nix48
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/fish/no-functions.nix22
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/fish/plugins.nix60
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/getmail/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/getmail/getmail-expected.conf16
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/getmail/getmail.nix28
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/gh/config-file.nix27
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/gh/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/git/default.nix5
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/git/git-expected-include.conf3
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/git/git-expected.conf58
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/git/git-with-email-expected.conf15
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/git/git-with-email.nix38
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/git/git-with-str-extra-config-expected.conf5
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/git/git-with-str-extra-config.nix23
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/git/git.nix100
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/gpg/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/gpg/override-defaults-expected.conf19
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/gpg/override-defaults.nix22
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/i3status-rust/default.nix6
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/i3status-rust/with-custom.nix186
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/i3status-rust/with-default.nix58
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/i3status-rust/with-extra-settings.nix202
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/i3status-rust/with-multiple-bars.nix106
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/i3status/default.nix4
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/i3status/with-custom.nix67
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/i3status/with-default.nix73
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/kakoune/default.nix7
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/kakoune/no-plugins.nix13
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/kakoune/use-plugins.nix16
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/kakoune/whitespace-highlighter-corner-cases.nix25
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/kakoune/whitespace-highlighter.nix25
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/lf/all-options.nix86
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/lf/default.nix5
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/lf/minimal-options.nix18
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/lf/no-pv-keybind.nix43
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/lieer/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/lieer/lieer-expected.json1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/lieer/lieer.nix23
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/man/apropos.nix22
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/man/default.nix4
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/man/no-manpath.nix13
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/mbsync/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/mbsync/mbsync-expected.conf80
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/mbsync/mbsync.nix83
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/ncmpcpp-linux/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/ncmpcpp-linux/ncmpcpp-use-mpd-config-expected-config1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/ncmpcpp-linux/ncmpcpp-use-mpd-config.nix25
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/ncmpcpp/default.nix4
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/ncmpcpp/ncmpcpp-empty-settings.nix16
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/ncmpcpp/ncmpcpp-example-settings-expected-bindings16
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/ncmpcpp/ncmpcpp-example-settings-expected-config4
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/ncmpcpp/ncmpcpp-example-settings.nix60
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/ne/default.nix4
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/ne/defprefs.nix36
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/ne/passthroughs.nix73
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/neomutt/default.nix4
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/neomutt/hm-example.com-expected35
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/neomutt/hm-example.com-msmtp-expected.conf31
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/neomutt/neomutt-expected.conf27
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/neomutt/neomutt-with-msmtp.nix39
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/neomutt/neomutt.nix42
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/neovim/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/neovim/plugin-config.nix37
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/neovim/plugin-config.vim22
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/newsboat/default.nix4
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/newsboat/newsboat-basics-2003.nix35
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/newsboat/newsboat-basics-urls-2003.txt3
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/newsboat/newsboat-basics-urls.txt3
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/newsboat/newsboat-basics.nix33
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/nushell/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/nushell/settings-expected.toml5
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/nushell/settings.nix34
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/powerline-go/bash.nix28
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/powerline-go/default.nix4
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/powerline-go/zsh.nix31
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/qutebrowser/default.nix4
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/qutebrowser/keybindings.nix39
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/qutebrowser/settings.nix48
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/readline/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/readline/using-all-options.nix31
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/readline/using-all-options.txt11
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/rofi-pass/default.nix4
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/rofi-pass/rofi-pass-config.nix35
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/rofi-pass/rofi-pass-root.nix30
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/rofi/assert-on-both-theme-and-colors-expected.json1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/rofi/assert-on-both-theme-and-colors.nix32
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/rofi/default.nix3
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/ssh/default-config-expected.conf16
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/ssh/default-config.nix18
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/ssh/default.nix17
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-dynamic-bind-path-with-port-asserts.nix29
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts-expected.conf20
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts.nix38
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-local-bind-path-with-port-asserts.nix33
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-local-host-path-with-port-asserts.nix33
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-paths-with-ports-error.json1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-remote-bind-path-with-port-asserts.nix33
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-remote-host-path-with-port-asserts.nix33
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/ssh/match-blocks-attrs-expected.conf34
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/ssh/match-blocks-attrs.nix58
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/ssh/no-assertions.json1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/starship/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/starship/settings-expected.toml27
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/starship/settings.nix49
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/texlive/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/texlive/texlive-minimal.nix27
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/tmux/default-shell.conf30
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/tmux/default-shell.nix27
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/tmux/default.nix10
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/tmux/disable-confirmation-prompt.conf30
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/tmux/disable-confirmation-prompt.nix26
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/tmux/emacs-with-plugins.conf53
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/tmux/emacs-with-plugins.nix49
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/tmux/not-enabled.nix13
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/tmux/prefix.conf32
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/tmux/prefix.nix26
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/tmux/secure-socket-enabled.nix18
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/tmux/shortcut-without-prefix.conf33
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/tmux/shortcut-without-prefix.nix27
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/tmux/vi-all-true.conf30
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/tmux/vi-all-true.nix29
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/vscode/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/vscode/keybindings.nix71
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/waybar/broken-settings.nix80
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/waybar/default.nix8
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/waybar/settings-complex-expected.json46
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/waybar/settings-complex.nix59
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/waybar/styling-expected.css23
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/waybar/styling.nix46
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/waybar/systemd-with-graphical-session-target.nix24
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/waybar/systemd-with-graphical-session-target.service14
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/zplug/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/zplug/modules.nix50
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/zsh/default.nix8
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/zsh/history-path-new-custom.nix20
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/zsh/history-path-new-default.nix17
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/zsh/history-path-old-custom.nix20
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/zsh/history-path-old-default.nix17
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/zsh/prezto.nix25
-rw-r--r--infra/libkookie/home-manager/tests/modules/programs/zsh/session-variables.nix28
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/dropbox/basic-configuration.nix25
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/dropbox/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/emacs/default.nix5
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/emacs/emacs-emacsclient.desktop11
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/emacs/emacs-service-emacs.service12
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/emacs/emacs-service.nix37
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/emacs/emacs-socket-26-emacs.service9
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/emacs/emacs-socket-26-emacs.socket12
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/emacs/emacs-socket-26.nix40
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/emacs/emacs-socket-27-emacs.service9
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/emacs/emacs-socket-27-emacs.socket12
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/emacs/emacs-socket-27.nix42
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/fluidsynth/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/fluidsynth/service.nix24
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/kanshi/basic-configuration.conf15
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/kanshi/basic-configuration.nix52
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/kanshi/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/lieer/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/lieer/lieer-service-expected.service8
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/lieer/lieer-service-expected.timer9
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/lieer/lieer-service.nix34
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/pbgopy/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/pbgopy/service.nix22
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/polybar/basic-configuration.conf21
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/polybar/basic-configuration.nix48
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/polybar/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/sxhkd/configuration.nix33
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/sxhkd/default.nix4
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/sxhkd/service.nix20
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/sxhkd/sxhkdrc13
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/window-managers/i3/default.nix4
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/window-managers/i3/i3-followmouse-expected.conf105
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/window-managers/i3/i3-followmouse.nix31
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/window-managers/i3/i3-keybindings-expected.conf106
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/window-managers/i3/i3-keybindings.nix36
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/window-managers/sway/default.nix6
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-default.conf117
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-default.nix36
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-followmouse-expected.conf94
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-followmouse-legacy-expected.conf94
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-followmouse-legacy.nix44
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-followmouse.nix41
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-post-2003.nix38
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/wlsunset/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/wlsunset/wlsunset-service-expected.service9
-rw-r--r--infra/libkookie/home-manager/tests/modules/services/wlsunset/wlsunset-service.nix25
-rw-r--r--infra/libkookie/home-manager/tests/modules/systemd/default.nix5
-rw-r--r--infra/libkookie/home-manager/tests/modules/systemd/services.nix33
-rw-r--r--infra/libkookie/home-manager/tests/modules/systemd/session-variables-expected.conf2
-rw-r--r--infra/libkookie/home-manager/tests/modules/systemd/session-variables.nix18
-rw-r--r--infra/libkookie/home-manager/tests/modules/systemd/timers-expected.conf8
-rw-r--r--infra/libkookie/home-manager/tests/modules/systemd/timers.nix25
-rw-r--r--infra/libkookie/home-manager/tests/modules/targets-darwin/darwin.nix20
-rw-r--r--infra/libkookie/home-manager/tests/modules/targets-darwin/default.nix5
-rw-r--r--infra/libkookie/home-manager/tests/modules/targets-linux/default.nix1
-rw-r--r--infra/libkookie/home-manager/tests/modules/targets-linux/generic-linux.nix25
-rw-r--r--infra/libkookie/home-manager/tests/modules/xresources/default.nix4
-rw-r--r--infra/libkookie/home-manager/tests/modules/xresources/empty.nix13
-rw-r--r--infra/libkookie/home-manager/tests/modules/xresources/xresources-expected.conf5
-rw-r--r--infra/libkookie/home-manager/tests/modules/xresources/xresources.nix22
544 files changed, 40536 insertions, 0 deletions
diff --git a/infra/libkookie/home-manager/.github/CODEOWNERS b/infra/libkookie/home-manager/.github/CODEOWNERS
new file mode 100644
index 000000000000..54be54fb80b0
--- /dev/null
+++ b/infra/libkookie/home-manager/.github/CODEOWNERS
@@ -0,0 +1,242 @@
+* @rycee
+
+/flake.nix @bqv @kisik21
+
+/modules/home-environment.nix @rycee
+
+/modules/misc/dconf.nix @gnidorah @rycee
+
+/modules/misc/fontconfig.nix @rycee
+/tests/modules/misc/fontconfig @rycee
+
+/modules/misc/gtk.nix @rycee
+
+/modules/misc/news.nix @rycee
+
+/modules/misc/numlock.nix @evanjs
+/tests/modules/misc/numlock @evanjs
+
+/modules/misc/pam.nix @rycee
+/tests/modules/misc/pam @rycee
+
+/modules/misc/qt.nix @rycee
+
+/modules/misc/submodule-support.nix @rycee
+
+/modules/misc/tmpfiles.nix @dawidsowa
+
+/modules/misc/vte.nix @rycee
+
+/modules/misc/xdg-mime-apps.nix @pacien
+
+/modules/misc/xdg-user-dirs.nix @pacien
+
+/modules/programs/aria2.nix @JustinLovinger
+
+/modules/programs/autojump.nix @evanjs
+/tests/modules/programs/autojump @evanjs
+
+/modules/programs/autorandr.nix @uvNikita
+
+/modules/programs/bash.nix @rycee
+
+/modules/programs/bat.nix @marsam
+
+/modules/programs/beets.nix @rycee
+
+/modules/programs/broot.nix @aheaume
+
+/modules/programs/dircolors.nix @JustinLovinger
+
+/modules/programs/direnv.nix @rycee
+
+/modules/programs/eclipse.nix @rycee
+
+/modules/programs/emacs.nix @rycee
+
+/modules/programs/firefox.nix @rycee
+
+/modules/programs/gh.nix @Gerschtli
+/tests/modules/programs/gh @Gerschtli
+
+/modules/programs/git.nix @rycee
+
+/modules/programs/gnome-terminal.nix @rycee
+
+/modules/programs/go.nix @rvolosatovs
+
+/modules/programs/home-manager.nix @rycee
+
+/modules/programs/i3status.nix @JustinLovinger
+
+/modules/programs/i3status-rust.nix @workflow
+
+/modules/programs/keychain.nix @marsam
+
+/modules/programs/lesspipe.nix @rycee
+
+/modules/programs/lf.nix @owm111
+/tests/modules/programs/lf @owm111
+
+/modules/programs/lieer.nix @tadfisher
+
+/modules/programs/lsd.nix @marsam
+
+/modules/programs/matplotlib.nix @rprospero
+
+/modules/programs/mbsync.nix @KarlJoad
+/tests/modules/programs/mbsync @KarlJoad
+
+/modules/programs/mcfly.nix @marsam
+
+/modules/programs/mpv.nix @tadeokondrak
+
+/modules/programs/mu.nix @KarlJoad
+
+/modules/programs/ncmpcpp.nix @olmokramer
+/tests/modules/programs/ncmpcpp @olmokramer
+/tests/modules/programs/ncmpcpp-linux @olmokramer
+
+/modules/programs/ne.nix @cwyc
+/tests/modules/programs/ne @cwyc
+
+/modules/programs/noti.nix @marsam
+
+/modules/programs/nushell.nix @Philipp-M
+/tests/modules/programs/nushell @Philipp-M
+
+/modules/programs/obs-studio.nix @adisbladis
+
+/modules/programs/opam.nix @marsam
+
+/modules/programs/openssh.nix @rycee
+
+/modules/programs/password-store.nix @pacien
+
+/modules/programs/pazi.nix @marsam
+
+/modules/programs/pidgin.nix @rycee
+
+/modules/programs/powerline-go.nix @DamienCassou
+
+/modules/programs/rofi-pass.nix @seylerius
+/tests/modules/programs/rofi-pass @seylerius
+
+/modules/programs/rtorrent.nix @marsam
+
+/modules/programs/ssh.nix @rycee
+
+/modules/programs/starship.nix @marsam
+
+/modules/programs/texlive.nix @rycee
+
+/modules/programs/waybar.nix @berbiche
+/tests/modules/programs/waybar @berbiche
+
+/modules/programs/z-lua.nix @marsam
+
+/modules/programs/zathura.nix @rprospero
+
+/modules/programs/zoxide.nix @marsam
+
+/modules/programs/zsh/prezto.nix @NickHu
+
+/modules/services/caffeine.nix @uvNikita
+
+/modules/services/cbatticon.nix @pmiddend
+
+/modules/services/clipmenu.nix @DamienCassou
+
+/modules/services/dropbox.nix @eyJhb
+/tests/modules/services/dropbox @eyJhb
+
+/modules/services/dunst.nix @rycee
+
+/modules/services/emacs.nix @tadfisher
+
+/modules/services/flameshot.nix @moredhel
+
+/modules/services/fluidsynth.nix @Valodim
+
+/modules/services/gammastep.nix @petabyteboy
+
+/modules/services/gnome-keyring.nix @rycee
+
+/modules/services/gpg-agent.nix @rycee
+
+/modules/services/grobi.nix @mbrgm
+
+/modules/services/hound.nix @adisbladis
+
+/modules/services/imapnotify.nix @nickhu
+
+/modules/services/kanshi.nix @nurelin
+/tests/modules/services/kanshi @nurelin
+
+/modules/services/kdeconnect.nix @adisbladis
+
+/modules/services/keepassx.nix @rycee
+
+/modules/services/lieer.nix @tadfisher
+
+/modules/services/lorri.nix @Gerschtli
+
+/modules/services/mako.nix @onny
+
+/modules/services/mbsync.nix @pjones
+
+/modules/services/mpdris2.nix @pjones
+
+/modules/services/muchsync.nix @pacien
+
+/modules/services/network-manager-applet.nix @rycee
+
+/modules/services/parcellite.nix @gleber
+
+/modules/services/password-store-sync.nix @pacien
+
+/modules/services/pasystray.nix @pltanton
+
+/modules/services/pbgopy.nix @ivarwithoutbones
+/tests/modules/services/pbgopy @ivarwithoutbones
+
+/modules/services/pulseeffects.nix @jonringer
+
+/modules/services/random-background.nix @rycee
+
+/modules/services/redshift.nix @rycee
+
+/modules/services/status-notifier-watcher.nix @pltanton
+
+/modules/services/syncthing.nix @rycee
+
+/modules/services/taffybar.nix @rycee
+
+/modules/services/tahoe-lafs.nix @rycee
+
+/modules/services/taskwarrior-sync.nix @minijackson @pacien
+
+/modules/services/udiskie.nix @rycee
+
+/modules/services/unison.nix @pacien
+
+/modules/services/window-managers/i3-sway/sway.nix @alexarice
+
+/modules/services/wlsunset.nix @matrss
+/tests/modules/services/wlsunset @matrss
+
+/modules/services/xcape.nix @nickhu
+
+/modules/services/xembed-sni-proxy.nix @rycee
+
+/modules/services/xscreensaver.nix @rycee
+
+/modules/services/xsuspender.nix @offlinehacker
+
+/modules/systemd.nix @rycee
+
+/modules/xcursor.nix @league
+
+/modules/xresources.nix @rycee
+
+/modules/xsession.nix @rycee
diff --git a/infra/libkookie/home-manager/.github/ISSUE_TEMPLATE.md b/infra/libkookie/home-manager/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 000000000000..905f975e85de
--- /dev/null
+++ b/infra/libkookie/home-manager/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,38 @@
+<!--
+
+If you are encountering the error
+
+ element xref: validity error : IDREF attribute linkend references an unknown ID "opt-home.file._name__.source"
+
+then it means that you are using an old version of Home Manager, such
+as the release-20.03 branch, with a recent version of Nixpkgs, such as
+version 20.09 or master. See https://git.io/JTb6K for more.
+
+In general, please check if there already exists a relevant issue
+before creating a new one.
+
+-->
+
+### Issue description
+
+<!--
+Please describe the issue. For support and help please use the IRC
+channel #home-manager @ freenode.net instead.
+-->
+
+### Meta
+
+#### Maintainer CC
+
+<!--
+Please @ people who are in the `meta.maintainers` list of the
+offending module. If in doubt, check `git blame` for whoever last
+touched something.
+-->
+
+#### Technical details
+
+<!--
+Please run `nix-shell -p nix-info --run "nix-info -m"` and paste the
+result.
+-->
diff --git a/infra/libkookie/home-manager/.github/PULL_REQUEST_TEMPLATE.md b/infra/libkookie/home-manager/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 000000000000..6c68d75b24e6
--- /dev/null
+++ b/infra/libkookie/home-manager/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,44 @@
+### Description
+
+<!--
+
+Please provide a brief description of your change.
+
+-->
+
+### Checklist
+
+<!--
+
+Please go through the following checklist before opening a non-WIP
+pull-request.
+
+Also make sure to read the guidelines found at
+
+ https://github.com/nix-community/home-manager/blob/master/doc/contributing.adoc#sec-guidelines
+
+-->
+
+- [ ] Change is backwards compatible.
+
+- [ ] Code formatted with `./format`.
+
+- [ ] Code tested through `nix-shell --pure tests -A run.all`.
+
+- [ ] Test cases updated/added. See [example](https://github.com/nix-community/home-manager/commit/f3fbb50b68df20da47f9b0def5607857fcc0d021#diff-b61a6d542f9036550ba9c401c80f00ef).
+
+- [ ] Commit messages are formatted like
+
+ ```
+ {component}: {description}
+
+ {long description}
+ ```
+
+ See [CONTRIBUTING](https://github.com/nix-community/home-manager/blob/master/doc/contributing.adoc#sec-commit-style) for more information and [recent commit messages](https://github.com/nix-community/home-manager/commits/master) for examples.
+
+- If this PR adds a new module
+
+ - [ ] Added myself as module maintainer. See [example](https://github.com/nix-community/home-manager/blob/068ff76a10e95820f886ac46957edcff4e44621d/modules/programs/lesspipe.nix#L6).
+
+ - [ ] Added myself and the module files to `.github/CODEOWNERS`.
diff --git a/infra/libkookie/home-manager/.github/workflows/github_pages.yml b/infra/libkookie/home-manager/.github/workflows/github_pages.yml
new file mode 100644
index 000000000000..aa175b253a0c
--- /dev/null
+++ b/infra/libkookie/home-manager/.github/workflows/github_pages.yml
@@ -0,0 +1,28 @@
+name: GitHub Pages
+on:
+ push:
+ branches:
+ - master
+jobs:
+ publish:
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v2
+ - uses: cachix/install-nix-action@v12
+ with:
+ nix_path: nixpkgs=channel:nixos-unstable
+ - uses: cachix/cachix-action@v8
+ with:
+ name: nix-community
+ signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
+ - run: |
+ nix-build -A docs.html
+ cp -r result/share/doc/home-manager public
+ - name: Deploy
+ uses: peaceiris/actions-gh-pages@v3
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_dir: ./public
diff --git a/infra/libkookie/home-manager/.github/workflows/test.yml b/infra/libkookie/home-manager/.github/workflows/test.yml
new file mode 100644
index 000000000000..7572016cb9a8
--- /dev/null
+++ b/infra/libkookie/home-manager/.github/workflows/test.yml
@@ -0,0 +1,23 @@
+name: Test
+on:
+ pull_request:
+ schedule:
+ - cron: "30 2 * * *"
+jobs:
+ tests:
+ strategy:
+ matrix:
+ os: [ubuntu-latest, macos-latest]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v2
+ - uses: cachix/install-nix-action@v12
+ with:
+ nix_path: nixpkgs=channel:nixos-unstable
+ - uses: cachix/cachix-action@v8
+ with:
+ name: nix-community
+ signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
+ - run: ./format -c
+ - run: nix-shell . -A install
+ - run: nix-shell --pure tests -A run.all
diff --git a/infra/libkookie/home-manager/.gitignore b/infra/libkookie/home-manager/.gitignore
new file mode 100644
index 000000000000..3526db718920
--- /dev/null
+++ b/infra/libkookie/home-manager/.gitignore
@@ -0,0 +1,2 @@
+/flake.lock
+/result*
diff --git a/infra/libkookie/home-manager/.gitlab-ci.yml b/infra/libkookie/home-manager/.gitlab-ci.yml
new file mode 100644
index 000000000000..34085a57e2ac
--- /dev/null
+++ b/infra/libkookie/home-manager/.gitlab-ci.yml
@@ -0,0 +1,43 @@
+image: nixos/nix:latest
+
+variables:
+ NIX_PATH: "nixpkgs=channel:nixos-unstable"
+
+stages:
+ - test
+ - deploy
+
+Run tests:
+ stage: test
+ script:
+ - nix-shell --pure tests -A run.files-text
+ rules:
+ - if: $CI_COMMIT_BRANCH == "master"
+ when: always
+
+pages:
+ stage: deploy
+ script:
+ - mkdir -p ~/.config/nixpkgs
+ - echo '{ manual.html.enable = true; }' > ~/.config/nixpkgs/home.nix
+ - nix-shell . -A install
+ - mkdir public
+ - cp -r ~/.nix-profile/share/doc/home-manager/* public/
+ artifacts:
+ paths:
+ - public
+ rules:
+ - if: $CI_COMMIT_BRANCH == "master"
+ when: always
+
+Deploy NUR:
+ stage: deploy
+ variables:
+ HM_BRANCH: $CI_COMMIT_REF_NAME
+ HM_COMMIT_SHA: $CI_COMMIT_SHA
+ trigger:
+ project: rycee/nur-expressions
+ branch: master
+ rules:
+ - if: $CI_COMMIT_BRANCH == "master" || $CI_COMMIT_BRANCH =~ /^release-/
+ when: always
diff --git a/infra/libkookie/home-manager/CONTRIBUTING.adoc b/infra/libkookie/home-manager/CONTRIBUTING.adoc
new file mode 120000
index 000000000000..b615999d1108
--- /dev/null
+++ b/infra/libkookie/home-manager/CONTRIBUTING.adoc
@@ -0,0 +1 @@
+doc/contributing.adoc \ No newline at end of file
diff --git a/infra/libkookie/home-manager/FAQ.adoc b/infra/libkookie/home-manager/FAQ.adoc
new file mode 120000
index 000000000000..7d90da711962
--- /dev/null
+++ b/infra/libkookie/home-manager/FAQ.adoc
@@ -0,0 +1 @@
+doc/faq.adoc \ No newline at end of file
diff --git a/infra/libkookie/home-manager/LICENSE b/infra/libkookie/home-manager/LICENSE
new file mode 100644
index 000000000000..2db3938bbebd
--- /dev/null
+++ b/infra/libkookie/home-manager/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017-2020 Home Manager contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/infra/libkookie/home-manager/README.md b/infra/libkookie/home-manager/README.md
new file mode 100644
index 000000000000..afb49f202b75
--- /dev/null
+++ b/infra/libkookie/home-manager/README.md
@@ -0,0 +1,372 @@
+Home Manager using Nix
+======================
+
+This project provides a basic system for managing a user environment
+using the [Nix][] package manager together with the Nix libraries
+found in [Nixpkgs][]. Before attempting to use Home Manager please
+read the warning below.
+
+For a more systematic overview of Home Manager and its available
+options, please see the [Home Manager manual][manual].
+
+Words of warning
+----------------
+
+This project is under development. I personally use it to manage
+several user configurations but it may fail catastrophically for you.
+So beware!
+
+Before using Home Manager you should be comfortable using the Nix
+language and the various tools in the Nix ecosystem. Reading through
+the [Nix Pills][] document is a good way to familiarize yourself with
+them.
+
+In some cases Home Manager cannot detect whether it will overwrite a
+previous manual configuration. For example, the Gnome Terminal module
+will write to your dconf store and cannot tell whether a configuration
+that it is about to be overwrite was from a previous Home Manager
+generation or from manual configuration.
+
+Home Manager targets [NixOS][] unstable and NixOS version 20.09 (the
+current stable version), it may or may not work on other Linux
+distributions and NixOS versions.
+
+Also, the `home-manager` tool does not explicitly support rollbacks at
+the moment so if your home directory gets messed up you'll have to fix
+it yourself. See the [rollbacks](#rollbacks) section for instructions
+on how to manually perform a rollback.
+
+Now when your expectations have been built up and you are eager to try
+all this out you can go ahead and read the rest of this text.
+
+Contact
+-------
+
+You can chat with us on IRC in the channel [#home-manager][] on
+[freenode][]. The [channel logs][] are hosted courtesy of
+[samueldr][].
+
+Installation
+------------
+
+Currently the easiest way to install Home Manager is as follows:
+
+1. Make sure you have a working Nix installation. Specifically, make
+ sure that your user is able to build and install Nix packages. For
+ example, you should be able to successfully run a command like
+ `nix-instantiate '<nixpkgs>' -A hello` without having to switch to
+ the root user. For a multi-user install of Nix this means that
+ your user must be covered by the
+ [`allowed-users`][nixAllowedUsers] Nix option. On NixOS you can
+ control this option using the
+ [`nix.allowedUsers`][nixosAllowedUsers] system option.
+
+ Note that Nix 2.4 (`nixUnstable`) is not yet supported.
+
+2. Add the appropriate Home Manager channel. If you are following
+ Nixpkgs master or an unstable channel you can run
+
+ ```console
+ $ nix-channel --add https://github.com/nix-community/home-manager/archive/master.tar.gz home-manager
+ $ nix-channel --update
+ ```
+
+ and if you follow a Nixpkgs version 20.09 channel you can run
+
+ ```console
+ $ nix-channel --add https://github.com/nix-community/home-manager/archive/release-20.09.tar.gz home-manager
+ $ nix-channel --update
+ ```
+
+ On NixOS you may need to log out and back in for the channel to
+ become available. On non-NixOS you may have to add
+
+ ```shell
+ export NIX_PATH=$HOME/.nix-defexpr/channels${NIX_PATH:+:}$NIX_PATH
+ ```
+
+ to your shell (see [nix#2033](https://github.com/NixOS/nix/issues/2033)).
+
+3. Install Home Manager and create the first Home Manager generation:
+
+ ```console
+ $ nix-shell '<home-manager>' -A install
+ ```
+
+ Once finished, Home Manager should be active and available in your
+ user environment.
+
+3. If you do not plan on having Home Manager manage your shell
+ configuration then you must source the
+
+ ```
+ $HOME/.nix-profile/etc/profile.d/hm-session-vars.sh
+ ```
+
+ file in your shell configuration. Unfortunately, in this specific
+ case we currently only support POSIX.2-like shells such as
+ [Bash][] or [Z shell][].
+
+ For example, if you use Bash then add
+
+ ```bash
+ . "$HOME/.nix-profile/etc/profile.d/hm-session-vars.sh"
+ ```
+
+ to your `~/.profile` file.
+
+If instead of using channels you want to run Home Manager from a Git
+checkout of the repository then you can use the
+`programs.home-manager.path` option to specify the absolute path to
+the repository.
+
+Usage
+-----
+
+Home Manager is typically managed through the `home-manager` tool.
+This tool can, for example, apply configurations to your home
+directory, list user packages installed by the tool, and list the
+configuration generations.
+
+As an example, let us expand the initial configuration file from the
+installation above to install the htop and fortune packages, install
+Emacs with a few extra packages enabled, install Firefox with
+smooth scrolling enabled, and enable the user gpg-agent service.
+
+To satisfy the above setup we should elaborate the
+`~/.config/nixpkgs/home.nix` file as follows:
+
+```nix
+{ pkgs, ... }:
+
+{
+ home.packages = [
+ pkgs.htop
+ pkgs.fortune
+ ];
+
+ programs.emacs = {
+ enable = true;
+ extraPackages = epkgs: [
+ epkgs.nix-mode
+ epkgs.magit
+ ];
+ };
+
+ programs.firefox = {
+ enable = true;
+ profiles = {
+ myprofile = {
+ settings = {
+ "general.smoothScroll" = false;
+ };
+ };
+ };
+ };
+
+ services.gpg-agent = {
+ enable = true;
+ defaultCacheTtl = 1800;
+ enableSshSupport = true;
+ };
+
+ programs.home-manager = {
+ enable = true;
+ path = "…";
+ };
+}
+```
+
+To activate this configuration you can then run
+
+```console
+$ home-manager switch
+```
+
+or if you are not feeling so lucky,
+
+```console
+$ home-manager build
+```
+
+which will create a `result` link to a directory containing an
+activation script and the generated home directory files.
+
+Documentation of available configuration options, including
+descriptions and usage examples, is available in the [Home Manager
+manual][configuration options] or offline by running
+
+```console
+$ man home-configuration.nix
+```
+
+Rollbacks
+---------
+
+While the `home-manager` tool does not explicitly support rollbacks at
+the moment it is relatively easy to perform one manually. The steps to
+do so are
+
+1. Run `home-manager generations` to determine which generation you
+ wish to rollback to:
+
+ ```console
+ $ home-manager generations
+ 2018-01-04 11:56 : id 765 -> /nix/store/kahm1rxk77mnvd2l8pfvd4jkkffk5ijk-home-manager-generation
+ 2018-01-03 10:29 : id 764 -> /nix/store/2wsmsliqr5yynqkdyjzb1y57pr5q2lsj-home-manager-generation
+ 2018-01-01 12:21 : id 763 -> /nix/store/mv960kl9chn2lal5q8lnqdp1ygxngcd1-home-manager-generation
+ 2017-12-29 21:03 : id 762 -> /nix/store/6c0k1r03fxckql4vgqcn9ccb616ynb94-home-manager-generation
+ 2017-12-25 18:51 : id 761 -> /nix/store/czc5y6vi1rvnkfv83cs3rn84jarcgsgh-home-manager-generation
+ …
+ ```
+
+2. Copy the Nix store path of the generation you chose, e.g.,
+
+ /nix/store/mv960kl9chn2lal5q8lnqdp1ygxngcd1-home-manager-generation
+
+ for generation 763.
+
+3. Run the `activate` script inside the copied store path:
+
+ ```console
+ $ /nix/store/mv960kl9chn2lal5q8lnqdp1ygxngcd1-home-manager-generation/activate
+ Starting home manager activation
+ …
+ ```
+
+Keeping your ~ safe from harm
+-----------------------------
+
+To configure programs and services Home Manager must write various
+things to your home directory. To prevent overwriting any existing
+files when switching to a new generation, Home Manager will attempt to
+detect collisions between existing files and generated files. If any
+such collision is detected the activation will terminate before
+changing anything on your computer.
+
+For example, suppose you have a wonderful, painstakingly created
+`~/.config/git/config` and add
+
+```nix
+{
+ # …
+
+ programs.git = {
+ enable = true;
+ userName = "Jane Doe";
+ userEmail = "jane.doe@example.org";
+ };
+
+ # …
+}
+```
+
+to your configuration. Attempting to switch to the generation will
+then result in
+
+```console
+$ home-manager switch
+…
+Activating checkLinkTargets
+Existing file '/home/jdoe/.gitconfig' is in the way
+Please move the above files and try again
+```
+
+Graphical services
+------------------
+
+Home Manager includes a number of services intended to run in a
+graphical session, for example `xscreensaver` and `dunst`.
+Unfortunately, such services will not be started automatically unless
+you let Home Manager start your X session. That is, you have something
+like
+
+```nix
+{
+ # …
+
+ services.xserver.enable = true;
+
+ # …
+}
+```
+
+in your system configuration and
+
+```nix
+{
+ # …
+
+ xsession.enable = true;
+ xsession.windowManager.command = "…";
+
+ # …
+}
+```
+
+in your Home Manager configuration.
+
+Nix Flakes
+----------
+
+Home Manager includes a `flake.nix` file for compatibility with [Nix Flakes][]
+for those that wish to use it as a module. A bare-minimum `flake.nix` would be
+as follows:
+
+```nix
+{
+ description = "NixOS configuration";
+
+ inputs = {
+ nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
+ home-manager.url = "github:nix-community/home-manager";
+ };
+
+ outputs = { home-manager, nixpkgs, ... }: {
+ nixosConfigurations = {
+ hostname = nixpkgs.lib.nixosSystem {
+ system = "x86_64-linux";
+ modules = [
+ ./configuration.nix
+ home-manager.nixosModules.home-manager
+ {
+ home-manager.useGlobalPkgs = true;
+ home-manager.useUserPackages = true;
+ home-manager.users.user = import ./home.nix;
+ }
+ ];
+ };
+ };
+ };
+}
+```
+
+Note, the Home Manager library is exported by the flake under
+`lib.hm`.
+
+Releases
+--------
+
+Home Manager is developed against `nixpkgs-unstable` branch, which
+often causes it to contain tweaks for changes/packages not yet
+released in stable NixOS. To avoid breaking users' configurations,
+Home Manager is released in branches corresponding to NixOS releases
+(e.g. `release-20.09`). These branches get fixes, but usually not new
+modules. If you need a module to be backported, then feel free to open
+an issue.
+
+[Bash]: https://www.gnu.org/software/bash/
+[Nix]: https://nixos.org/nix/
+[NixOS]: https://nixos.org/
+[Nixpkgs]: https://nixos.org/nixpkgs/
+[nixAllowedUsers]: https://nixos.org/nix/manual/#conf-allowed-users
+[nixosAllowedUsers]: https://nixos.org/nixos/manual/options.html#opt-nix.allowedUsers
+[Z shell]: http://zsh.sourceforge.net/
+[manual]: https://nix-community.github.io/home-manager/
+[configuration options]: https://nix-community.github.io/home-manager/options.html
+[#home-manager]: https://webchat.freenode.net/?url=irc%3A%2F%2Firc.freenode.net%2Fhome-manager
+[freenode]: https://freenode.net/
+[channel logs]: https://logs.nix.samueldr.com/home-manager/
+[samueldr]: https://github.com/samueldr/
+[Nix Pills]: https://nixos.org/nixos/nix-pills/
+[Nix Flakes]: https://nixos.wiki/wiki/Flakes
diff --git a/infra/libkookie/home-manager/default.nix b/infra/libkookie/home-manager/default.nix
new file mode 100644
index 000000000000..4219d370c493
--- /dev/null
+++ b/infra/libkookie/home-manager/default.nix
@@ -0,0 +1,18 @@
+{ pkgs ? import <nixpkgs> { } }:
+
+rec {
+ docs = with import ./doc { inherit pkgs; }; {
+ html = manual.html;
+ manPages = manPages;
+ json = options.json;
+ };
+
+ home-manager = pkgs.callPackage ./home-manager { path = toString ./.; };
+
+ install =
+ pkgs.callPackage ./home-manager/install.nix { inherit home-manager; };
+
+ nixos = import ./nixos;
+
+ path = ./.;
+}
diff --git a/infra/libkookie/home-manager/doc/contributing.adoc b/infra/libkookie/home-manager/doc/contributing.adoc
new file mode 100644
index 000000000000..dd68f296f785
--- /dev/null
+++ b/infra/libkookie/home-manager/doc/contributing.adoc
@@ -0,0 +1,255 @@
+[[ch-contributing]]
+== Contributing
+
+:open-issues: https://github.com/nix-community/home-manager/issues
+:new-issue: https://github.com/nix-community/home-manager/issues/new
+:fork-a-repo: https://help.github.com/articles/fork-a-repo/
+:create-a-pull-request: https://help.github.com/articles/creating-a-pull-request/
+:seven-rules: https://chris.beams.io/posts/git-commit/#seven-rules
+:news-nix: https://github.com/nix-community/home-manager/blob/master/modules/misc/news.nix
+:nixfmt: https://github.com/serokell/nixfmt/
+:example-commit-message: https://github.com/nix-community/home-manager/commit/69f8e47e9e74c8d3d060ca22e18246b7f7d988ef
+
+Contributions to Home Manager are very welcome. To make the process as smooth as possible for both you and the Home Manager maintainers we provide some guidelines that we ask you to follow. See <<sec-contrib-getting-started>> for information on how to set up a suitable development environment and <<sec-guidelines>> for the actual guidelines.
+
+This text is mainly directed at those who would like to make code contributions to Home Manager. If you just want to report a bug then first look among the already {open-issues}[open issues], if you find one matching yours then feel free to comment on it to add any additional information you may have. If no matching issue exists then go to the {new-issue}[new issue] page and write a description of your problem. Include as much information as you can, ideally also include relevant excerpts from your Home Manager configuration.
+
+[[sec-contrib-getting-started]]
+=== Getting started
+
+If you have not previously forked Home Manager then you need to do that first. Have a look at GitHub's {fork-a-repo}[Fork a repo] for instructions on how to do this.
+
+Once you have a fork of Home Manager you should create a branch starting at the most recent `master` branch. Give your branch a reasonably descriptive name. Commit your changes to this branch and when you are happy with the result and it fulfills <<sec-guidelines>> then push the branch to GitHub and {create-a-pull-request}[create a pull request].
+
+Assuming your clone is at `$HOME/devel/home-manager` then you can make the `home-manager` command use it by either
+
+1. overriding the default path by using the `-I` command line option:
++
+[source,console]
+$ home-manager -I home-manager=$HOME/devel/home-manager
++
+or
+
+2. changing the default path by ensuring your configuration includes
++
+[source,nix]
+----
+programs.home-manager.enable = true;
+programs.home-manager.path = "$HOME/devel/home-manager";
+----
++
+and running `home-manager switch` to activate the change. Afterwards, `home-manager build` and `home-manager switch` will use your cloned repository.
+
+The first option is good if you only temporarily want to use your clone.
+
+[[sec-guidelines]]
+=== Guidelines
+:irc-home-manager: https://webchat.freenode.net/?url=irc%3A%2F%2Firc.freenode.net%2Fhome-manager
+:valuable-options: https://github.com/Infinisil/rfcs/blob/config-option/rfcs/0042-config-option.md#valuable-options
+:rfc-42: https://github.com/Infinisil/rfcs/blob/config-option/rfcs/0042-config-option.md
+
+If your contribution satisfy the following rules then there is a good chance it will be merged without too much trouble. The rules are enforced by the Home Manager maintainers and to a lesser extent the Home Manager CI system.
+
+If you are uncertain how these rules affect the change you would like to make then feel free to start a discussion in the {irc-home-manager}[#home-manager] IRC channel, ideally before you start developing.
+
+[[sec-guidelines-back-compat]]
+==== Maintain backward compatibility
+
+Your contribution should never cause another user's existing configuration to break. Home Manager is used in many different environments and you should consider how you change may effect others. For example,
+
+- Does your change work for people that do not use NixOS? Consider other GNU/Linux distributions and macOS.
+- Does your change work for people whose configuration is built on one system and deployed on another system?
+
+[[sec-guidelines-forward-compat]]
+==== Keep forward compatibility in mind
+
+The master branch of Home Manager tracks the unstable channel of Nixpkgs, which may update package versions at any time. It is therefore important to consider how a package update may affect your code and try to reduce the risk of breakage.
+
+The most effective way to reduce this risk is to follow the advice in <<sec-guidelines-valuable-options>>.
+
+[[sec-guidelines-valuable-options]]
+==== Add only valuable options
+
+When creating a new module it is tempting to include every option supported by the software. This is _strongly_ discouraged. Providing many options increases maintenance burden and risk of breakage considerably. This is why only the most {valuable-options}[important software options] should be modeled explicitly. Less important options should be expressible through an `extraConfig` escape hatch.
+
+A good rule of thumb for the first implementation of a module is to only add explicit options for those settings that absolutely must be set for the software to function correctly. It follows that a module for software that provides sensible default values for all settings would require no explicit options at all.
+
+If the software uses a structured configuration format like a JSON, YAML, INI, TOML, or even a plain list of key/value pairs then consider using a `settings` option as described in {rfc-42}[Nix RFC 42].
+
+[[sec-guidelines-add-tests]]
+==== Add relevant tests
+
+If at all possible, make sure to add new tests and expand existing tests so that your change will keep working in the future. See <<sec-tests>> for more information about the Home Manager test suite.
+
+All contributed code _must_ pass the test suite.
+
+[[sec-guidelines-module-maintainer]]
+
+==== Add relevant documentation
+:docbook: https://tdg.docbook.org/
+:asciidoc: https://asciidoc.org/
+:docbook-rocks: https://docbook.rocks/
+
+Many code changes require changing the documentation as well. Module options should be documented with DocBook. See {docbook-rocks}[DocBook rocks!] for a quick introduction and {docbook}[DocBook 5: The Definitive Guide] for in-depth information of DocBook. Home Manager is itself documented using a combination of DocBook and {asciidoc}[AsciiDoc]. All text is hosted in Home Manager's Git repository.
+
+The HTML version of the manual containing both the module option descriptions and the documentation of Home Manager can be generated and opened by typing the following in a shell within a clone of the Home Manager Git repository:
+
+[source,console]
+$ nix-build -A docs.html
+$ xdg-open ./result/share/doc/home-manager/index.html
+
+When you have made changes to a module, it is a good idea to check that the man page version of the module options looks good:
+
+[source,console]
+$ nix-build -A docs.manPages
+$ man ./result/share/man/man5/home-configuration.nix.5
+
+==== Add yourself as a module maintainer
+
+Every new module _must_ include a named maintainer using the `meta.maintainers` attribute. If you are a user of a module that currently lacks a maintainer then please consider adopting it.
+
+If you are present in the NixOS maintainer list then you can use that entry. If you are not then you can add yourself to `modules/lib/maintainers.nix` in the Home Manager project.
+
+Also add yourself to `.github/CODEOWNERS` as owner of the associated module files, including the test files. You will then be automatically added as a reviewer on any new pull request that touches your files.
+
+Maintainers are encouraged to join the IRC channel and participate when they have opportunity.
+
+[[sec-guidelines-code-style]]
+==== Format your code
+
+Make sure your code is formatted as described in <<sec-code-style>>. To maintain consistency throughout the project you are encouraged to browse through existing code and adopt its style also in new code.
+
+[[sec-guidelines-commit-message-style]]
+==== Format your commit messages
+
+Similar to <<sec-guidelines-code-style>> we encourage a consistent commit message format as described in <<sec-commit-style>>.
+
+[[sec-guidelines-news-style]]
+==== Format your news entries
+
+If your contribution includes a change that should be communicated to users of Home Manager then you can add a news entry. The entry must be formatted as described in <<sec-news>>.
+
+When new modules are added a news entry should be included but you do not need to create this entry manually. The merging maintainer will create the entry for you. This is to reduce the risk of merge conflicts.
+
+[[sec-guidelines-conditional-modules]]
+==== Use conditional modules and news
+
+Home Manager includes a number of modules that are only usable on some of the supported platforms. The most common example of platform specific modules are those that define systemd user services, which only works on Linux systems.
+
+If you add a module that is platform specific then make sure to include a condition in the `loadModule` function call. This will make the module accessible only on systems where the condition evaluates to `true`.
+
+Similarly, if you are adding a news entry then it should be shown only to users that may find it relevant, see <<sec-news>> for a description of conditional news.
+
+[[sec-guidelines-licensing]]
+==== Mind the license
+
+The Home Manager project is covered by the MIT license and we can only accept contributions that fall under this license, or are licensed in a compatible way. When you contribute self written code and documentation it is assumed that you are doing so under the MIT license.
+
+A potential gotcha with respect to licensing are option descriptions. Often it is convenient to copy from the upstream software documentation. When this is done it is important to verify that the license of the upstream documentation allows redistribution under the terms of the MIT license.
+
+[[sec-commit-style]]
+=== Commits
+
+The commits in your pull request should be reasonably self-contained, that is, each commit should make sense in isolation. In particular, you will be asked to amend any commit that introduces syntax errors or similar problems even if they are fixed in a later commit.
+
+The commit messages should follow the {seven-rules}[seven rules]. We also ask you to include the affected code component or module in the first line. That is, a commit message should follow the template
+
+----
+{component}: {description}
+
+{long description}
+----
+
+where `{component}` refers to the code component (or module) your change affects, `{description}` is a very brief description of your change, and `{long description}` is an optional clarifying description. Note, `{description}` should start with a lower case letter. As a rare exception, if there is no clear component, or your change affects many components, then the `{component}` part is optional. See <<ex-commit-message>> for a commit message that fulfills these requirements.
+
+[[ex-commit-message]]
+.Compliant commit message
+===============================================================================
+The commit {example-commit-message}[69f8e47e9e74c8d3d060ca22e18246b7f7d988ef] contains the commit message
+
+----
+starship: allow running in Emacs if vterm is used
+
+The vterm buffer is backed by libvterm and can handle Starship prompts
+without issues.
+----
+
+which ticks all the boxes necessary to be accepted in Home Manager.
+===============================================================================
+
+Finally, when adding a new module, say `programs/foo.nix`, we use the fixed commit format `foo: add module`. You can, of course, still include a long description if you wish.
+
+[[sec-code-style]]
+=== Code Style
+
+The code in Home Manager is formatted by the {nixfmt}[nixfmt] tool and the formatting is checked in the pull request tests. Run the `format` tool inside the project repository before submitting your pull request.
+
+Keep lines at a reasonable width, ideally 80 characters or less. This also applies to string literals.
+
+We prefer `lowerCamelCase` for variable and attribute names with the accepted exception of variables directly referencing packages in Nixpkgs which use a hyphenated style. For example, the Home Manager option `services.gpg-agent.enableSshSupport` references the `gpg-agent` package in Nixpkgs.
+
+[[sec-news]]
+=== News
+
+Home Manager includes a system for presenting news to the user. When making a change you, therefore, have the option to also include an associated news entry. In general, a news entry should only be added for truly noteworthy news. For example, a bug fix or new option does generally not need a news entry.
+
+If you do have a change worthy of a news entry then please add one in {news-nix}[`news.nix`] but you should follow some basic guidelines:
+
+- The entry timestamp should be in ISO-8601 format having "+00:00" as time zone. For example, "2017-09-13T17:10:14+00:00". A suitable timestamp can be produced by the command
++
+[source,console]
+$ date --iso-8601=second --universal
+
+- The entry condition should be as specific as possible. For example, if you are changing or deprecating a specific option then you could restrict the news to those users who actually use this option.
+
+- Wrap the news message so that it will fit in the typical terminal, that is, at most 80 characters wide. Ideally a bit less.
+
+- Unlike commit messages, news will be read without any connection to the Home Manager source code. It is therefore important to make the message understandable in isolation and to those who do not have knowledge of the Home Manager internals. To this end it should be written in more descriptive, prose like way.
+
+- If you refer to an option then write its full attribute path. That is, instead of writing
++
+----
+The option 'foo' has been deprecated, please use 'bar' instead.
+----
++
+it should read
++
+----
+The option 'services.myservice.foo' has been deprecated, please
+use 'services.myservice.bar' instead.
+----
+
+- A new module, say `foo.nix`, should always include a news entry that has a message along the lines of
++
+----
+A new module is available: 'services.foo'.
+----
++
+If the module is platform specific, e.g., a service module using systemd, then a condition like
++
+[source,nix]
+condition = hostPlatform.isLinux;
++
+should be added. If you contribute a module then you don't need to add this entry, the merger will create an entry for you.
+
+[[sec-tests]]
+=== Tests
+
+Home Manager includes a basic test suite and it is highly recommended to include at least one test when adding a module. Tests are typically in the form of "golden tests" where, for example, a generated configuration file is compared to a known correct file.
+
+It is relatively easy to create tests by modeling the existing tests, found in the `tests` project directory.
+
+The full Home Manager test suite can be run by executing
+
+[source,console]
+$ nix-shell --pure tests -A run.all
+
+in the project root. List all test cases through
+
+[source,console]
+$ nix-shell --pure tests -A list
+
+and run an individual test, for example `alacritty-empty-settings`, through
+
+[source,console]
+$ nix-shell --pure tests -A run.alacritty-empty-settings
diff --git a/infra/libkookie/home-manager/doc/default.nix b/infra/libkookie/home-manager/doc/default.nix
new file mode 100644
index 000000000000..6b8e229aae1e
--- /dev/null
+++ b/infra/libkookie/home-manager/doc/default.nix
@@ -0,0 +1,69 @@
+{ pkgs
+
+# Note, this should be "the standard library" + HM extensions.
+, lib ? import ../modules/lib/stdlib-extended.nix pkgs.lib }:
+
+let
+
+ nmdSrc = pkgs.fetchFromGitLab {
+ name = "nmd";
+ owner = "rycee";
+ repo = "nmd";
+ rev = "2398aa79ab12aa7aba14bc3b08a6efd38ebabdc5";
+ sha256 = "0yxb48afvccn8vvpkykzcr4q1rgv8jsijqncia7a5ffzshcrwrnh";
+ };
+
+ nmd = import nmdSrc { inherit lib pkgs; };
+
+ # Make sure the used package is scrubbed to avoid actually
+ # instantiating derivations.
+ scrubbedPkgsModule = {
+ imports = [{
+ _module.args = {
+ pkgs = lib.mkForce (nmd.scrubDerivations "pkgs" pkgs);
+ pkgs_i686 = lib.mkForce { };
+ };
+ }];
+ };
+
+ hmModulesDocs = nmd.buildModulesDocs {
+ modules = import ../modules/modules.nix {
+ inherit lib pkgs;
+ check = false;
+ } ++ [ scrubbedPkgsModule ];
+ moduleRootPaths = [ ./.. ];
+ mkModuleUrl = path:
+ "https://github.com/nix-community/home-manager/blob/master/${path}#blob-path";
+ channelName = "home-manager";
+ docBook.id = "home-manager-options";
+ };
+
+ docs = nmd.buildDocBookDocs {
+ pathName = "home-manager";
+ modulesDocs = [ hmModulesDocs ];
+ documentsDirectory = ./.;
+ documentType = "book";
+ chunkToc = ''
+ <toc>
+ <d:tocentry xmlns:d="http://docbook.org/ns/docbook" linkend="book-home-manager-manual"><?dbhtml filename="index.html"?>
+ <d:tocentry linkend="ch-options"><?dbhtml filename="options.html"?></d:tocentry>
+ <d:tocentry linkend="ch-tools"><?dbhtml filename="tools.html"?></d:tocentry>
+ <d:tocentry linkend="ch-release-notes"><?dbhtml filename="release-notes.html"?></d:tocentry>
+ </d:tocentry>
+ </toc>
+ '';
+ };
+
+in {
+ inherit nmdSrc;
+
+ options = {
+ json = hmModulesDocs.json.override {
+ path = "share/doc/home-manager/options.json";
+ };
+ };
+
+ manPages = docs.manPages;
+
+ manual = { inherit (docs) html htmlOpenTool; };
+}
diff --git a/infra/libkookie/home-manager/doc/faq.adoc b/infra/libkookie/home-manager/doc/faq.adoc
new file mode 100644
index 000000000000..b8215254dd7d
--- /dev/null
+++ b/infra/libkookie/home-manager/doc/faq.adoc
@@ -0,0 +1,171 @@
+[[ch-faq]]
+== Frequently Asked Questions (FAQ)
+
+=== Why is there a collision error when switching generation?
+
+Home Manager currently installs packages into the user environment, precisely as if the packages were installed through `nix-env --install`. This means that you will get a collision error if your Home Manager configuration attempts to install a package that you already have installed manually, that is, packages that shows up when you run `nix-env --query`.
+
+For example, imagine you have the `hello` package installed in your environment
+
+[source,console]
+----
+$ nix-env --query
+hello-2.10
+----
+
+and your Home Manager configuration contains
+
+[source,nix]
+----
+home.packages = [ pkgs.hello ];
+----
+
+Then attempting to switch to this configuration will result in an error similar to
+
+[source,console]
+----
+$ home-manager switch
+these derivations will be built:
+ /nix/store/xg69wsnd1rp8xgs9qfsjal017nf0ldhm-home-manager-path.drv
+[…]
+Activating installPackages
+replacing old ‘home-manager-path’
+installing ‘home-manager-path’
+building path(s) ‘/nix/store/b5c0asjz9f06l52l9812w6k39ifr49jj-user-environment’
+Wide character in die at /nix/store/64jc9gd2rkbgdb4yjx3nrgc91bpjj5ky-buildenv.pl line 79.
+collision between ‘/nix/store/fmwa4axzghz11cnln5absh31nbhs9lq1-home-manager-path/bin/hello’ and ‘/nix/store/c2wyl8b9p4afivpcz8jplc9kis8rj36d-hello-2.10/bin/hello’; use ‘nix-env --set-flag priority NUMBER PKGNAME’ to change the priority of one of the conflicting packages
+builder for ‘/nix/store/b37x3s7pzxbasfqhaca5dqbf3pjjw0ip-user-environment.drv’ failed with exit code 2
+error: build of ‘/nix/store/b37x3s7pzxbasfqhaca5dqbf3pjjw0ip-user-environment.drv’ failed
+----
+
+The solution is typically to uninstall the package from the environment using `nix-env --uninstall` and reattempt the Home Manager generation switch.
+
+=== Why are the session variables not set?
+
+Home Manager is only able to set session variables automatically if it manages your Bash or Z shell configuration. If you don't want to let Home Manager manage your shell then you will have to manually source the `~/.nix-profile/etc/profile.d/hm-session-vars.sh` file in an appropriate way. In Bash and Z shell this can be done by adding
+
+[source,bash]
+----
+. "$HOME/.nix-profile/etc/profile.d/hm-session-vars.sh"
+----
+
+to your `.profile` and `.zshrc` files, respectively. The `hm-session-vars.sh` file should work in most Bourne-like shells.
+
+=== How do set up a configuration for multiple users/machines?
+:post-your-homenix: https://www.reddit.com/r/NixOS/comments/9bb9h9/post_your_homemanager_homenix_file/
+
+A typical way to prepare a repository of configurations for multiple logins and machines is to prepare one "top-level" file for each unique combination.
+
+For example, if you have two machines, called "kronos" and "rhea" on which you want to configure your user "jane" then you could create the files
+
+- `kronos-jane.nix`,
+- `rhea-jane.nix`, and
+- `common.nix`
+
+in your repository. On the kronos and rhea machines you can then make `~jane/.config/nixpkgs/home.nix` be a symbolic link to the corresponding file in your configuration repository.
+
+The `kronos-jane.nix` and `rhea-jane.nix` files follow the format
+
+[source,nix]
+----
+{ ... }:
+
+{
+ imports = [ ./common.nix ];
+
+ # Various options that are specific for this machine/user.
+}
+----
+
+while the `common.nix` file contains configuration shared across the two logins. Of course, instead of just a single `common.nix` file you can have multiple ones, even one per program or service.
+
+You can get some inspiration from the {post-your-homenix}[Post your home-manager home.nix file!] Reddit thread.
+
+=== Why do I get an error message about `ca.desrt.dconf`?
+
+You are most likely trying to configure the GTK or Gnome Terminal but the DBus session is not aware of the dconf service. The full error you might get is
+
+----
+error: GDBus.Error:org.freedesktop.DBus.Error.ServiceUnknown: The name ca.desrt.dconf was not provided by any .service files
+----
+
+The solution on NixOS is to add
+
+[source,nix]
+services.dbus.packages = with pkgs; [ gnome3.dconf ];
+
+to your system configuration.
+
+=== How do I install packages from Nixpkgs unstable?
+
+If you are using a stable version of Nixpkgs but would like to install some particular packages from Nixpkgs unstable – or some other channel – then you can import the unstable Nixpkgs and refer to its packages within your configuration. Something like
+
+[source,nix]
+----
+{ pkgs, config, ... }:
+
+let
+
+ pkgsUnstable = import <nixpkgs-unstable> {};
+
+in
+
+{
+ home.packages = [
+ pkgsUnstable.foo
+ ];
+
+ # …
+}
+----
+
+should work provided you have a Nix channel called `nixpkgs-unstable`.
+
+You can add the `nixpkgs-unstable` channel by running
+
+[source,console]
+----
+# nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs-unstable
+# nix-channel --update
+----
+
+Note, the package will not be affected by any package overrides, overlays, etc.
+
+=== How do I override the package used by a module?
+:nixpkgs-overlays: https://nixos.org/nixpkgs/manual/#chap-overlays
+
+By default Home Manager will install the package provided by your chosen `nixpkgs` channel but occasionally you might end up needing to change this package. This can typically be done in two ways.
+
+1. If the module provides a `package` option, such as `programs.beets.package`, then this is the recommended way to perform the override. For example,
++
+[source,nix]
+programs.beets.package = pkgs.beets.override { enableCheck = true; };
+
+2. If no `package` option is available then you can typically override the relevant package using an {nixpkgs-overlays}[overlay].
++
+For example, if you want to use the `programs.skim` module but use the `skim` package from Nixpkgs unstable, then a configuration like
++
+[source,nix]
+----
+{ pkgs, config, ... }:
+
+let
+
+ pkgsUnstable = import <nixpkgs-unstable> {};
+
+in
+
+{
+ programs.skim.enable = true;
+
+ nixpkgs.overlays = [
+ (self: super: {
+ skim = pkgsUnstable.skim;
+ })
+ ];
+
+ # …
+}
+----
++
+should work OK.
diff --git a/infra/libkookie/home-manager/doc/installation.adoc b/infra/libkookie/home-manager/doc/installation.adoc
new file mode 100644
index 000000000000..b51173b096ff
--- /dev/null
+++ b/infra/libkookie/home-manager/doc/installation.adoc
@@ -0,0 +1,257 @@
+[[ch-installation]]
+== Installing Home Manager
+
+:nix-darwin: https://github.com/LnL7/nix-darwin/
+
+Home Manager can be used in three primary ways:
+
+1. Using the standalone `home-manager` tool. For platforms other than
+NixOS and Darwin, this is the only available choice. It is also
+recommended for people on NixOS or Darwin that want to manage their
+home directory independent of the system as a whole. See
+<<sec-install-standalone>> for instructions on how to perform this
+installation.
+
+2. As a module within a NixOS system configuration. This allows the
+user profiles to be built together with the system when running
+`nixos-rebuild`. See <<sec-install-nixos-module>> for a description of
+this setup.
+
+3. As a module within a {nix-darwin}[nix-darwin] system configuration.
+This allows the user profiles to be built together with the system
+when running `darwin-rebuild`. See <<sec-install-nix-darwin-module>>
+for a description of this setup.
+
+[[sec-install-standalone]]
+=== Standalone installation
+
+:nix-allowed-users: https://nixos.org/nix/manual/#conf-allowed-users
+:nixos-allowed-users: https://nixos.org/nixos/manual/options.html#opt-nix.allowedUsers
+
+1. Make sure you have a working Nix installation. Specifically, make
+sure that your user is able to build and install Nix packages. For
+example, you should be able to successfully run a command like
+`nix-instantiate '<nixpkgs>' -A hello` without having to switch to the
+root user. For a multi-user install of Nix this means that your user
+must be covered by the {nix-allowed-users}[`allowed-users`] Nix
+option. On NixOS you can control this option using the
+{nixos-allowed-users}[`nix.allowedUsers`] system option.
+
+2. Add the Home Manager channel that you wish to follow. If you are
+following Nixpkgs master or an unstable channel then this is done by
+running
++
+[source,console]
+----
+$ nix-channel --add https://github.com/nix-community/home-manager/archive/master.tar.gz home-manager
+$ nix-channel --update
+----
++
+and if you follow a Nixpkgs version 20.09 channel, you can run
++
+[source,console]
+----
+$ nix-channel --add https://github.com/nix-community/home-manager/archive/release-20.09.tar.gz home-manager
+$ nix-channel --update
+----
++
+On NixOS you may need to log out and back in for the channel to become
+available. On non-NixOS you may have to add
++
+[source,bash]
+export NIX_PATH=$HOME/.nix-defexpr/channels${NIX_PATH:+:}$NIX_PATH
++
+to your shell (see
+https://github.com/NixOS/nix/issues/2033[nix#2033]).
+
+3. Run the Home Manager installation command and create the first Home
+Manager generation:
++
+[source,console]
+$ nix-shell '<home-manager>' -A install
++
+Once finished, Home Manager should be active and available in your
+user environment.
+
+4. If you do not plan on having Home Manager manage your shell
+configuration then you must source the
++
+[source,bash]
+$HOME/.nix-profile/etc/profile.d/hm-session-vars.sh
++
+file in your shell configuration. Unfortunately, we currently only
+support POSIX.2-like shells such as
+https://www.gnu.org/software/bash/[Bash] or
+http://zsh.sourceforge.net/[Z shell].
++
+For example, if you use Bash then add
++
+[source,bash]
+----
+. "$HOME/.nix-profile/etc/profile.d/hm-session-vars.sh"
+----
++
+to your `~/.profile` file.
+
+If instead of using channels you want to run Home Manager from a Git
+checkout of the repository then you can use the
+<<opt-programs.home-manager.path>> option to specify the absolute path
+to the repository.
+
+[[sec-install-nixos-module]]
+=== NixOS module
+
+Home Manager provides a NixOS module that allows you to prepare user
+environments directly from the system configuration file, which often
+is more convenient than using the `home-manager` tool. It also opens
+up additional possibilities, for example, to automatically configure
+user environments in NixOS declarative containers or on systems
+deployed through NixOps.
+
+To make the NixOS module available for use you must `import` it into
+your system configuration. This is most conveniently done by adding a
+Home Manager channel. For example, if you are following Nixpkgs master
+or an unstable channel, you can run
+
+[source,console]
+----
+# nix-channel --add https://github.com/nix-community/home-manager/archive/master.tar.gz home-manager
+# nix-channel --update
+----
+
+and if you follow a Nixpkgs version 20.09 channel, you can run
+
+[source,console]
+----
+# nix-channel --add https://github.com/nix-community/home-manager/archive/release-20.09.tar.gz home-manager
+# nix-channel --update
+----
+
+It is then possible to add
+
+[source,nix]
+imports = [ <home-manager/nixos> ];
+
+to your system `configuration.nix` file, which will introduce a new
+NixOS option called `home-manager.users` whose type is an attribute
+set that maps user names to Home Manager configurations.
+
+For example, a NixOS configuration may include the lines
+
+[source,nix]
+----
+users.users.eve.isNormalUser = true;
+home-manager.users.eve = { pkgs, ... }: {
+ home.packages = [ pkgs.atool pkgs.httpie ];
+ programs.bash.enable = true;
+};
+----
+
+and after a `nixos-rebuild switch` the user eve's environment should
+include a basic Bash configuration and the packages atool and httpie.
+
+[NOTE]
+====
+By default packages will be installed to `$HOME/.nix-profile` but they
+can be installed to `/etc/profiles` if
+
+[source,nix]
+home-manager.useUserPackages = true;
+
+is added to the system configuration. This is necessary if, for
+example, you wish to use `nixos-rebuild build-vm`. This option may
+become the default value in the future.
+====
+
+[NOTE]
+====
+By default, Home Manager uses a private `pkgs` instance that is
+configured via the `home-manager.users.<name>.nixpkgs` options. To
+instead use the global `pkgs` that is configured via the system level
+`nixpkgs` options, set
+
+[source,nix]
+home-manager.useGlobalPkgs = true;
+
+This saves an extra Nixpkgs evaluation, adds consistency, and removes
+the dependency on `NIX_PATH`, which is otherwise used for importing
+Nixpkgs.
+====
+
+[[sec-install-nix-darwin-module]]
+=== nix-darwin module
+
+Home Manager provides a module that allows you to prepare user
+environments directly from the {nix-darwin}[nix-darwin] configuration
+file, which often is more convenient than using the `home-manager`
+tool.
+
+To make the NixOS module available for use you must `import` it into
+your system configuration. This is most conveniently done by adding a
+Home Manager channel. For example, if you are following Nixpkgs master
+or an unstable channel, you can run
+
+[source,console]
+----
+# nix-channel --add https://github.com/nix-community/home-manager/archive/master.tar.gz home-manager
+# nix-channel --update
+----
+
+and if you follow a Nixpkgs version 20.09 channel, you can run
+
+[source,console]
+----
+# nix-channel --add https://github.com/nix-community/home-manager/archive/release-20.09.tar.gz home-manager
+# nix-channel --update
+----
+
+It is then possible to add
+
+[source,nix]
+imports = [ <home-manager/nix-darwin> ];
+
+to your nix-darwin `configuration.nix` file, which will introduce a
+new NixOS option called `home-manager` whose type is an attribute set
+that maps user names to Home Manager configurations.
+
+For example, a nix-darwin configuration may include the lines
+
+[source,nix]
+----
+home-manager.users.eve = { pkgs, ... }: {
+ home.packages = [ pkgs.atool pkgs.httpie ];
+ programs.bash.enable = true;
+};
+----
+
+and after a `darwin-rebuild --switch` the user eve's environment
+should include a basic Bash configuration and the packages atool and
+httpie.
+
+[NOTE]
+====
+By default user packages will not be ignored in favor of
+`environment.systemPackages`, but they will be intalled to
+`/etc/profiles/per-user/$USERNAME` if
+
+[source,nix]
+home-manager.useUserPackages = true;
+
+is added to the nix-darwin configuration. This option may become the
+default value in the future.
+====
+
+[NOTE]
+====
+By default, Home Manager uses a private `pkgs` instance that is
+configured via the `home-manager.users.<name>.nixpkgs` options. To
+instead use the global `pkgs` that is configured via the system level
+`nixpkgs` options, set
+
+[source,nix]
+home-manager.useGlobalPkgs = true;
+
+This saves an extra Nixpkgs evaluation, adds consistency, and removes
+the dependency on `NIX_PATH`, which is otherwise used for importing
+Nixpkgs.
+====
diff --git a/infra/libkookie/home-manager/doc/man-configuration.xml b/infra/libkookie/home-manager/doc/man-configuration.xml
new file mode 100644
index 000000000000..42962a75f3c9
--- /dev/null
+++ b/infra/libkookie/home-manager/doc/man-configuration.xml
@@ -0,0 +1,40 @@
+<refentry xmlns="http://docbook.org/ns/docbook"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+ <refmeta>
+ <refentrytitle><filename>home-configuration.nix</filename></refentrytitle>
+ <manvolnum>5</manvolnum>
+ <refmiscinfo class="source">Home Manager</refmiscinfo>
+<!-- <refmiscinfo class="version"><xi:include href="version.txt" parse="text"/></refmiscinfo> -->
+ </refmeta>
+ <refnamediv>
+ <refname><filename>home-configuration.nix</filename></refname>
+ <refpurpose>Home Manager configuration specification</refpurpose>
+ </refnamediv>
+ <refsection>
+ <title>Description</title>
+ <para>
+ The file <filename>~/.config/nixpkgs/home.nix</filename> contains the
+ declarative specification of your Home Manager configuration. The command
+ <command>home-manager</command> takes this file and realises the user
+ environment configuration specified therein.
+ </para>
+ </refsection>
+ <refsection>
+ <title>Options</title>
+ <para>
+ You can use the following options in
+ <filename>home-configuration.nix</filename>:
+ </para>
+ <xi:include href="./nmd-result/home-manager-options.xml" />
+ </refsection>
+ <refsection>
+ <title>See also</title>
+ <para>
+ <citerefentry>
+ <refentrytitle>home-manager</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ </para>
+ </refsection>
+</refentry>
diff --git a/infra/libkookie/home-manager/doc/man-home-manager.xml b/infra/libkookie/home-manager/doc/man-home-manager.xml
new file mode 100644
index 000000000000..3bfe023026a2
--- /dev/null
+++ b/infra/libkookie/home-manager/doc/man-home-manager.xml
@@ -0,0 +1,539 @@
+<refentry xmlns="http://docbook.org/ns/docbook"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+ <refmeta>
+ <refentrytitle><command>home-manager</command>
+ </refentrytitle><manvolnum>1</manvolnum>
+ <refmiscinfo class="source">Home Manager</refmiscinfo>
+ </refmeta>
+ <refnamediv>
+ <refname><command>home-manager</command>
+ </refname><refpurpose>reconfigure a user environment</refpurpose>
+ </refnamediv>
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>home-manager</command> <group choice="req">
+ <arg choice="plain">
+ build
+ </arg>
+
+ <arg choice="plain">
+ instantiate
+ </arg>
+
+ <arg choice="plain">
+ edit
+ </arg>
+
+ <arg choice="plain">
+ expire-generations <replaceable>timestamp</replaceable>
+ </arg>
+
+ <arg choice="plain">
+ generations
+ </arg>
+
+ <arg choice="plain">
+ help
+ </arg>
+
+ <arg choice="plain">
+ news
+ </arg>
+
+ <arg choice="plain">
+ packages
+ </arg>
+
+ <arg choice="plain">
+ remove-generations <replaceable>ID …</replaceable>
+ </arg>
+
+ <arg choice="plain">
+ switch
+ </arg>
+
+ <arg choice="plain">
+ uninstall
+ </arg>
+ </group>
+ <sbr />
+ <arg>
+ -A <replaceable>attrPath</replaceable>
+ </arg>
+
+ <arg>
+ -I <replaceable>path</replaceable>
+ </arg>
+
+ <arg>
+ -b <replaceable>ext</replaceable>
+ </arg>
+
+ <arg>
+ <group choice="req">
+ <arg choice="plain">
+ -f
+ </arg>
+
+ <arg choice="plain">
+ --file
+ </arg>
+ </group> <replaceable>path</replaceable>
+ </arg>
+
+ <arg>
+ <group choice="req">
+ <arg choice="plain">
+ -h
+ </arg>
+
+ <arg choice="plain">
+ --help
+ </arg>
+ </group>
+ </arg>
+
+ <arg>
+ <group choice="req">
+ <arg choice="plain">
+ -n
+ </arg>
+
+ <arg choice="plain">
+ --dry-run
+ </arg>
+ </group>
+ </arg>
+
+ <arg>
+ --option <replaceable>name</replaceable> <replaceable>value</replaceable>
+ </arg>
+
+ <arg>
+ --cores <replaceable>number</replaceable>
+ </arg>
+
+ <arg>
+ <group choice="req">
+ <arg choice="plain">
+ -j
+ </arg>
+
+ <arg choice="plain">
+ --max-jobs
+ </arg>
+ </group>
+ <replaceable>number</replaceable>
+ </arg>
+
+ <arg>
+ --keep-failed
+ </arg>
+
+ <arg>
+ --keep-going
+ </arg>
+
+ <arg>
+ --show-trace
+ </arg>
+
+ <arg>
+ --(no-)substitute
+ </arg>
+
+ <arg>
+ <group choice="req">
+ <arg choice="plain">
+ -v
+ </arg>
+
+ <arg choice="plain">
+ --verbose
+ </arg>
+ </group>
+ </arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+ <refsection>
+ <title>Description</title>
+ <para>
+ This command updates the user environment so that it corresponds to the
+ configuration specified in <filename>~/.config/nixpkgs/home.nix</filename>.
+ </para>
+ <para>
+ All operations using this tool expects a sub-command that indicates the
+ operation to perform. It must be one of
+ <variablelist>
+ <varlistentry>
+ <term>
+ <option>build</option>
+ </term>
+ <listitem>
+ <para>
+ Build configuration into a <filename>result</filename> directory.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>instantiate</option>
+ </term>
+ <listitem>
+ <para>
+ Instantiate the configuration and print the resulting derivation.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>edit</option>
+ </term>
+ <listitem>
+ <para>
+ Open the home configuration using the editor indicated by
+ <envar>EDITOR</envar>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>expire-generations <replaceable>timestamp</replaceable></option>
+ </term>
+ <listitem>
+ <para>
+ Remove generations older than <replaceable>timestamp</replaceable> where
+ <replaceable>timestamp</replaceable> is interpreted as in the
+ <option>-d</option> argument of the <citerefentry>
+ <refentrytitle>date</refentrytitle>
+ <manvolnum>1</manvolnum> </citerefentry> tool. For example <literal>-30
+ days</literal> or <literal>2018-01-01</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>generations</option>
+ </term>
+ <listitem>
+ <para>
+ List all home environment generations.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>help</option>
+ </term>
+ <listitem>
+ <para>
+ Print tool help.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>news</option>
+ </term>
+ <listitem>
+ <para>
+ Show news entries in a pager.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>packages</option>
+ </term>
+ <listitem>
+ <para>
+ List all packages installed in <varname>home-manager-path</varname>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>remove-generations <replaceable>ID …</replaceable></option>
+ </term>
+ <listitem>
+ <para>
+ Remove indicated generations. Use the <option>generations</option>
+ sub-command to find suitable generation numbers.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>switch</option>
+ </term>
+ <listitem>
+ <para>
+ Build and activate the configuration.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>uninstall</option>
+ </term>
+ <listitem>
+ <para>
+ Remove Home Manager from the user environment. This will
+ <itemizedlist>
+ <listitem>
+ <para>
+ remove all managed files from the home directory,
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ remove packages installed through Home Manager from the user profile,
+ and
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ optionally remove all Home Manager generations and make them
+ available for immediate garbage collection.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </refsection>
+ <refsection>
+ <title>Options</title>
+ <para>
+ The tool accepts the options
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>
+ <option>-A <replaceable>attrPath</replaceable></option>
+ </term>
+ <listitem>
+ <para>
+ Optional attribute that selects a configuration expression in the
+ configuration file. That is, if <filename>home.nix</filename> contains
+<programlisting language="nix">
+{
+ joe-at-work = {pkgs, ...}: { home.packages = [ pkgs.fortune ]; };
+ joe-at-home = {pkgs, ...}: { home.packages = [ pkgs.cowsay ]; };
+}
+</programlisting>
+ then the command <command>home-manager switch -A joe-at-work</command>
+ will activate the profile containing the fortune program.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-I <replaceable>path</replaceable></option>
+ </term>
+ <listitem>
+ <para>
+ Add a path to the Nix expression search path. For example, to build a
+ Home Manager profile using a specific Nixpkgs run <command>home-manager
+ -I nixpkgs=/absolute/path/to/nixpkgs build</command>. By default
+ <literal>&lt;nixpkgs&gt;</literal> is used.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-b <replaceable>extension</replaceable></option>
+ </term>
+ <listitem>
+ <para>
+ Enable automatic resolution of collisions between unmanaged and managed
+ files. The name of the original file will be suffixed by the given
+ extension. For example,
+<screen>
+<prompt>$</prompt> <userinput>home-manager -b bck switch</userinput>
+</screen>
+ will cause a colliding file <filename>~/.config/foo.conf</filename> to be
+ moved to <filename>~/.config/foo.conf.bck</filename>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-f <replaceable>path</replaceable></option>
+ </term>
+ <term>
+ <option>--file <replaceable>path</replaceable></option>
+ </term>
+ <listitem>
+ <para>
+ Indicates the path to the Home Manager configuration file. If not given,
+ <filename>~/.config/nixpkgs/home.nix</filename> is used.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-h</option>
+ </term>
+ <term>
+ <option>--help</option>
+ </term>
+ <listitem>
+ <para>
+ Prints usage information for the <command>home-manager</command> tool.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-n</option>
+ </term>
+ <term>
+ <option>--dry-run</option>
+ </term>
+ <listitem>
+ <para>
+ Perform a dry-run of the given operation, only prints what actions would
+ be taken.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>--option <replaceable>name</replaceable> <replaceable>value</replaceable></option>
+ </term>
+ <listitem>
+ <para>
+ Passed on to <citerefentry>
+ <refentrytitle>nix-build</refentrytitle>
+ <manvolnum>1</manvolnum> </citerefentry>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>--cores <replaceable>number</replaceable></option>
+ </term>
+ <listitem>
+ <para>
+ Passed on to <citerefentry>
+ <refentrytitle>nix-build</refentrytitle>
+ <manvolnum>1</manvolnum> </citerefentry>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-j <replaceable>number</replaceable></option>
+ </term>
+ <term>
+ <option>--max-jobs <replaceable>number</replaceable></option>
+ </term>
+ <listitem>
+ <para>
+ Passed on to <citerefentry>
+ <refentrytitle>nix-build</refentrytitle>
+ <manvolnum>1</manvolnum> </citerefentry>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>--keep-failed</option>
+ </term>
+ <listitem>
+ <para>
+ Passed on to <citerefentry>
+ <refentrytitle>nix-build</refentrytitle>
+ <manvolnum>1</manvolnum> </citerefentry>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>--keep-going</option>
+ </term>
+ <listitem>
+ <para>
+ Passed on to <citerefentry>
+ <refentrytitle>nix-build</refentrytitle>
+ <manvolnum>1</manvolnum> </citerefentry>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>--show-trace</option>
+ </term>
+ <listitem>
+ <para>
+ Passed on to <citerefentry>
+ <refentrytitle>nix-build</refentrytitle>
+ <manvolnum>1</manvolnum> </citerefentry>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>--(no-)substitute</option>
+ </term>
+ <listitem>
+ <para>
+ Passed on to <citerefentry>
+ <refentrytitle>nix-build</refentrytitle>
+ <manvolnum>1</manvolnum> </citerefentry>.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-v</option>
+ </term>
+ <term>
+ <option>--verbose</option>
+ </term>
+ <listitem>
+ <para>
+ Activates verbose output.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsection>
+ <refsection>
+ <title>Files</title>
+ <variablelist>
+ <varlistentry>
+ <term>
+ <filename>~/.local/share/home-manager/news-read-ids</filename>
+ </term>
+ <listitem>
+ <para>
+ Identifiers of news items that have been shown. Can be deleted to reset
+ the read news indicator.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsection>
+ <refsection>
+ <title>Bugs</title>
+ <para>
+ Please report any bugs on the
+ <link
+ xlink:href="https://github.com/nix-community/home-manager/issues">project
+ issue tracker</link>.
+ </para>
+ </refsection>
+ <refsection>
+ <title>See also</title>
+ <para>
+ <citerefentry>
+ <refentrytitle>home-configuration.nix</refentrytitle>
+ <manvolnum>5</manvolnum> </citerefentry>
+ </para>
+ </refsection>
+</refentry>
diff --git a/infra/libkookie/home-manager/doc/man-pages.xml b/infra/libkookie/home-manager/doc/man-pages.xml
new file mode 100644
index 000000000000..bb484ae019b0
--- /dev/null
+++ b/infra/libkookie/home-manager/doc/man-pages.xml
@@ -0,0 +1,12 @@
+<reference xmlns="http://docbook.org/ns/docbook"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+ <title>Home Manager Reference Pages</title>
+ <info>
+ <author><personname>Home Manager contributors</personname></author>
+ <copyright><year>2017–2020</year><holder>Home Manager contributors</holder>
+ </copyright>
+ </info>
+ <xi:include href="man-configuration.xml" />
+ <xi:include href="man-home-manager.xml" />
+</reference>
diff --git a/infra/libkookie/home-manager/doc/manual.xml b/infra/libkookie/home-manager/doc/manual.xml
new file mode 100644
index 000000000000..314d7c10a961
--- /dev/null
+++ b/infra/libkookie/home-manager/doc/manual.xml
@@ -0,0 +1,46 @@
+<book xmlns="http://docbook.org/ns/docbook"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ version="5.0"
+ xml:id="book-home-manager-manual">
+ <info>
+ <title>Home Manager Manual</title>
+ </info>
+ <preface>
+ <title>Preface</title>
+ <para>
+ This manual will eventually describes how to install, use, and extend Home
+ Manager.
+ </para>
+ <para>
+ If you encounter problems then please reach out on the IRC channel
+ <link xlink:href="https://webchat.freenode.net/?url=irc%3A%2F%2Firc.freenode.net%2Fhome-manager">#home-manager</link>
+ hosted by <link xlink:href="https://freenode.net/">freenode</link>.
+ The <link xlink:href="https://logs.nix.samueldr.com/home-manager/">channel logs</link>
+ are hosted courtesy of <link xlink:href="https://github.com/samueldr/">samueldr</link>.
+ If your problem is caused by a bug in Home Manager then it should
+ be reported on the
+ <link xlink:href="https://github.com/nix-community/home-manager/issues">Home Manager issue tracker</link>.
+ </para>
+ <note>
+ <para>
+ Commands prefixed with <literal>#</literal> have to be run as root, either
+ requiring to login as root user or temporarily switching to it using
+ <literal>sudo</literal> for example.
+ </para>
+ </note>
+ </preface>
+ <xi:include href="installation.xml" />
+ <xi:include href="writing-modules.xml" />
+ <xi:include href="contributing.xml" />
+ <xi:include href="faq.xml" />
+ <appendix xml:id="ch-options">
+ <title>Configuration Options</title>
+ <xi:include href="./nmd-result/home-manager-options.xml" />
+ </appendix>
+ <appendix xml:id="ch-tools">
+ <title>Tools</title>
+ <xi:include href="./man-home-manager.xml" />
+ </appendix>
+ <xi:include href="./release-notes/release-notes.xml" />
+</book>
diff --git a/infra/libkookie/home-manager/doc/release-notes/release-notes.adoc b/infra/libkookie/home-manager/doc/release-notes/release-notes.adoc
new file mode 100644
index 000000000000..e89e588fe1e5
--- /dev/null
+++ b/infra/libkookie/home-manager/doc/release-notes/release-notes.adoc
@@ -0,0 +1,21 @@
+[[ch-release-notes]]
+[appendix]
+== Release Notes
+
+This section lists the release notes for stable versions of Home Manager and the current unstable version.
+
+:leveloffset: 1
+
+include::rl-2103.adoc[]
+
+include::rl-2009.adoc[]
+
+include::rl-2003.adoc[]
+
+include::rl-1909.adoc[]
+
+include::rl-1903.adoc[]
+
+include::rl-1809.adoc[]
+
+:leveloffset: 0
diff --git a/infra/libkookie/home-manager/doc/release-notes/rl-1809.adoc b/infra/libkookie/home-manager/doc/release-notes/rl-1809.adoc
new file mode 100644
index 000000000000..b363704e241f
--- /dev/null
+++ b/infra/libkookie/home-manager/doc/release-notes/rl-1809.adoc
@@ -0,0 +1,4 @@
+[[sec-release-18.09]]
+== Release 18.09
+
+The 18.09 release branch became the stable branch in September, 2018.
diff --git a/infra/libkookie/home-manager/doc/release-notes/rl-1903.adoc b/infra/libkookie/home-manager/doc/release-notes/rl-1903.adoc
new file mode 100644
index 000000000000..1cba4235d60d
--- /dev/null
+++ b/infra/libkookie/home-manager/doc/release-notes/rl-1903.adoc
@@ -0,0 +1,59 @@
+[[sec-release-19.03]]
+== Release 19.03
+
+The 19.03 release branch became the stable branch in April, 2019.
+
+[[sec-release-19.03-highlights]]
+=== Highlights
+:opt-home-file-source: opt-home.file._name_.source
+
+This release has the following notable changes:
+
+* The <<{opt-home-file-source}>> option now allows source files to be
+hidden, that is, having a name starting with the `.` character. It
+also allows the source file name to contain characters not typically
+allowed for Nix store paths. For example, your configuration can now
+contain things such as
++
+[source,nix]
+----
+home.file."my file".source = ./. + "/file with spaces!";
+----
+
+* The type used for the systemd unit options under
+<<opt-systemd.user.services>>, <<opt-systemd.user.sockets>>, etc. has
+been changed to offer more robust merging of configurations. If you
+don't override values within systemd units then you are not affected
+by this change. Unfortunately, if you do override unit values you may
+encounter errors.
++
+In particular, if you get an error saying that a ``unique option'' is
+``defined multiple times'' then you need to use the
+https://nixos.org/nixos/manual/#sec-option-definitions-setting-priorities[`mkForce`]
+function. For example,
++
+[source,nix]
+----
+systemd.user.services.foo.Service.ExecStart = "/foo/bar";
+----
++
+becomes
++
+[source,nix]
+----
+systemd.user.services.foo.Service.ExecStart = lib.mkForce "/foo/bar";
+----
++
+We had to make this change because the old merging was causing too
+many confusing situations for people.
+
+[[sec-release-19.03-state-version-changes]]
+=== State Version Changes
+
+The state version in this release includes the changes below. These
+changes are only active if the <<opt-home.stateVersion>> option is set
+to ``19.03'' or later.
+
+* There is now an option <<opt-programs.beets.enable>> that defaults
+to `false`. Before the module would be active if the
+<<opt-programs.beets.settings>> option was non-empty.
diff --git a/infra/libkookie/home-manager/doc/release-notes/rl-1909.adoc b/infra/libkookie/home-manager/doc/release-notes/rl-1909.adoc
new file mode 100644
index 000000000000..89bbbdc2b4f5
--- /dev/null
+++ b/infra/libkookie/home-manager/doc/release-notes/rl-1909.adoc
@@ -0,0 +1,31 @@
+[[sec-release-19.09]]
+== Release 19.09
+
+The 19.09 release branch became the stable branch in October, 2019.
+
+[[sec-release-19.09-highlights]]
+=== Highlights
+
+This release has the following notable changes:
+
+* The `programs.firefox.enableGoogleTalk` and
+ `programs.firefox.enableIcedTea` options are now deprecated
+ and will only work if Firefox ESR 52.x is used.
+
+* The `home-manager` tool now provides an `uninstall` sub-command that
+ can be used to uninstall Home Manager, if used in the standalone
+ mode. That is, not as a NixOS module.
+
+[[sec-release-19.09-state-version-changes]]
+=== State Version Changes
+
+The state version in this release includes the changes below. These
+changes are only active if the `home.stateVersion` option is set to
+"19.09" or later.
+
+* The <<opt-programs.firefox.package>> option now expects a wrapped
+ Firefox package and defaults to `pkgs.firefox`.
+
+* The options <<opt-home.keyboard.layout>> and
+ <<opt-home.keyboard.variant>> now default to `null`, which indicates
+ that the system value should be used.
diff --git a/infra/libkookie/home-manager/doc/release-notes/rl-2003.adoc b/infra/libkookie/home-manager/doc/release-notes/rl-2003.adoc
new file mode 100644
index 000000000000..5832e2e5ab58
--- /dev/null
+++ b/infra/libkookie/home-manager/doc/release-notes/rl-2003.adoc
@@ -0,0 +1,126 @@
+[[sec-release-20.03]]
+== Release 20.03
+
+The 20.03 release branch became the stable branch in April, 2020.
+
+[[sec-release-20.03-highlights]]
+=== Highlights
+
+This release has the following notable changes:
+
+* Assigning a list to the <<opt-home.file>>, <<opt-xdg.configFile>>,
+and <<opt-xdg.dataFile>> options is now deprecated and will produce a
+warning message if used. Specifically, if your configuration currently
+contains something like
++
+[source,nix]
+----
+home.file = [
+ {
+ target = ".config/foo.txt";
+ text = "bar";
+ }
+]
+----
++
+then it should be updated to instead use the equivalent attribute set form
++
+[source,nix]
+----
+home.file = {
+ ".config/foo.txt".text = "bar";
+}
+----
++
+Support for the list form will be removed in Home Manager version
+20.09.
+
+* The `lib` function attribute given to modules is now enriched with
+an attribute `hm` containing extra library functions specific for Home
+Manager. More specifically, `lib.hm` is now the same as `config.lib`
+and should be the preferred choice since it is more robust.
++
+Therefore, if your configuration makes use of, for example,
+`config.lib.dag` to create activation script blocks, it is recommended
+to change to `lib.hm.dag`.
++
+Note, in the unlikely case that you are
++
+** using Home Manager's NixOS or nix-darwin module,
+** have made your own Home Manager module containing an top-level
+ option named `config` or `options`, and
+** assign to this option in your system configuration inside a plain
+ attribute set, i.e., without a function argument,
+
++
+then you must update your configuration to perform the option
+assignment inside a `config` attribute. For example, instead of
++
+[source,nix]
+----
+home-manager.users.jane = { config = "foo"; };
+----
++
+use
++
+[source,nix]
+----
+home-manager.users.jane = { config.config = "foo"; };
+----
+
+* The `services.compton` module has been deprecated and instead the
+new module `services.picom` should be used. This is because Nixpkgs no
+longer packages compton, and instead packages the (mostly) compatible
+fork called picom.
+
+* The list form of the <<opt-programs.ssh.matchBlocks>> option has
+been deprecated and configurations requiring match blocks in a defined
+order should switch to using DAG entries instead. For example, a
+configuration
++
+[source,nix]
+----
+programs.ssh.matchBlocks = [
+ {
+ host = "alpha.foo.com";
+ user = "jd";
+ }
+ {
+ host = "*.foo.com";
+ user = "john.doe";
+ }
+];
+----
++
+can be expressed along the lines of
++
+[source,nix]
+----
+programs.ssh.matchBlocks = {
+ "*.example.com" = {
+ user = "john.doe";
+ }
+ "alpha.example.com" = lib.hm.dag.entryBefore ["*.example.com"] {
+ user = "jd";
+ }
+};
+----
++
+Support for the list form will be removed in Home Manager version
+20.09.
+
+[[sec-release-20.03-state-version-changes]]
+=== State Version Changes
+
+The state version in this release includes the changes below. These
+changes are only active if the `home.stateVersion` option is set to
+"20.03" or later.
+
+* The <<opt-programs.zsh.history.path>> option is no longer prepended
+ by `$HOME`, which allows specifying absolute paths, for example,
+ using the xdg module. Also, the default value is fixed to
+ `$HOME/.zsh_history` and `dotDir` path is not prepended to it
+ anymore.
+* The newsboat module will now default in displaying `queries` before `urls` in
+ its main window. This makes sense in the case when one has a lot of URLs and
+ few queries.
diff --git a/infra/libkookie/home-manager/doc/release-notes/rl-2009.adoc b/infra/libkookie/home-manager/doc/release-notes/rl-2009.adoc
new file mode 100644
index 000000000000..a3de0260c7d1
--- /dev/null
+++ b/infra/libkookie/home-manager/doc/release-notes/rl-2009.adoc
@@ -0,0 +1,96 @@
+[[sec-release-20.09]]
+== Release 20.09
+
+The 20.09 release branch became the stable branch in late September, 2020.
+
+[[sec-release-20.09-highlights]]
+=== Highlights
+
+This release has the following notable changes:
+
+* Nothing has happened.
+
+[[sec-release-20.09-state-version-changes]]
+=== State Version Changes
+
+The state version in this release includes the changes below. These
+changes are only active if the `home.stateVersion` option is set to
+"20.09" or later.
+
+* The options <<opt-home.homeDirectory>> and <<opt-home.username>> no
+longer have default values and must therefore be provided in your
+configuration. Previously their values would default to the content of
+the environment variables `HOME` and `USER`, respectively.
++
+--
+Further, the options <<opt-xdg.cacheHome>>, <<opt-xdg.configHome>>,
+and <<opt-xdg.dataHome>> will no longer be affected by the
+`XDG_CACHE_HOME`, `XDG_CONFIG_HOME`, and `XDG_DATA_HOME` environment
+variables. They now unconditionally default to
+
+- `"${config.home.homeDirectory}/.cache"`,
+- `"${config.home.homeDirectory}/.config"`, and
+- `"${config.home.homeDirectory}/.local/share"`.
+
+If you choose to switch to state version 20.09 then you must set these
+options if you use non-default XDG base directory paths.
+
+The initial configuration generated by
+
+[source,console]
+$ nix-shell '<home-manager>' -A install
+
+will automatically include these options, when necessary.
+--
+
+* Git's `smtpEncryption` option is now set to `tls` only if both <<opt-accounts.email.accounts.\_name_.smtp.tls.enable>> and <<opt-accounts.email.accounts.\_name_.smtp.tls.useStartTls>> are `true`. If only <<opt-accounts.email.accounts.\_name_.smtp.tls.enable>> is `true`, `ssl` is used instead.
+
+* The `nixpkgs` module no longer references `<nixpkgs>`. Before it would do so when building the `pkgs` module argument. Starting with state version 20.09, the `pkgs` argument is instead built from the same Nixpkgs that was used to initialize the Home Manager modules. This is useful, for example, when using Home Manager within a Nix Flake. If you want to keep using `<nixpkgs>` with state version ≥ 20.09 then add
++
+[source,nix]
+_module.args.pkgsPath = <nixpkgs>;
++
+to your Home Manager configuration.
+
+* The options `wayland.windowManager.sway.config.bars` and `opt-xsession.windowManager.i3.config.bars` have been changed so that most of the suboptions are now nullable and default to `null`. The default for these two options has been changed to manually set the old defaults for each suboption. The overall effect is that if the `bars` options is not set, then the default remains the same. On the other hand, something like:
++
+--
+[source,nix]
+----
+bars = [ {
+ command = "waybar";
+} ];
+----
+will now create the config:
+....
+bar {
+ swaybar_command waybar
+}
+....
+instead of
+....
+bar {
+
+ font pango:monospace 8
+ mode dock
+ hidden_state hide
+ position bottom
+ status_command /nix/store/h7s6i9q1z5fxrlyyw5ls8vqxhf5bcs5a-i3status-2.13/bin/i3status
+ swaybar_command waybar
+ workspace_buttons yes
+ strip_workspace_numbers no
+ tray_output primary
+ colors {
+ background #000000
+ statusline #ffffff
+ separator #666666
+ focused_workspace #4c7899 #285577 #ffffff
+ active_workspace #333333 #5f676a #ffffff
+ inactive_workspace #333333 #222222 #888888
+ urgent_workspace #2f343a #900000 #ffffff
+ binding_mode #2f343a #900000 #ffffff
+ }
+
+}
+....
+--
diff --git a/infra/libkookie/home-manager/doc/release-notes/rl-2103.adoc b/infra/libkookie/home-manager/doc/release-notes/rl-2103.adoc
new file mode 100644
index 000000000000..7d5b55d49a2b
--- /dev/null
+++ b/infra/libkookie/home-manager/doc/release-notes/rl-2103.adoc
@@ -0,0 +1,42 @@
+[[sec-release-21.03]]
+== Release 21.03
+
+This is the current unstable branch and the information in this
+section is therefore not final.
+
+[[sec-release-21.03-highlights]]
+=== Highlights
+
+This release has the following notable changes:
+
+* The <<opt-programs.broot.verbs>> option is now a list rather than an
+attribute set. To migrate, move the keys of the attrset into the list
+items' `invocation` keys. For example,
++
+[source,nix]
+----
+programs.broot.verbs = {
+ "p" = { execution = ":parent"; };
+};
+----
++
+becomes
++
+[source,nix]
+----
+programs.broot.verbs = [
+ {
+ invocation = "p";
+ execution = ":parent";
+ }
+];
+----
+
+[[sec-release-21.03-state-version-changes]]
+=== State Version Changes
+
+The state version in this release includes the changes below. These
+changes are only active if the `home.stateVersion` option is set to
+"21.03" or later.
+
+* Nothing has happened.
diff --git a/infra/libkookie/home-manager/doc/writing-modules.adoc b/infra/libkookie/home-manager/doc/writing-modules.adoc
new file mode 100644
index 000000000000..0f3336ff2c06
--- /dev/null
+++ b/infra/libkookie/home-manager/doc/writing-modules.adoc
@@ -0,0 +1,187 @@
+[[ch-writing-modules]]
+== Writing Home Manager Modules
+:writing-nixos-modules: https://nixos.org/nixos/manual/index.html#sec-writing-modules
+
+The module system in Home Manager is based entirely on the NixOS module system so we will here only highlight aspects that are specific for Home Manager. For information about the module system as such please refer to the {writing-nixos-modules}[Writing NixOS Modules] chapter of the NixOS manual.
+
+[[sec-option-types]]
+=== Option Types
+:wikipedia-dag: https://en.wikipedia.org/w/index.php?title=Directed_acyclic_graph&oldid=939656095
+:gvariant-description: https://developer.gnome.org/glib/stable/glib-GVariant.html#glib-GVariant.description
+
+Overall the basic option types are the same in Home Manager as NixOS. A few Home Manager options, however, make use of custom types that are worth describing in more detail. These are the option types `dagOf` and `gvariant` that are used, for example, by <<opt-programs.ssh.matchBlocks>> and <<opt-dconf.settings>>.
+
+`hm.types.dagOf`::
+Options of this type have attribute sets as values where each member is a node in a {wikipedia-dag}[directed acyclic graph] (DAG). This allows the attribute set entries to express dependency relations among themselves. This can, for example, be used to control the order of match blocks in a OpenSSH client configuration or the order of activation script blocks in <<opt-home.activation>>.
++
+A number of functions are provided to create DAG nodes. The functions are shown below with examples using an option `foo.bar` of type `hm.types.dagOf types.int`.
++
+`hm.dag.entryAnywhere (value: T)`:::
+Indicates that `value` can be placed anywhere within the DAG. This is also the default for plain attribute set entries, that is
++
+[source,nix]
+----
+foo.bar = {
+ a = hm.dag.entryAnywhere 0;
+}
+----
++
+and
++
+[source,nix]
+----
+foo.bar = {
+ a = 0;
+}
+----
++
+are equivalent.
++
+`hm.dag.entryAfter (afters: list string) (value: T)`:::
+Indicates that `value` must be placed _after_ each of the attribute names in the given list. For example
++
+[source,nix]
+----
+foo.bar = {
+ a = 0;
+ b = hm.dag.entryAfter [ "a" ] 1;
+}
+----
++
+would place `b` after `a` in the graph.
++
+`hm.dag.entryBefore (befores: list string) (value: T)`:::
+Indicates that `value` must be placed _before_ each of the attribute names in the given list. For example
++
+[source,nix]
+----
+foo.bar = {
+ b = hm.dag.entryBefore [ "a" ] 1;
+ a = 0;
+}
+----
++
+would place `b` before `a` in the graph.
++
+`hm.dag.entryBetween (befores: list string) (afters: list string) (value: T)`:::
+Indicates that `value` must be placed _before_ the attribute names in the first list and _after_ the attribute names in the second list. For example
++
+[source,nix]
+----
+foo.bar = {
+ a = 0;
+ c = hm.dag.entryBetween [ "b" ] [ "a" ] 2;
+ b = 1;
+}
+----
++
+would place `c` before `b` and after `a` in the graph.
+
+`hm.types.gvariant`::
+This type is useful for options representing {gvariant-description}[GVariant] values. The type accepts all primitive GVariant types as well as arrays and tuples. Dictionaries are not currently supported.
++
+To create a GVariant value you can use a number of provided functions. Examples assume an option `foo.bar` of type `hm.types.gvariant`.
++
+`hm.gvariant.mkBoolean (v: bool)`:::
+Takes a Nix value `v` to a GVariant `boolean` value. Note, Nix booleans are automatically coerced using this function. That is,
++
+[source,nix]
+----
+foo.bar = hm.gvariant.mkBoolean true;
+----
++
+is equivalent to
++
+[source,nix]
+----
+foo.bar = true;
+----
+`hm.gvariant.mkString (v: string)`:::
+Takes a Nix value `v` to a GVariant `string` value. Note, Nix strings are automatically coerced using this function. That is,
++
+[source,nix]
+----
+foo.bar = hm.gvariant.mkString "a string";
+----
++
+is equivalent to
++
+[source,nix]
+----
+foo.bar = "a string";
+----
+`hm.gvariant.mkObjectpath (v: string)`:::
+Takes a Nix value `v` to a GVariant `objectpath` value.
+`hm.gvariant.mkUchar (v: string)`:::
+Takes a Nix value `v` to a GVariant `uchar` value.
+`hm.gvariant.mkInt16 (v: int)`:::
+Takes a Nix value `v` to a GVariant `int16` value.
+`hm.gvariant.mkUint16 (v: int)`:::
+Takes a Nix value `v` to a GVariant `uint16` value.
+`hm.gvariant.mkInt32 (v: int)`:::
+Takes a Nix value `v` to a GVariant `int32` value. Note, Nix integers are automatically coerced using this function. That is,
++
+[source,nix]
+----
+foo.bar = hm.gvariant.mkInt32 7;
+----
++
+is equivalent to
++
+[source,nix]
+----
+foo.bar = 7;
+----
+`hm.gvariant.mkUint32 (v: int)`:::
+Takes a Nix value `v` to a GVariant `uint32` value.
+`hm.gvariant.mkInt64 (v: int)`:::
+Takes a Nix value `v` to a GVariant `int64` value.
+`hm.gvariant.mkUint64 (v: int)`:::
+Takes a Nix value `v` to a GVariant `uint64` value.
+`hm.gvariant.mkDouble (v: double)`:::
+Takes a Nix value `v` to a GVariant `double` value. Note, Nix floats are automatically coerced using this function. That is,
++
+[source,nix]
+----
+foo.bar = hm.gvariant.mkDouble 3.14;
+----
++
+is equivalent to
++
+[source,nix]
+----
+foo.bar = 3.14;
+----
++
+`hm.gvariant.mkArray type elements`:::
+Builds a GVariant array containing the given list of elements, where each element is a GVariant value of the given type. The `type` value can be constructed using
++
+--
+- `hm.gvariant.type.string`
+- `hm.gvariant.type.boolean`
+- `hm.gvariant.type.uchar`
+- `hm.gvariant.type.int16`
+- `hm.gvariant.type.uint16`
+- `hm.gvariant.type.int32`
+- `hm.gvariant.type.uint32`
+- `hm.gvariant.type.int64`
+- `hm.gvariant.type.uint64`
+- `hm.gvariant.type.double`
+- `hm.gvariant.type.arrayOf type`
+- `hm.gvariant.type.maybeOf type`
+- `hm.gvariant.type.tupleOf types`
+--
++
+where `type` and `types` are themselves a type and list of types, respectively.
++
+`hm.gvariant.mkEmptyArray type`:::
+An alias of `hm.gvariant.mkArray type []`.
++
+`hm.gvariant.mkNothing type`:::
+Builds a GVariant maybe value whose (non-existent) element is of the given type. The `type` value is constructed as described for the `mkArray` function above.
++
+`hm.gvariant.mkJust element`:::
+Builds a GVariant maybe value containing the given GVariant element.
++
+`hm.gvariant.mkTuple elements`:::
+Builds a GVariant tuple containing the given list of elements, where each element is a GVariant value.
diff --git a/infra/libkookie/home-manager/flake.nix b/infra/libkookie/home-manager/flake.nix
new file mode 100644
index 000000000000..b0aa6095ac63
--- /dev/null
+++ b/infra/libkookie/home-manager/flake.nix
@@ -0,0 +1,24 @@
+{
+ description = "Home Manager for Nix";
+
+ outputs = { self, nixpkgs }: rec {
+ nixosModules.home-manager = import ./nixos;
+
+ darwinModules.home-manager = import ./nix-darwin;
+
+ lib = {
+ hm = import ./modules/lib { lib = nixpkgs.lib; };
+ homeManagerConfiguration = { configuration, system, homeDirectory
+ , username
+ , pkgs ? builtins.getAttr system nixpkgs.outputs.legacyPackages
+ , check ? true }@args:
+ import ./modules {
+ inherit pkgs check;
+ configuration = { ... }: {
+ imports = [ configuration ];
+ home = { inherit homeDirectory username; };
+ };
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/format b/infra/libkookie/home-manager/format
new file mode 100755
index 000000000000..7d48cb8a111b
--- /dev/null
+++ b/infra/libkookie/home-manager/format
@@ -0,0 +1,59 @@
+#! /usr/bin/env nix-shell
+#! nix-shell -I nixpkgs=https://github.com/NixOS/nixpkgs/archive/05f0934825c2a0750d4888c4735f9420c906b388.tar.gz -i bash -p findutils nixfmt
+
+CHECK_ARG=
+
+case $1 in
+ -h)
+ echo "$0 [-c]"
+ ;;
+ -c)
+ CHECK_ARG=-c
+ ;;
+esac
+
+# The first block of excludes are files where nixfmt does a poor job,
+# IMHO. The second block of excludes are files touched by open pull
+# requests and we want to avoid merge conflicts.
+find . -name '*.nix' \
+ ! -path ./modules/programs/irssi.nix \
+ \
+ ! -path ./home-manager/default.nix \
+ ! -path ./home-manager/home-manager.nix \
+ ! -path ./modules/default.nix \
+ ! -path ./modules/files.nix \
+ ! -path ./modules/home-environment.nix \
+ ! -path ./modules/lib/default.nix \
+ ! -path ./modules/lib/file-type.nix \
+ ! -path ./modules/manual.nix \
+ ! -path ./modules/misc/dconf.nix \
+ ! -path ./modules/misc/news.nix \
+ ! -path ./modules/misc/nixpkgs.nix \
+ ! -path ./modules/misc/xdg.nix \
+ ! -path ./modules/modules.nix \
+ ! -path ./modules/programs/afew.nix \
+ ! -path ./modules/programs/bash.nix \
+ ! -path ./modules/programs/firefox.nix \
+ ! -path ./modules/programs/gpg.nix \
+ ! -path ./modules/programs/lesspipe.nix \
+ ! -path ./modules/programs/ssh.nix \
+ ! -path ./modules/programs/tmux.nix \
+ ! -path ./modules/programs/zsh.nix \
+ ! -path ./modules/services/gpg-agent.nix \
+ ! -path ./modules/services/kbfs.nix \
+ ! -path ./modules/services/keybase.nix \
+ ! -path ./modules/services/mpd.nix \
+ ! -path ./modules/services/sxhkd.nix \
+ ! -path ./modules/services/window-managers/i3.nix \
+ ! -path ./modules/systemd.nix \
+ ! -path ./nix-darwin/default.nix \
+ ! -path ./tests/default.nix \
+ ! -path ./tests/modules/home-environment/default.nix \
+ ! -path ./tests/modules/home-environment/session-variables.nix \
+ ! -path ./tests/modules/programs/gpg/override-defaults.nix \
+ ! -path ./tests/modules/programs/tmux/default.nix \
+ ! -path ./tests/modules/programs/zsh/session-variables.nix \
+ ! -path ./tests/modules/services/sxhkd/service.nix \
+ ! -path ./tests/modules/systemd/services.nix \
+ ! -path ./tests/modules/systemd/session-variables.nix \
+ -exec nixfmt $CHECK_ARG {} +
diff --git a/infra/libkookie/home-manager/home-manager/completion.bash b/infra/libkookie/home-manager/home-manager/completion.bash
new file mode 100644
index 000000000000..d5b48258da25
--- /dev/null
+++ b/infra/libkookie/home-manager/home-manager/completion.bash
@@ -0,0 +1,357 @@
+#!/bin/env bash
+
+##################################################
+
+# « home-manager » command-line completion
+#
+# © 2019 "Sam Boosalis" <samboosalis@gmail.com>
+#
+# MIT License
+#
+
+##################################################
+# Contributing:
+
+# Compatibility — Bash 3.
+#
+# OSX won't update Bash 3 (last updated circa 2009) to Bash 4,
+# and we'd like this completion script to work on both Linux and Mac.
+#
+# For example, OSX Yosemite (released circa 2014) ships with Bash 3:
+#
+# $ echo $BASH_VERSION
+# 3.2
+#
+# While Ubuntu LTS 14.04 (a.k.a. Trusty, also released circa 2016)
+# ships with the latest version, Bash 4 (updated circa 2016):
+#
+# $ echo $BASH_VERSION
+# 4.3
+#
+
+# Testing
+#
+# (1) Invoke « shellcheck »
+#
+# * source: « https://github.com/koalaman/shellcheck »
+# * run: « shellcheck ./share/bash-completion/completions/home-manager »
+#
+# (2) Interpret via Bash 3
+#
+# * run: « bash --noprofile --norc ./share/bash-completion/completions/home-manager »
+#
+
+##################################################
+# Examples:
+
+# $ home-manager <TAB>
+#
+# -A
+# -I
+# -f
+# --file
+# -h
+# --help
+# -n
+# --dry-run
+# -v
+# --verbose
+# build
+# edit
+# expire-generations
+# generations
+# help
+# news
+# packages
+# remove-generations
+# switch
+# uninstall
+
+# $ home-manager e<TAB>
+#
+# edit
+# expire-generations
+
+# $ home-manager remove-generations 20<TAB>
+#
+# 200
+# 201
+# 202
+# 203
+
+##################################################
+# Notes:
+
+# « home-manager » Subcommands:
+#
+# help
+# edit
+# build
+# switch
+# generations
+# remove-generations
+# expire-generations
+# packages
+# news
+# uninstall
+
+# « home-manager » Options:
+#
+# -b EXT
+# -f FILE
+# --file FILE
+# -A ATTRIBUTE
+# -I PATH
+# -v
+# --verbose
+# -n
+# --dry-run
+# -h
+# --help
+
+# $ home-manager
+#
+# Usage: /home/sboo/.nix-profile/bin/home-manager [OPTION] COMMAND
+#
+# Options
+#
+# -f FILE The home configuration file.
+# Default is '~/.config/nixpkgs/home.nix'.
+# -A ATTRIBUTE Optional attribute that selects a configuration
+# expression in the configuration file.
+# -I PATH Add a path to the Nix expression search path.
+# -b EXT Move existing files to new path rather than fail.
+# -v Verbose output
+# -n Do a dry run, only prints what actions would be taken
+# -h Print this help
+#
+# Commands
+#
+# help Print this help
+#
+# edit Open the home configuration in $EDITOR
+#
+# build Build configuration into result directory
+#
+# switch Build and activate configuration
+#
+# generations List all home environment generations
+#
+# remove-generations ID...
+# Remove indicated generations. Use 'generations' command to
+# find suitable generation numbers.
+#
+# expire-generations TIMESTAMP
+# Remove generations older than TIMESTAMP where TIMESTAMP is
+# interpreted as in the -d argument of the date tool. For
+# example "-30 days" or "2018-01-01".
+#
+# packages List all packages installed in home-manager-path
+#
+# news Show news entries in a pager
+#
+# uninstall Remove Home Manager
+#
+##################################################
+# Dependencies:
+
+command -v home-manager >/dev/null
+command -v grep >/dev/null
+command -v sed >/dev/null
+
+##################################################
+# Code:
+
+_home-manager_list-generation-identifiers ()
+
+{
+
+ home-manager generations | sed -n -e 's/^................ : id \([[:alnum:]]\+\) -> .*/\1/p'
+
+}
+
+# NOTES
+#
+# (1) the « sed -n -e 's/.../.../p' » invocation:
+#
+# * the « -e '...' » option takes a Sed Script.
+# * the « -n » option only prints when « .../p » would print.
+# * the « s/xxx/yyy/ » Sed Script substitutes « yyy » whenever « xxx » is matched.
+#
+# (2) the « '^................ : id \([[:alnum:]]\+\) -> .*' » regular expression:
+#
+# * matches « 199 », for example, in the line « 2019-03-13 15:26 : id 199 -> /nix/store/mv619y9pzgsx3kndq0q7fjfvbqqdy5k8-home-manager-generation »
+#
+#
+
+#------------------------------------------------#
+
+# shellcheck disable=SC2120
+_home-manager_list-nix-attributes ()
+
+{
+ local HomeFile
+ local HomeAttrsString
+ # local HomeAttrsArray
+ # local HomeAttr
+
+ if [ -z "$1" ]
+ then
+ HomeFile=$(readlink -f "$(_home-manager_get-default-home-file)")
+ else
+ HomeFile="$1"
+ fi
+
+ HomeAttrsString=$(nix-instantiate --eval -E "let home = import ${HomeFile}; in (builtins.trace (builtins.toString (builtins.attrNames home)) null)" |& grep '^trace: ')
+ HomeAttrsString="${HomeAttrsString#trace: }"
+
+ echo "${HomeAttrsString}"
+
+ # IFS=" " read -ar HomeAttrsArray <<< "${HomeAttrsString}"
+ #
+ # local HomeAttr
+ # for HomeAttr in "${HomeAttrsArray[@]}"
+ # do
+ # echo "${HomeAttr}"
+ # done
+
+}
+
+# e.g.:
+#
+# $ nix-instantiate --eval -E 'let home = import /home/sboo/configuration/configs/nixpkgs/home-attrs.nix; in (builtins.trace (builtins.toString (builtins.attrNames home)) null)' 1>/dev/null
+# trace: darwin linux
+#
+# $ _home-manager_list-nix-attributes
+# linux darwin
+#
+
+#------------------------------------------------#
+
+_home-manager_get-default-home-file ()
+
+{
+ local HomeFileDefault
+
+ HomeFileDefault="$(_home-manager_xdg-get-config-home)/nixpkgs/home.nix"
+
+ echo "${HomeFileDefault}"
+}
+
+# e.g.:
+#
+# $ _home-manager_get-default-home-file
+# ~/.config/nixpkgs/home.nix
+#
+
+##################################################
+# XDG-BaseDirs:
+
+_home-manager_xdg-get-config-home () {
+
+ echo "${XDG_CONFIG_HOME:-$HOME/.config}"
+
+}
+
+#------------------------------------------------#
+
+_home-manager_xdg-get-data-home () {
+
+ echo "${XDG_DATA_HOME:-$HOME/.local/share}"
+
+}
+
+
+#------------------------------------------------#
+_home-manager_xdg-get-cache-home () {
+
+ echo "${XDG_CACHE_HOME:-$HOME/.cache}"
+
+}
+
+##################################################
+
+# shellcheck disable=SC2207
+_home-manager_completions ()
+{
+
+ #--------------------------#
+
+ local Subcommands
+ Subcommands=( "help" "edit" "build" "instantiate" "switch" "generations" "remove-generations" "expire-generations" "packages" "news" "uninstall" )
+
+ # ^ « home-manager »'s subcommands.
+
+ #--------------------------#
+
+ local Options
+ Options=( "-f" "--file" "-b" "-A" "-I" "-h" "--help" "-n" "--dry-run" "-v" "--verbose" "--show-trace" \
+ "-j" "--max-jobs" )
+
+ # ^ « home-manager »'s options.
+
+ #--------------------------#
+
+ local CurrentWord
+ CurrentWord="${COMP_WORDS[$COMP_CWORD]}"
+
+ # ^ the word currently being completed
+
+ local PreviousWord
+ if [ "$COMP_CWORD" -ge 1 ]
+ then
+ PreviousWord="${COMP_WORDS[COMP_CWORD-1]}"
+ else
+ PreviousWord=""
+ fi
+
+ # ^ the word to the left of the current word.
+ #
+ # e.g. in « home-manager -v -f ./<TAB> »:
+ #
+ # PreviousWord="-f"
+ # CurrentWord="./"
+
+ #--------------------------#
+
+ COMPREPLY=()
+
+ case "$PreviousWord" in
+
+ "-f"|"--file")
+
+ COMPREPLY+=( $( compgen -A file -- "$CurrentWord") )
+ ;;
+
+ "-I")
+
+ COMPREPLY+=( $( compgen -A directory -- "$CurrentWord") )
+ ;;
+
+ "-A")
+
+ # shellcheck disable=SC2119
+ COMPREPLY+=( $( compgen -W "$(_home-manager_list-nix-attributes)" -- "$CurrentWord") )
+ ;;
+
+ "remove-generations")
+
+ COMPREPLY+=( $( compgen -W "$(_home-manager_list-generation-identifiers)" -- "$CurrentWord" ) )
+ ;;
+
+ *)
+
+ COMPREPLY+=( $( compgen -W "${Subcommands[*]}" -- "$CurrentWord" ) )
+ COMPREPLY+=( $( compgen -W "${Options[*]}" -- "$CurrentWord" ) )
+ ;;
+
+ esac
+
+ #--------------------------#
+}
+
+##################################################
+
+complete -F _home-manager_completions -o default home-manager
+
+#complete -W "help edit build switch generations remove-generations expire-generations packages news" home-manager
diff --git a/infra/libkookie/home-manager/home-manager/default.nix b/infra/libkookie/home-manager/home-manager/default.nix
new file mode 100644
index 000000000000..8b5ae75e0fd7
--- /dev/null
+++ b/infra/libkookie/home-manager/home-manager/default.nix
@@ -0,0 +1,40 @@
+{ runCommand, lib, bash, coreutils, findutils, gnused, less
+
+ # Extra path to Home Manager. If set then this path will be tried
+ # before `$HOME/.config/nixpkgs/home-manager` and
+ # `$HOME/.nixpkgs/home-manager`.
+, path ? null
+}:
+
+let
+
+ pathStr = if path == null then "" else path;
+
+in
+
+runCommand
+ "home-manager"
+ {
+ preferLocalBuild = true;
+ allowSubstitutes = false;
+ meta = with lib; {
+ description = "A user environment configurator";
+ maintainers = [ maintainers.rycee ];
+ platforms = platforms.unix;
+ license = licenses.mit;
+ };
+ }
+ ''
+ install -v -D -m755 ${./home-manager} $out/bin/home-manager
+
+ substituteInPlace $out/bin/home-manager \
+ --subst-var-by bash "${bash}" \
+ --subst-var-by coreutils "${coreutils}" \
+ --subst-var-by findutils "${findutils}" \
+ --subst-var-by gnused "${gnused}" \
+ --subst-var-by less "${less}" \
+ --subst-var-by HOME_MANAGER_PATH '${pathStr}'
+
+ install -D -m755 ${./completion.bash} \
+ $out/share/bash-completion/completions/home-manager
+ ''
diff --git a/infra/libkookie/home-manager/home-manager/home-manager b/infra/libkookie/home-manager/home-manager/home-manager
new file mode 100644
index 000000000000..632198386bb3
--- /dev/null
+++ b/infra/libkookie/home-manager/home-manager/home-manager
@@ -0,0 +1,583 @@
+#!@bash@/bin/bash
+
+# Prepare to use tools from Nixpkgs.
+PATH=@coreutils@/bin:@findutils@/bin:@gnused@/bin:@less@/bin${PATH:+:}$PATH
+
+set -euo pipefail
+
+function errorEcho() {
+ # shellcheck disable=2048,2086
+ echo $* >&2
+}
+
+function setVerboseAndDryRun() {
+ if [[ -v VERBOSE ]]; then
+ export VERBOSE_ARG="--verbose"
+ else
+ export VERBOSE_ARG=""
+ fi
+
+ if [[ -v DRY_RUN ]] ; then
+ export DRY_RUN_CMD=echo
+ else
+ export DRY_RUN_CMD=""
+ fi
+}
+
+function setWorkDir() {
+ if [[ ! -v WORK_DIR ]]; then
+ WORK_DIR="$(mktemp --tmpdir -d home-manager-build.XXXXXXXXXX)"
+ # shellcheck disable=2064
+ trap "rm -r '$WORK_DIR'" EXIT
+ fi
+}
+
+# Attempts to set the HOME_MANAGER_CONFIG global variable.
+#
+# If no configuration file can be found then this function will print
+# an error message and exit with an error code.
+function setConfigFile() {
+ if [[ -v HOME_MANAGER_CONFIG ]] ; then
+ if [[ ! -e "$HOME_MANAGER_CONFIG" ]] ; then
+ errorEcho "No configuration file found at $HOME_MANAGER_CONFIG"
+ exit 1
+ fi
+
+ HOME_MANAGER_CONFIG="$(realpath "$HOME_MANAGER_CONFIG")"
+ return
+ fi
+
+ local defaultConfFile="${XDG_CONFIG_HOME:-$HOME/.config}/nixpkgs/home.nix"
+ local confFile
+ for confFile in "$defaultConfFile" \
+ "$HOME/.nixpkgs/home.nix" ; do
+ if [[ -e "$confFile" ]] ; then
+ HOME_MANAGER_CONFIG="$(realpath "$confFile")"
+ return
+ fi
+ done
+
+ errorEcho "No configuration file found." \
+ "Please create one at $defaultConfFile"
+ exit 1
+}
+
+function setHomeManagerNixPath() {
+ local path
+ for path in "@HOME_MANAGER_PATH@" \
+ "${XDG_CONFIG_HOME:-$HOME/.config}/nixpkgs/home-manager" \
+ "$HOME/.nixpkgs/home-manager" ; do
+ if [[ -e "$path" || "$path" =~ ^https?:// ]] ; then
+ export NIX_PATH="home-manager=$path${NIX_PATH:+:}$NIX_PATH"
+ return
+ fi
+ done
+}
+
+function doInstantiate() {
+ setConfigFile
+ setHomeManagerNixPath
+
+ local extraArgs=()
+
+ for p in "${EXTRA_NIX_PATH[@]}"; do
+ extraArgs=("${extraArgs[@]}" "-I" "$p")
+ done
+
+ if [[ -v VERBOSE ]]; then
+ extraArgs=("${extraArgs[@]}" "--show-trace")
+ fi
+
+ nix-instantiate \
+ "<home-manager/home-manager/home-manager.nix>" \
+ "${extraArgs[@]}" \
+ "${PASSTHROUGH_OPTS[@]}" \
+ --argstr confPath "$HOME_MANAGER_CONFIG" \
+ --argstr confAttr "$HOME_MANAGER_CONFIG_ATTRIBUTE"
+}
+
+function doBuildAttr() {
+ setConfigFile
+ setHomeManagerNixPath
+
+ local extraArgs=("$@")
+
+ for p in "${EXTRA_NIX_PATH[@]}"; do
+ extraArgs=("${extraArgs[@]}" "-I" "$p")
+ done
+
+ if [[ -v VERBOSE ]]; then
+ extraArgs=("${extraArgs[@]}" "--show-trace")
+ fi
+
+ nix-build \
+ "<home-manager/home-manager/home-manager.nix>" \
+ "${extraArgs[@]}" \
+ "${PASSTHROUGH_OPTS[@]}" \
+ --argstr confPath "$HOME_MANAGER_CONFIG" \
+ --argstr confAttr "$HOME_MANAGER_CONFIG_ATTRIBUTE"
+}
+
+# Presents news to the user. Takes as argument the path to a "news
+# info" file as generated by `buildNews`.
+function presentNews() {
+ local infoFile="$1"
+
+ # shellcheck source=/dev/null
+ . "$infoFile"
+
+ # shellcheck disable=2154
+ if [[ $newsNumUnread -eq 0 ]]; then
+ return
+ elif [[ "$newsDisplay" == "silent" ]]; then
+ return
+ elif [[ "$newsDisplay" == "notify" ]]; then
+ local msg
+ if [[ $newsNumUnread -eq 1 ]]; then
+ msg="There is an unread and relevant news item.\n"
+ msg+="Read it by running the command '$(basename "$0") news'."
+ else
+ msg="There are $newsNumUnread unread and relevant news items.\n"
+ msg+="Read them by running the command '$(basename "$0") news'."
+ fi
+
+ # Not actually an error but here stdout is reserved for
+ # nix-build output.
+ errorEcho
+ errorEcho -e "$msg"
+ errorEcho
+
+ if [[ -v DISPLAY ]] && type -P notify-send > /dev/null; then
+ notify-send "Home Manager" "$msg"
+ fi
+ elif [[ "$newsDisplay" == "show" ]]; then
+ doShowNews --unread
+ else
+ errorEcho "Unknown 'news.display' setting '$newsDisplay'."
+ fi
+}
+
+function doEdit() {
+ if [[ ! -v EDITOR || -z $EDITOR ]]; then
+ errorEcho "Please set the \$EDITOR environment variable"
+ return 1
+ fi
+
+ setConfigFile
+
+ # Don't quote $EDITOR in order to support values including options, e.g.,
+ # "code --wait".
+ #
+ # shellcheck disable=2086
+ exec $EDITOR "$HOME_MANAGER_CONFIG"
+}
+
+function doBuild() {
+ if [[ ! -w . ]]; then
+ errorEcho "Cannot run build in read-only directory";
+ return 1
+ fi
+
+ setWorkDir
+
+ local newsInfo
+ newsInfo=$(buildNews)
+
+ local exitCode
+
+ doBuildAttr --attr activationPackage \
+ && exitCode=0 || exitCode=1
+
+ presentNews "$newsInfo"
+
+ return $exitCode
+}
+
+function doSwitch() {
+ setWorkDir
+
+ local newsInfo
+ newsInfo=$(buildNews)
+
+ local generation
+ local exitCode=0
+
+ # Build the generation and run the activate script. Note, we
+ # specify an output link so that it is treated as a GC root. This
+ # prevents an unfortunately timed GC from removing the generation
+ # before activation completes.
+ generation="$WORK_DIR/generation"
+
+ doBuildAttr \
+ --out-link "$generation" \
+ --attr activationPackage \
+ && "$generation/activate" || exitCode=1
+
+ presentNews "$newsInfo"
+
+ return $exitCode
+}
+
+function doListGens() {
+ # Whether to colorize the generations output.
+ local color="never"
+ if [[ -t 1 ]]; then
+ color="always"
+ fi
+
+ pushd "$NIX_STATE_DIR/profiles/per-user/$USER" > /dev/null
+ # shellcheck disable=2012
+ ls --color=$color -gG --time-style=long-iso --sort time home-manager-*-link \
+ | cut -d' ' -f 4- \
+ | sed -E 's/home-manager-([[:digit:]]*)-link/: id \1/'
+ popd > /dev/null
+}
+
+# Removes linked generations. Takes as arguments identifiers of
+# generations to remove.
+function doRmGenerations() {
+ setVerboseAndDryRun
+
+ pushd "$NIX_STATE_DIR/profiles/per-user/$USER" > /dev/null
+
+ for generationId in "$@"; do
+ local linkName="home-manager-$generationId-link"
+
+ if [[ ! -e $linkName ]]; then
+ errorEcho "No generation with ID $generationId"
+ elif [[ $linkName == $(readlink home-manager) ]]; then
+ errorEcho "Cannot remove the current generation $generationId"
+ else
+ echo Removing generation $generationId
+ $DRY_RUN_CMD rm $VERBOSE_ARG $linkName
+ fi
+ done
+
+ popd > /dev/null
+}
+
+function doRmAllGenerations() {
+ $DRY_RUN_CMD rm $VERBOSE_ARG \
+ "$NIX_STATE_DIR/profiles/per-user/$USER/home-manager"*
+}
+
+function doExpireGenerations() {
+ local profileDir="$NIX_STATE_DIR/profiles/per-user/$USER"
+
+ local generations
+ generations="$( \
+ find "$profileDir" -name 'home-manager-*-link' -not -newermt "$1" \
+ | sed 's/^.*-\([0-9]*\)-link$/\1/' \
+ )"
+
+ if [[ -n $generations ]]; then
+ # shellcheck disable=2086
+ doRmGenerations $generations
+ elif [[ -v VERBOSE ]]; then
+ echo "No generations to expire"
+ fi
+}
+
+function doListPackages() {
+ local outPath
+ outPath="$(nix-env -q --out-path | grep -o '/.*home-manager-path$')"
+ if [[ -n "$outPath" ]] ; then
+ nix-store -q --references "$outPath" | sed 's/[^-]*-//'
+ else
+ errorEcho "No home-manager packages seem to be installed."
+ fi
+}
+
+function newsReadIdsFile() {
+ local dataDir="${XDG_DATA_HOME:-$HOME/.local/share}/home-manager"
+ local path="$dataDir/news-read-ids"
+
+ # If the path doesn't exist then we should create it, otherwise
+ # Nix will error out when we attempt to use builtins.readFile.
+ if [[ ! -f "$path" ]]; then
+ mkdir -p "$dataDir"
+ touch "$path"
+ fi
+
+ echo "$path"
+}
+
+# Builds news meta information to be sourced into this script.
+#
+# Note, we suppress build output to remove unnecessary verbosity. We
+# put the output in the work directory to avoid the risk of an
+# unfortunately timed GC removing it.
+function buildNews() {
+ local output
+ output="$WORK_DIR/news-info.sh"
+
+ doBuildAttr \
+ --out-link "$output" \
+ --no-build-output \
+ --quiet \
+ --arg check false \
+ --argstr newsReadIdsFile "$(newsReadIdsFile)" \
+ --attr newsInfo \
+ > /dev/null
+
+ echo "$output"
+}
+
+function doShowNews() {
+ setWorkDir
+
+ local infoFile
+ infoFile=$(buildNews) || return 1
+
+ # shellcheck source=/dev/null
+ . "$infoFile"
+
+ # shellcheck disable=2154
+ case $1 in
+ --all)
+ ${PAGER:-less} "$newsFileAll"
+ ;;
+ --unread)
+ ${PAGER:-less} "$newsFileUnread"
+ ;;
+ *)
+ errorEcho "Unknown argument $1"
+ return 1
+ esac
+
+ # shellcheck disable=2154
+ if [[ -s "$newsUnreadIdsFile" ]]; then
+ local newsReadIdsFile
+ newsReadIdsFile="$(newsReadIdsFile)"
+ cat "$newsUnreadIdsFile" >> "$newsReadIdsFile"
+ fi
+}
+
+function doUninstall() {
+ setVerboseAndDryRun
+
+ echo "This will remove Home Manager from your system."
+
+ if [[ -v DRY_RUN ]]; then
+ echo "This is a dry run, nothing will actually be uninstalled."
+ fi
+
+ local confirmation
+ read -r -n 1 -p "Really uninstall Home Manager? [y/n] " confirmation
+ echo
+
+ case $confirmation in
+ y|Y)
+ echo "Switching to empty Home Manager configuration..."
+ HOME_MANAGER_CONFIG="$(mktemp --tmpdir home-manager.XXXXXXXXXX)"
+ echo "{ lib, ... }: { home.file = lib.mkForce {}; }" > "$HOME_MANAGER_CONFIG"
+ doSwitch
+ $DRY_RUN_CMD nix-env -e home-manager-path || true
+ rm "$HOME_MANAGER_CONFIG"
+ $DRY_RUN_CMD rm $VERBOSE_ARG -r \
+ "${XDG_DATA_HOME:-$HOME/.local/share}/home-manager"
+ $DRY_RUN_CMD rm $VERBOSE_ARG \
+ "$NIX_STATE_DIR/gcroots/per-user/$USER/current-home"
+ ;;
+ *)
+ echo "Yay!"
+ exit 0
+ ;;
+ esac
+
+ local deleteProfiles
+ read -r -n 1 \
+ -p 'Remove all Home Manager generations? [y/n] ' \
+ deleteProfiles
+ echo
+
+ case $deleteProfiles in
+ y|Y)
+ doRmAllGenerations
+ echo "All generations are now eligible for garbage collection."
+ ;;
+ *)
+ echo "Leaving generations but they may still be garbage collected."
+ ;;
+ esac
+
+ echo "Home Manager is uninstalled but your home.nix is left untouched."
+}
+
+function doHelp() {
+ echo "Usage: $0 [OPTION] COMMAND"
+ echo
+ echo "Options"
+ echo
+ echo " -f FILE The home configuration file."
+ echo " Default is '~/.config/nixpkgs/home.nix'."
+ echo " -A ATTRIBUTE Optional attribute that selects a configuration"
+ echo " expression in the configuration file."
+ echo " -I PATH Add a path to the Nix expression search path."
+ echo " -b EXT Move existing files to new path rather than fail."
+ echo " -v Verbose output"
+ echo " -n Do a dry run, only prints what actions would be taken"
+ echo " -h Print this help"
+ echo
+ echo "Options passed on to nix-build(1)"
+ echo
+ echo " --cores NUM"
+ echo " --keep-failed"
+ echo " --keep-going"
+ echo " -j, --max-jobs NUM"
+ echo " --option NAME VALUE"
+ echo " --show-trace"
+ echo " --(no-)substitute"
+ echo
+ echo "Commands"
+ echo
+ echo " help Print this help"
+ echo
+ echo " edit Open the home configuration in \$EDITOR"
+ echo
+ echo " build Build configuration into result directory"
+ echo
+ echo " instantiate Instantiate the configuration and print the resulting derivation"
+ echo
+ echo " switch Build and activate configuration"
+ echo
+ echo " generations List all home environment generations"
+ echo
+ echo " remove-generations ID..."
+ echo " Remove indicated generations. Use 'generations' command to"
+ echo " find suitable generation numbers."
+ echo
+ echo " expire-generations TIMESTAMP"
+ echo " Remove generations older than TIMESTAMP where TIMESTAMP is"
+ echo " interpreted as in the -d argument of the date tool. For"
+ echo " example \"-30 days\" or \"2018-01-01\"."
+ echo
+ echo " packages List all packages installed in home-manager-path"
+ echo
+ echo " news Show news entries in a pager"
+ echo
+ echo " uninstall Remove Home Manager"
+}
+
+readonly NIX_STATE_DIR="${NIX_STATE_DIR:-/nix/var/nix}"
+
+EXTRA_NIX_PATH=()
+HOME_MANAGER_CONFIG_ATTRIBUTE=""
+PASSTHROUGH_OPTS=()
+COMMAND=""
+COMMAND_ARGS=()
+
+while [[ $# -gt 0 ]]; do
+ opt="$1"
+ shift
+ case $opt in
+ build|instantiate|edit|expire-generations|generations|help|news|packages|remove-generations|switch|uninstall)
+ COMMAND="$opt"
+ ;;
+ -A)
+ HOME_MANAGER_CONFIG_ATTRIBUTE="$1"
+ shift
+ ;;
+ -I)
+ EXTRA_NIX_PATH+=("$1")
+ shift
+ ;;
+ -b)
+ export HOME_MANAGER_BACKUP_EXT="$1"
+ shift
+ ;;
+ -f|--file)
+ HOME_MANAGER_CONFIG="$1"
+ shift
+ ;;
+ -h|--help)
+ doHelp
+ exit 0
+ ;;
+ -n|--dry-run)
+ export DRY_RUN=1
+ ;;
+ --option)
+ PASSTHROUGH_OPTS+=("$opt" "$1" "$2")
+ shift 2
+ ;;
+ -j|--max-jobs|--cores)
+ PASSTHROUGH_OPTS+=("$opt" "$1")
+ shift
+ ;;
+ --keep-failed|--keep-going|--show-trace\
+ |--substitute|--no-substitute)
+ PASSTHROUGH_OPTS+=("$opt")
+ ;;
+ -v|--verbose)
+ export VERBOSE=1
+ ;;
+ --version)
+ echo 21.03
+ exit 0
+ ;;
+ *)
+ case $COMMAND in
+ expire-generations|remove-generations)
+ COMMAND_ARGS+=("$opt")
+ ;;
+ *)
+ errorEcho "$0: unknown option '$opt'"
+ errorEcho "Run '$0 --help' for usage help"
+ exit 1
+ ;;
+ esac
+ ;;
+ esac
+done
+
+if [[ -z $COMMAND ]]; then
+ doHelp >&2
+ exit 1
+fi
+
+case $COMMAND in
+ edit)
+ doEdit
+ ;;
+ build)
+ doBuild
+ ;;
+ instantiate)
+ doInstantiate
+ ;;
+ switch)
+ doSwitch
+ ;;
+ generations)
+ doListGens
+ ;;
+ remove-generations)
+ doRmGenerations "${COMMAND_ARGS[@]}"
+ ;;
+ expire-generations)
+ if [[ ${#COMMAND_ARGS[@]} != 1 ]]; then
+ errorEcho "expire-generations expects one argument, got ${#COMMAND_ARGS[@]}."
+ exit 1
+ else
+ doExpireGenerations "${COMMAND_ARGS[@]}"
+ fi
+ ;;
+ packages)
+ doListPackages
+ ;;
+ news)
+ doShowNews --all
+ ;;
+ uninstall)
+ doUninstall
+ ;;
+ help)
+ doHelp
+ ;;
+ *)
+ errorEcho "Unknown command: $COMMAND"
+ doHelp >&2
+ exit 1
+ ;;
+esac
diff --git a/infra/libkookie/home-manager/home-manager/home-manager.nix b/infra/libkookie/home-manager/home-manager/home-manager.nix
new file mode 100644
index 000000000000..04c2d28d32bc
--- /dev/null
+++ b/infra/libkookie/home-manager/home-manager/home-manager.nix
@@ -0,0 +1,88 @@
+{ pkgs ? import <nixpkgs> {}
+, confPath
+, confAttr ? null
+, check ? true
+, newsReadIdsFile ? null
+}:
+
+with pkgs.lib;
+
+let
+
+ env = import ../modules {
+ configuration =
+ if confAttr == "" || confAttr == null
+ then confPath
+ else (import confPath).${confAttr};
+ pkgs = pkgs;
+ check = check;
+ };
+
+ newsReadIds =
+ if newsReadIdsFile == null
+ then {}
+ else
+ let
+ ids = splitString "\n" (fileContents newsReadIdsFile);
+ in
+ builtins.listToAttrs (map (id: { name = id; value = null; }) ids);
+
+ newsIsRead = entry: builtins.hasAttr entry.id newsReadIds;
+
+ newsFiltered =
+ let
+ pred = entry: entry.condition && ! newsIsRead entry;
+ in
+ filter pred env.newsEntries;
+
+ newsNumUnread = length newsFiltered;
+
+ newsFileUnread = pkgs.writeText "news-unread.txt" (
+ concatMapStringsSep "\n\n" (entry:
+ let
+ time = replaceStrings ["T"] [" "] (removeSuffix "+00:00" entry.time);
+ in
+ ''
+ * ${time}
+
+ ${replaceStrings ["\n"] ["\n "] entry.message}
+ ''
+ ) newsFiltered
+ );
+
+ newsFileAll = pkgs.writeText "news-all.txt" (
+ concatMapStringsSep "\n\n" (entry:
+ let
+ flag = if newsIsRead entry then "read" else "unread";
+ time = replaceStrings ["T"] [" "] (removeSuffix "+00:00" entry.time);
+ in
+ ''
+ * ${time} [${flag}]
+
+ ${replaceStrings ["\n"] ["\n "] entry.message}
+ ''
+ ) env.newsEntries
+ );
+
+ # File where each line corresponds to an unread news entry
+ # identifier. If non-empty then the file ends in "\n".
+ newsUnreadIdsFile = pkgs.writeText "news-unread-ids" (
+ let
+ text = concatMapStringsSep "\n" (entry: entry.id) newsFiltered;
+ in
+ text + optionalString (text != "") "\n"
+ );
+
+ newsInfo = pkgs.writeText "news-info.sh" ''
+ local newsNumUnread=${toString newsNumUnread}
+ local newsDisplay="${env.newsDisplay}"
+ local newsFileAll="${newsFileAll}"
+ local newsFileUnread="${newsFileUnread}"
+ local newsUnreadIdsFile="${newsUnreadIdsFile}"
+ '';
+
+in
+ {
+ inherit (env) activationPackage;
+ inherit newsInfo;
+ }
diff --git a/infra/libkookie/home-manager/home-manager/install.nix b/infra/libkookie/home-manager/home-manager/install.nix
new file mode 100644
index 000000000000..01697eb77b3a
--- /dev/null
+++ b/infra/libkookie/home-manager/home-manager/install.nix
@@ -0,0 +1,84 @@
+{ home-manager, runCommand }:
+
+runCommand "home-manager-install" {
+ propagatedBuildInputs = [ home-manager ];
+ preferLocalBuild = true;
+ allowSubstitutes = false;
+ shellHookOnly = true;
+ shellHook = ''
+ confFile="''${XDG_CONFIG_HOME:-$HOME/.config}/nixpkgs/home.nix"
+
+ if [[ ! -e $confFile ]]; then
+ echo
+ echo "Creating initial Home Manager configuration..."
+
+ nl=$'\n'
+ xdgVars=""
+ if [[ -v XDG_CACHE_HOME && $XDG_CACHE_HOME != "$HOME/.cache" ]]; then
+ xdgVars="$xdgVars xdg.cacheHome = \"$XDG_CACHE_HOME\";$nl"
+ fi
+ if [[ -v XDG_CONFIG_HOME && $XDG_CONFIG_HOME != "$HOME/.config" ]]; then
+ xdgVars="$xdgVars xdg.configHome = \"$XDG_CONFIG_HOME\";$nl"
+ fi
+ if [[ -v XDG_DATA_HOME && $XDG_DATA_HOME != "$HOME/.local/share" ]]; then
+ xdgVars="$xdgVars xdg.dataHome = \"$XDG_DATA_HOME\";$nl"
+ fi
+
+ mkdir -p "$(dirname "$confFile")"
+ cat > $confFile <<EOF
+ { config, pkgs, ... }:
+
+ {
+ # Let Home Manager install and manage itself.
+ programs.home-manager.enable = true;
+
+ # Home Manager needs a bit of information about you and the
+ # paths it should manage.
+ home.username = "$USER";
+ home.homeDirectory = "$HOME";
+ $xdgVars
+ # This value determines the Home Manager release that your
+ # configuration is compatible with. This helps avoid breakage
+ # when a new Home Manager release introduces backwards
+ # incompatible changes.
+ #
+ # You can update Home Manager without changing this value. See
+ # the Home Manager release notes for a list of state version
+ # changes in each release.
+ home.stateVersion = "21.03";
+ }
+ EOF
+ fi
+
+ echo
+ echo "Creating initial Home Manager generation..."
+ echo
+
+ if home-manager switch; then
+ cat <<EOF
+
+ All done! The home-manager tool should now be installed and you
+ can edit
+
+ $confFile
+
+ to configure Home Manager. Run 'man home-configuration.nix' to
+ see all available options.
+ EOF
+ exit 0
+ else
+ cat <<EOF
+
+ Uh oh, the installation failed! Please create an issue at
+
+ https://github.com/nix-community/home-manager/issues
+
+ if the error seems to be the fault of Home Manager.
+ EOF
+ exit 1
+ fi
+ '';
+} ''
+ echo This derivation is not buildable, instead run it using nix-shell.
+ exit 1
+''
diff --git a/infra/libkookie/home-manager/modules/accounts/email.nix b/infra/libkookie/home-manager/modules/accounts/email.nix
new file mode 100644
index 000000000000..1e7aff94611b
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/accounts/email.nix
@@ -0,0 +1,396 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.accounts.email;
+
+ gpgModule = types.submodule {
+ options = {
+ key = mkOption {
+ type = types.str;
+ description = ''
+ The key to use as listed in <command>gpg --list-keys</command>.
+ '';
+ };
+
+ signByDefault = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Sign messages by default.";
+ };
+
+ encryptByDefault = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Encrypt outgoing messages by default.";
+ };
+ };
+ };
+
+ signatureModule = types.submodule {
+ options = {
+ text = mkOption {
+ type = types.str;
+ default = "";
+ example = ''
+ --
+ Luke Skywalker
+ May the force be with you.
+ '';
+ description = ''
+ Signature content.
+ '';
+ };
+
+ showSignature = mkOption {
+ type = types.enum [ "append" "attach" "none" ];
+ default = "none";
+ description = "Method to communicate the signature.";
+ };
+ };
+ };
+
+ tlsModule = types.submodule {
+ options = {
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable TLS/SSL.
+ '';
+ };
+
+ useStartTls = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to use STARTTLS.
+ '';
+ };
+
+ certificatesFile = mkOption {
+ type = types.path;
+ default = config.accounts.email.certificatesFile;
+ defaultText = "config.accounts.email.certificatesFile";
+ description = ''
+ Path to file containing certificate authorities that should
+ be used to validate the connection authenticity. If
+ <literal>null</literal> then the system default is used.
+ Note, if set then the system default may still be accepted.
+ '';
+ };
+ };
+ };
+
+ imapModule = types.submodule {
+ options = {
+ host = mkOption {
+ type = types.str;
+ example = "imap.example.org";
+ description = ''
+ Hostname of IMAP server.
+ '';
+ };
+
+ port = mkOption {
+ type = types.nullOr types.port;
+ default = null;
+ example = 993;
+ description = ''
+ The port on which the IMAP server listens. If
+ <literal>null</literal> then the default port is used.
+ '';
+ };
+
+ tls = mkOption {
+ type = tlsModule;
+ default = { };
+ description = ''
+ Configuration for secure connections.
+ '';
+ };
+ };
+ };
+
+ smtpModule = types.submodule {
+ options = {
+ host = mkOption {
+ type = types.str;
+ example = "smtp.example.org";
+ description = ''
+ Hostname of SMTP server.
+ '';
+ };
+
+ port = mkOption {
+ type = types.nullOr types.port;
+ default = null;
+ example = 465;
+ description = ''
+ The port on which the SMTP server listens. If
+ <literal>null</literal> then the default port is used.
+ '';
+ };
+
+ tls = mkOption {
+ type = tlsModule;
+ default = { };
+ description = ''
+ Configuration for secure connections.
+ '';
+ };
+ };
+ };
+
+ maildirModule = types.submodule ({ config, ... }: {
+ options = {
+ path = mkOption {
+ type = types.str;
+ description = ''
+ Path to maildir directory where mail for this account is
+ stored. This is relative to the base maildir path.
+ '';
+ };
+
+ absPath = mkOption {
+ type = types.path;
+ readOnly = true;
+ internal = true;
+ default = "${cfg.maildirBasePath}/${config.path}";
+ description = ''
+ A convenience option whose value is the absolute path of
+ this maildir.
+ '';
+ };
+ };
+ });
+
+ mailAccountOpts = { name, config, ... }: {
+ options = {
+ name = mkOption {
+ type = types.str;
+ readOnly = true;
+ description = ''
+ Unique identifier of the account. This is set to the
+ attribute name of the account configuration.
+ '';
+ };
+
+ primary = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether this is the primary account. Only one account may be
+ set as primary.
+ '';
+ };
+
+ flavor = mkOption {
+ type = types.enum [ "plain" "gmail.com" "runbox.com" ];
+ default = "plain";
+ description = ''
+ Some email providers have peculiar behavior that require
+ special treatment. This option is therefore intended to
+ indicate the nature of the provider.
+ </para><para>
+ When this indicates a specific provider then, for example,
+ the IMAP and SMTP server configuration may be set
+ automatically.
+ '';
+ };
+
+ address = mkOption {
+ type = types.strMatching ".*@.*";
+ example = "jane.doe@example.org";
+ description = "The email address of this account.";
+ };
+
+ aliases = mkOption {
+ type = types.listOf (types.strMatching ".*@.*");
+ default = [ ];
+ example = [ "webmaster@example.org" "admin@example.org" ];
+ description = "Alternative email addresses of this account.";
+ };
+
+ realName = mkOption {
+ type = types.str;
+ example = "Jane Doe";
+ description = "Name displayed when sending mails.";
+ };
+
+ userName = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The server username of this account. This will be used as
+ the SMTP and IMAP user name.
+ '';
+ };
+
+ passwordCommand = mkOption {
+ type = types.nullOr (types.either types.str (types.listOf types.str));
+ default = null;
+ apply = p: if isString p then splitString " " p else p;
+ example = "secret-tool lookup email me@example.org";
+ description = ''
+ A command, which when run writes the account password on
+ standard output.
+ '';
+ };
+
+ folders = mkOption {
+ type = types.submodule {
+ options = {
+ inbox = mkOption {
+ type = types.str;
+ default = "Inbox";
+ description = ''
+ Relative path of the inbox mail.
+ '';
+ };
+
+ sent = mkOption {
+ type = types.nullOr types.str;
+ default = "Sent";
+ description = ''
+ Relative path of the sent mail folder.
+ '';
+ };
+
+ drafts = mkOption {
+ type = types.str;
+ default = "Drafts";
+ description = ''
+ Relative path of the drafts mail folder.
+ '';
+ };
+
+ trash = mkOption {
+ type = types.str;
+ default = "Trash";
+ description = ''
+ Relative path of the deleted mail folder.
+ '';
+ };
+ };
+ };
+ default = { };
+ description = ''
+ Standard email folders.
+ '';
+ };
+
+ imap = mkOption {
+ type = types.nullOr imapModule;
+ default = null;
+ description = ''
+ The IMAP configuration to use for this account.
+ '';
+ };
+
+ signature = mkOption {
+ type = signatureModule;
+ default = { };
+ description = ''
+ Signature configuration.
+ '';
+ };
+
+ gpg = mkOption {
+ type = types.nullOr gpgModule;
+ default = null;
+ description = ''
+ GPG configuration.
+ '';
+ };
+
+ smtp = mkOption {
+ type = types.nullOr smtpModule;
+ default = null;
+ description = ''
+ The SMTP configuration to use for this account.
+ '';
+ };
+
+ maildir = mkOption {
+ type = types.nullOr maildirModule;
+ defaultText = { path = "\${name}"; };
+ description = ''
+ Maildir configuration for this account.
+ '';
+ };
+ };
+
+ config = mkMerge [
+ {
+ name = name;
+ maildir = mkOptionDefault { path = "${name}"; };
+ }
+
+ (mkIf (config.flavor == "gmail.com") {
+ userName = mkDefault config.address;
+
+ imap = { host = "imap.gmail.com"; };
+
+ smtp = {
+ host = "smtp.gmail.com";
+ port = if config.smtp.tls.useStartTls then 587 else 465;
+ };
+ })
+
+ (mkIf (config.flavor == "runbox.com") {
+ imap = { host = "mail.runbox.com"; };
+
+ smtp = { host = "mail.runbox.com"; };
+ })
+ ];
+ };
+
+in {
+ options.accounts.email = {
+ certificatesFile = mkOption {
+ type = types.path;
+ default = "/etc/ssl/certs/ca-certificates.crt";
+ description = ''
+ Path to default file containing certificate authorities that
+ should be used to validate the connection authenticity. This
+ path may be overridden on a per-account basis.
+ '';
+ };
+
+ maildirBasePath = mkOption {
+ type = types.str;
+ default = "${config.home.homeDirectory}/Maildir";
+ defaultText = "$HOME/Maildir";
+ apply = p:
+ if hasPrefix "/" p then p else "${config.home.homeDirectory}/${p}";
+ description = ''
+ The base directory for account maildir directories. May be a
+ relative path, in which case it is relative the home
+ directory.
+ '';
+ };
+
+ accounts = mkOption {
+ type = types.attrsOf (types.submodule mailAccountOpts);
+ default = { };
+ description = "List of email accounts.";
+ };
+ };
+
+ config = mkIf (cfg.accounts != { }) {
+ assertions = [
+ (let
+ primaries =
+ catAttrs "name" (filter (a: a.primary) (attrValues cfg.accounts));
+ in {
+ assertion = length primaries == 1;
+ message = "Must have exactly one primary mail account but found "
+ + toString (length primaries) + optionalString (length primaries > 1)
+ (", namely " + concatStringsSep ", " primaries);
+ })
+ ];
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/default.nix b/infra/libkookie/home-manager/modules/default.nix
new file mode 100644
index 000000000000..7f3494e4deaf
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/default.nix
@@ -0,0 +1,62 @@
+{ configuration
+, pkgs
+, lib ? pkgs.stdenv.lib
+
+ # Whether to check that each option has a matching declaration.
+, check ? true
+}:
+
+with lib;
+
+let
+
+ collectFailed = cfg:
+ map (x: x.message) (filter (x: !x.assertion) cfg.assertions);
+
+ showWarnings = res:
+ let
+ f = w: x: builtins.trace "warning: ${w}" x;
+ in
+ fold f res res.config.warnings;
+
+ extendedLib = import ./lib/stdlib-extended.nix pkgs.lib;
+
+ hmModules =
+ import ./modules.nix {
+ inherit check pkgs;
+ lib = extendedLib;
+ };
+
+ rawModule = extendedLib.evalModules {
+ modules = [ configuration ] ++ hmModules;
+ specialArgs = {
+ modulesPath = builtins.toString ./.;
+ };
+ };
+
+ module = showWarnings (
+ let
+ failed = collectFailed rawModule.config;
+ failedStr = concatStringsSep "\n" (map (x: "- ${x}") failed);
+ in
+ if failed == []
+ then rawModule
+ else throw "\nFailed assertions:\n${failedStr}"
+ );
+
+in
+
+{
+ inherit (module) options config;
+
+ activationPackage = module.config.home.activationPackage;
+
+ # For backwards compatibility. Please use activationPackage instead.
+ activation-script = module.config.home.activationPackage;
+
+ newsDisplay = rawModule.config.news.display;
+ newsEntries =
+ sort (a: b: a.time > b.time) (
+ filter (a: a.condition) rawModule.config.news.entries
+ );
+}
diff --git a/infra/libkookie/home-manager/modules/files.nix b/infra/libkookie/home-manager/modules/files.nix
new file mode 100644
index 000000000000..09ecf715497e
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/files.nix
@@ -0,0 +1,331 @@
+{ pkgs, config, lib, ... }:
+
+with lib;
+
+let
+
+ cfg = config.home.file;
+
+ homeDirectory = config.home.homeDirectory;
+
+ fileType = (import lib/file-type.nix {
+ inherit homeDirectory lib pkgs;
+ }).fileType;
+
+ sourceStorePath = file:
+ let
+ sourcePath = toString file.source;
+ sourceName = config.lib.strings.storeFileName (baseNameOf sourcePath);
+ in
+ if builtins.hasContext sourcePath
+ then file.source
+ else builtins.path { path = file.source; name = sourceName; };
+
+in
+
+{
+ options = {
+ home.file = mkOption {
+ description = "Attribute set of files to link into the user home.";
+ default = {};
+ type = fileType "<envar>HOME</envar>" homeDirectory;
+ };
+
+ home-files = mkOption {
+ type = types.package;
+ internal = true;
+ description = "Package to contain all home files";
+ };
+ };
+
+ config = {
+ lib.file.mkOutOfStoreSymlink = path:
+ let
+ pathStr = toString path;
+ name = hm.strings.storeFileName (baseNameOf pathStr);
+ in
+ pkgs.runCommandLocal name {} ''ln -s ${escapeShellArg pathStr} $out'';
+
+ # This verifies that the links we are about to create will not
+ # overwrite an existing file.
+ home.activation.checkLinkTargets = hm.dag.entryBefore ["writeBoundary"] (
+ let
+ # Paths that should be forcibly overwritten by Home Manager.
+ # Caveat emptor!
+ forcedPaths =
+ concatMapStringsSep " " (p: ''"$HOME/${p}"'')
+ (mapAttrsToList (n: v: v.target)
+ (filterAttrs (n: v: v.force) cfg));
+
+ check = pkgs.writeText "check" ''
+ . ${./lib-bash/color-echo.sh}
+
+ # A symbolic link whose target path matches this pattern will be
+ # considered part of a Home Manager generation.
+ homeFilePattern="$(readlink -e "${builtins.storeDir}")/*-home-manager-files/*"
+
+ forcedPaths=(${forcedPaths})
+
+ newGenFiles="$1"
+ shift
+ for sourcePath in "$@" ; do
+ relativePath="''${sourcePath#$newGenFiles/}"
+ targetPath="$HOME/$relativePath"
+
+ forced=""
+ for forcedPath in "''${forcedPaths[@]}"; do
+ if [[ $targetPath == $forcedPath* ]]; then
+ forced="yeah"
+ break
+ fi
+ done
+
+ if [[ -n $forced ]]; then
+ $VERBOSE_ECHO "Skipping collision check for $targetPath"
+ elif [[ -e "$targetPath" \
+ && ! "$(readlink "$targetPath")" == $homeFilePattern ]] ; then
+ if [[ ! -L "$targetPath" && -n "$HOME_MANAGER_BACKUP_EXT" ]] ; then
+ backup="$targetPath.$HOME_MANAGER_BACKUP_EXT"
+ if [[ -e "$backup" ]]; then
+ errorEcho "Existing file '$backup' would be clobbered by backing up '$targetPath'"
+ collision=1
+ else
+ warnEcho "Existing file '$targetPath' is in the way of '$sourcePath', will be moved to '$backup'"
+ fi
+ else
+ errorEcho "Existing file '$targetPath' is in the way of '$sourcePath'"
+ collision=1
+ fi
+ fi
+ done
+
+ if [[ -v collision ]] ; then
+ errorEcho "Please move the above files and try again or use -b <ext> to move automatically."
+ exit 1
+ fi
+ '';
+ in
+ ''
+ function checkNewGenCollision() {
+ local newGenFiles
+ newGenFiles="$(readlink -e "$newGenPath/home-files")"
+ find "$newGenFiles" \( -type f -or -type l \) \
+ -exec bash ${check} "$newGenFiles" {} +
+ }
+
+ checkNewGenCollision || exit 1
+ ''
+ );
+
+ # This activation script will
+ #
+ # 1. Remove files from the old generation that are not in the new
+ # generation.
+ #
+ # 2. Switch over the Home Manager gcroot and current profile
+ # links.
+ #
+ # 3. Symlink files from the new generation into $HOME.
+ #
+ # This order is needed to ensure that we always know which links
+ # belong to which generation. Specifically, if we're moving from
+ # generation A to generation B having sets of home file links FA
+ # and FB, respectively then cleaning before linking produces state
+ # transitions similar to
+ #
+ # FA → FA ∩ FB → (FA ∩ FB) ∪ FB = FB
+ #
+ # and a failure during the intermediate state FA ∩ FB will not
+ # result in lost links because this set of links are in both the
+ # source and target generation.
+ home.activation.linkGeneration = hm.dag.entryAfter ["writeBoundary"] (
+ let
+ link = pkgs.writeText "link" ''
+ newGenFiles="$1"
+ shift
+ for sourcePath in "$@" ; do
+ relativePath="''${sourcePath#$newGenFiles/}"
+ targetPath="$HOME/$relativePath"
+ if [[ -e "$targetPath" && ! -L "$targetPath" && -n "$HOME_MANAGER_BACKUP_EXT" ]] ; then
+ backup="$targetPath.$HOME_MANAGER_BACKUP_EXT"
+ $DRY_RUN_CMD mv $VERBOSE_ARG "$targetPath" "$backup" || errorEcho "Moving '$targetPath' failed!"
+ fi
+ $DRY_RUN_CMD mkdir -p $VERBOSE_ARG "$(dirname "$targetPath")"
+ $DRY_RUN_CMD ln -nsf $VERBOSE_ARG "$sourcePath" "$targetPath"
+ done
+ '';
+
+ cleanup = pkgs.writeText "cleanup" ''
+ . ${./lib-bash/color-echo.sh}
+
+ # A symbolic link whose target path matches this pattern will be
+ # considered part of a Home Manager generation.
+ homeFilePattern="$(readlink -e "${builtins.storeDir}")/*-home-manager-files/*"
+
+ newGenFiles="$1"
+ shift 1
+ for relativePath in "$@" ; do
+ targetPath="$HOME/$relativePath"
+ if [[ -e "$newGenFiles/$relativePath" ]] ; then
+ $VERBOSE_ECHO "Checking $targetPath: exists"
+ elif [[ ! "$(readlink "$targetPath")" == $homeFilePattern ]] ; then
+ warnEcho "Path '$targetPath' does not link into a Home Manager generation. Skipping delete."
+ else
+ $VERBOSE_ECHO "Checking $targetPath: gone (deleting)"
+ $DRY_RUN_CMD rm $VERBOSE_ARG "$targetPath"
+
+ # Recursively delete empty parent directories.
+ targetDir="$(dirname "$relativePath")"
+ if [[ "$targetDir" != "." ]] ; then
+ pushd "$HOME" > /dev/null
+
+ # Call rmdir with a relative path excluding $HOME.
+ # Otherwise, it might try to delete $HOME and exit
+ # with a permission error.
+ $DRY_RUN_CMD rmdir $VERBOSE_ARG \
+ -p --ignore-fail-on-non-empty \
+ "$targetDir"
+
+ popd > /dev/null
+ fi
+ fi
+ done
+ '';
+ in
+ ''
+ function linkNewGen() {
+ echo "Creating home file links in $HOME"
+
+ local newGenFiles
+ newGenFiles="$(readlink -e "$newGenPath/home-files")"
+ find "$newGenFiles" \( -type f -or -type l \) \
+ -exec bash ${link} "$newGenFiles" {} +
+ }
+
+ function cleanOldGen() {
+ if [[ ! -v oldGenPath ]] ; then
+ return
+ fi
+
+ echo "Cleaning up orphan links from $HOME"
+
+ local newGenFiles oldGenFiles
+ newGenFiles="$(readlink -e "$newGenPath/home-files")"
+ oldGenFiles="$(readlink -e "$oldGenPath/home-files")"
+
+ # Apply the cleanup script on each leaf in the old
+ # generation. The find command below will print the
+ # relative path of the entry.
+ find "$oldGenFiles" '(' -type f -or -type l ')' -printf '%P\0' \
+ | xargs -0 bash ${cleanup} "$newGenFiles"
+ }
+
+ cleanOldGen
+
+ if [[ ! -v oldGenPath || "$oldGenPath" != "$newGenPath" ]] ; then
+ echo "Creating profile generation $newGenNum"
+ $DRY_RUN_CMD nix-env $VERBOSE_ARG --profile "$genProfilePath" --set "$newGenPath"
+ $DRY_RUN_CMD ln -Tsf $VERBOSE_ARG "$newGenPath" "$newGenGcPath"
+ else
+ echo "No change so reusing latest profile generation $oldGenNum"
+ fi
+
+ linkNewGen
+ ''
+ );
+
+ home.activation.checkFilesChanged = hm.dag.entryBefore ["linkGeneration"] (
+ ''
+ declare -A changedFiles
+ '' + concatMapStrings (v: ''
+ cmp --quiet "${sourceStorePath v}" "${homeDirectory}/${v.target}" \
+ && changedFiles["${v.target}"]=0 \
+ || changedFiles["${v.target}"]=1
+ '') (filter (v: v.onChange != "") (attrValues cfg))
+ );
+
+ home.activation.onFilesChange = hm.dag.entryAfter ["linkGeneration"] (
+ concatMapStrings (v: ''
+ if [[ ${"$\{changedFiles"}["${v.target}"]} -eq 1 ]]; then
+ ${v.onChange}
+ fi
+ '') (filter (v: v.onChange != "") (attrValues cfg))
+ );
+
+ # Symlink directories and files that have the right execute bit.
+ # Copy files that need their execute bit changed.
+ home-files = pkgs.runCommand
+ "home-manager-files"
+ {
+ nativeBuildInputs = [ pkgs.xorg.lndir ];
+ preferLocalBuild = true;
+ allowSubstitutes = false;
+ }
+ (''
+ mkdir -p $out
+
+ # Needed in case /nix is a symbolic link.
+ realOut="$(realpath -m "$out")"
+
+ function insertFile() {
+ local source="$1"
+ local relTarget="$2"
+ local executable="$3"
+ local recursive="$4"
+
+ # Figure out the real absolute path to the target.
+ local target
+ target="$(realpath -m "$realOut/$relTarget")"
+
+ # Target path must be within $HOME.
+ if [[ ! $target == $realOut* ]] ; then
+ echo "Error installing file '$relTarget' outside \$HOME" >&2
+ exit 1
+ fi
+
+ mkdir -p "$(dirname "$target")"
+ if [[ -d $source ]]; then
+ if [[ $recursive ]]; then
+ mkdir -p "$target"
+ lndir -silent "$source" "$target"
+ else
+ ln -s "$source" "$target"
+ fi
+ else
+ [[ -x $source ]] && isExecutable=1 || isExecutable=""
+
+ # Link the file into the home file directory if possible,
+ # i.e., if the executable bit of the source is the same we
+ # expect for the target. Otherwise, we copy the file and
+ # set the executable bit to the expected value.
+ if [[ $executable == inherit || $isExecutable == $executable ]]; then
+ ln -s "$source" "$target"
+ else
+ cp "$source" "$target"
+
+ if [[ $executable == inherit ]]; then
+ # Don't change file mode if it should match the source.
+ :
+ elif [[ $executable ]]; then
+ chmod +x "$target"
+ else
+ chmod -x "$target"
+ fi
+ fi
+ fi
+ }
+ '' + concatStrings (
+ mapAttrsToList (n: v: ''
+ insertFile ${
+ escapeShellArgs [
+ (sourceStorePath v)
+ v.target
+ (if v.executable == null
+ then "inherit"
+ else toString v.executable)
+ (toString v.recursive)
+ ]}
+ '') cfg
+ ));
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/home-environment.nix b/infra/libkookie/home-manager/modules/home-environment.nix
new file mode 100644
index 000000000000..e5b6cc3a6ed8
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/home-environment.nix
@@ -0,0 +1,577 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.home;
+
+ languageSubModule = types.submodule {
+ options = {
+ base = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The language to use unless overridden by a more specific option.
+ '';
+ };
+
+ ctype = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ Character classification category.
+ '';
+ };
+
+ numeric = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The language to use for numerical values.
+ '';
+ };
+
+ time = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The language to use for formatting times.
+ '';
+ };
+
+ collate = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The language to use for collation (alphabetical ordering).
+ '';
+ };
+
+ monetary = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The language to use for formatting currencies and money amounts.
+ '';
+ };
+
+ messages = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The language to use for messages, application UI languages, etc.
+ '';
+ };
+
+ paper = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The language to use for paper sizes.
+ '';
+ };
+
+ name = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The language to use for personal names.
+ '';
+ };
+
+ address = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The language to use for addresses.
+ '';
+ };
+
+ telephone = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The language to use for telephone numbers.
+ '';
+ };
+
+ measurement = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The language to use for measurement values.
+ '';
+ };
+
+ };
+ };
+
+ keyboardSubModule = types.submodule {
+ options = {
+ layout = mkOption {
+ type = with types; nullOr str;
+ default =
+ if versionAtLeast config.home.stateVersion "19.09"
+ then null
+ else "us";
+ defaultText = literalExample "null";
+ description = ''
+ Keyboard layout. If <literal>null</literal>, then the system
+ configuration will be used.
+ </para><para>
+ This defaults to <literal>null</literal> for state
+ version ≥ 19.09 and <literal>"us"</literal> otherwise.
+ '';
+ };
+
+ model = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ example = "presario";
+ description = ''
+ Keyboard model.
+ '';
+ };
+
+ options = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = ["grp:caps_toggle" "grp_led:scroll"];
+ description = ''
+ X keyboard options; layout switching goes here.
+ '';
+ };
+
+ variant = mkOption {
+ type = with types; nullOr str;
+ default =
+ if versionAtLeast config.home.stateVersion "19.09"
+ then null
+ else "";
+ defaultText = literalExample "null";
+ example = "colemak";
+ description = ''
+ X keyboard variant. If <literal>null</literal>, then the
+ system configuration will be used.
+ </para><para>
+ This defaults to <literal>null</literal> for state
+ version ≥ 19.09 and <literal>""</literal> otherwise.
+ '';
+ };
+ };
+ };
+
+in
+
+{
+ meta.maintainers = [ maintainers.rycee ];
+
+ imports = [
+ (mkRemovedOptionModule [ "home" "sessionVariableSetter" ] ''
+ Session variables are now always set through the shell. This is
+ done automatically if the shell configuration is managed by Home
+ Manager. If not, then you must source the
+
+ ~/.nix-profile/etc/profile.d/hm-session-vars.sh
+
+ file yourself.
+ '')
+ ];
+
+ options = {
+ home.username = mkOption {
+ type = types.str;
+ defaultText = literalExample ''
+ "$USER" for state version < 20.09,
+ undefined for state version ≥ 20.09
+ '';
+ example = "jane.doe";
+ description = "The user's username.";
+ };
+
+ home.homeDirectory = mkOption {
+ type = types.path;
+ defaultText = literalExample ''
+ "$HOME" for state version < 20.09,
+ undefined for state version ≥ 20.09
+ '';
+ apply = toString;
+ example = "/home/jane.doe";
+ description = "The user's home directory. Must be an absolute path.";
+ };
+
+ home.profileDirectory = mkOption {
+ type = types.path;
+ defaultText = "~/.nix-profile";
+ internal = true;
+ readOnly = true;
+ description = ''
+ The profile directory where Home Manager generations are
+ installed.
+ '';
+ };
+
+ home.language = mkOption {
+ type = languageSubModule;
+ default = {};
+ description = "Language configuration.";
+ };
+
+ home.keyboard = mkOption {
+ type = types.nullOr keyboardSubModule;
+ default = {};
+ description = ''
+ Keyboard configuration. Set to <literal>null</literal> to
+ disable Home Manager keyboard management.
+ '';
+ };
+
+ home.sessionVariables = mkOption {
+ default = {};
+ type = types.attrs;
+ example = { EDITOR = "emacs"; GS_OPTIONS = "-sPAPERSIZE=a4"; };
+ description = ''
+ Environment variables to always set at login.
+ </para><para>
+ The values may refer to other environment variables using
+ POSIX.2 style variable references. For example, a variable
+ <varname>parameter</varname> may be referenced as
+ <code>$parameter</code> or <code>''${parameter}</code>. A
+ default value <literal>foo</literal> may be given as per
+ <code>''${parameter:-foo}</code> and, similarly, an alternate
+ value <literal>bar</literal> can be given as per
+ <code>''${parameter:+bar}</code>.
+ </para><para>
+ Note, these variables may be set in any order so no session
+ variable may have a runtime dependency on another session
+ variable. In particular code like
+ <programlisting language="nix">
+ home.sessionVariables = {
+ FOO = "Hello";
+ BAR = "$FOO World!";
+ };
+ </programlisting>
+ may not work as expected. If you need to reference another
+ session variable, then do so inside Nix instead. The above
+ example then becomes
+ <programlisting language="nix">
+ home.sessionVariables = {
+ FOO = "Hello";
+ BAR = "''${config.home.sessionVariables.FOO} World!";
+ };
+ </programlisting>
+ '';
+ };
+
+ home.sessionPath = mkOption {
+ type = with types; listOf str;
+ default = [ ];
+ example = [
+ ".git/safe/../../bin"
+ "\${xdg.configHome}/emacs/bin"
+ "~/.local/bin"
+ ];
+ description = "Extra directories to add to <envar>PATH</envar>.";
+ };
+
+ home.sessionVariablesExtra = mkOption {
+ type = types.lines;
+ default = "";
+ internal = true;
+ description = ''
+ Extra configuration to add to the
+ <filename>hm-session-vars.sh</filename> file.
+ '';
+ };
+
+ home.packages = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ description = "The set of packages to appear in the user environment.";
+ };
+
+ home.extraOutputsToInstall = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "doc" "info" "devdoc" ];
+ description = ''
+ List of additional package outputs of the packages
+ <varname>home.packages</varname> that should be installed into
+ the user environment.
+ '';
+ };
+
+ home.path = mkOption {
+ internal = true;
+ description = "The derivation installing the user packages.";
+ };
+
+ home.emptyActivationPath = mkOption {
+ internal = true;
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether the activation script should start with an empty
+ <envar>PATH</envar> variable. When <literal>false</literal>
+ then the user's <envar>PATH</envar> will be used.
+ '';
+ };
+
+ home.activation = mkOption {
+ type = hm.types.dagOf types.str;
+ default = {};
+ example = literalExample ''
+ {
+ myActivationAction = lib.hm.dag.entryAfter ["writeBoundary"] '''
+ $DRY_RUN_CMD ln -s $VERBOSE_ARG \
+ ''${builtins.toPath ./link-me-directly} $HOME
+ ''';
+ }
+ '';
+ description = ''
+ The activation scripts blocks to run when activating a Home
+ Manager generation. Any entry here should be idempotent,
+ meaning running twice or more times produces the same result
+ as running it once.
+
+ </para><para>
+
+ If the script block produces any observable side effect, such
+ as writing or deleting files, then it
+ <emphasis>must</emphasis> be placed after the special
+ <literal>writeBoundary</literal> script block. Prior to the
+ write boundary one can place script blocks that verifies, but
+ does not modify, the state of the system and exits if an
+ unexpected state is found. For example, the
+ <literal>checkLinkTargets</literal> script block checks for
+ collisions between non-managed files and files defined in
+ <varname><link linkend="opt-home.file">home.file</link></varname>.
+
+ </para><para>
+
+ A script block should respect the <varname>DRY_RUN</varname>
+ variable, if it is set then the actions taken by the script
+ should be logged to standard out and not actually performed.
+ The variable <varname>DRY_RUN_CMD</varname> is set to
+ <command>echo</command> if dry run is enabled.
+
+ </para><para>
+
+ A script block should also respect the
+ <varname>VERBOSE</varname> variable, and if set print
+ information on standard out that may be useful for debugging
+ any issue that may arise. The variable
+ <varname>VERBOSE_ARG</varname> is set to
+ <option>--verbose</option> if verbose output is enabled.
+ '';
+ };
+
+ home.activationPackage = mkOption {
+ internal = true;
+ type = types.package;
+ description = "The package containing the complete activation script.";
+ };
+
+ home.extraBuilderCommands = mkOption {
+ type = types.lines;
+ default = "";
+ internal = true;
+ description = ''
+ Extra commands to run in the Home Manager generation builder.
+ '';
+ };
+
+ home.extraProfileCommands = mkOption {
+ type = types.lines;
+ default = "";
+ internal = true;
+ description = ''
+ Extra commands to run in the Home Manager profile builder.
+ '';
+ };
+ };
+
+ config = {
+ assertions = [
+ {
+ assertion = config.home.username != "";
+ message = "Username could not be determined";
+ }
+ {
+ assertion = config.home.homeDirectory != "";
+ message = "Home directory could not be determined";
+ }
+ ];
+
+ home.username =
+ mkIf (versionOlder config.home.stateVersion "20.09")
+ (mkDefault (builtins.getEnv "USER"));
+ home.homeDirectory =
+ mkIf (versionOlder config.home.stateVersion "20.09")
+ (mkDefault (builtins.getEnv "HOME"));
+
+ home.profileDirectory =
+ if config.submoduleSupport.enable
+ && config.submoduleSupport.externalPackageInstall
+ then "/etc/profiles/per-user/${cfg.username}"
+ else cfg.homeDirectory + "/.nix-profile";
+
+ home.sessionVariables =
+ let
+ maybeSet = n: v: optionalAttrs (v != null) { ${n} = v; };
+ in
+ (maybeSet "LANG" cfg.language.base)
+ //
+ (maybeSet "LC_CTYPE" cfg.language.ctype)
+ //
+ (maybeSet "LC_NUMERIC" cfg.language.numeric)
+ //
+ (maybeSet "LC_TIME" cfg.language.time)
+ //
+ (maybeSet "LC_COLLATE" cfg.language.collate)
+ //
+ (maybeSet "LC_MONETARY" cfg.language.monetary)
+ //
+ (maybeSet "LC_MESSAGES" cfg.language.messages)
+ //
+ (maybeSet "LC_PAPER" cfg.language.paper)
+ //
+ (maybeSet "LC_NAME" cfg.language.name)
+ //
+ (maybeSet "LC_ADDRESS" cfg.language.address)
+ //
+ (maybeSet "LC_TELEPHONE" cfg.language.telephone)
+ //
+ (maybeSet "LC_MEASUREMENT" cfg.language.measurement);
+
+ home.packages = [
+ # Provide a file holding all session variables.
+ (
+ pkgs.writeTextFile {
+ name = "hm-session-vars.sh";
+ destination = "/etc/profile.d/hm-session-vars.sh";
+ text = ''
+ # Only source this once.
+ if [ -n "$__HM_SESS_VARS_SOURCED" ]; then return; fi
+ export __HM_SESS_VARS_SOURCED=1
+
+ ${config.lib.shell.exportAll cfg.sessionVariables}
+ '' + lib.optionalString (cfg.sessionPath != [ ]) ''
+ export PATH="$PATH''${PATH:+:}${concatStringsSep ":" cfg.sessionPath}"
+ '' + cfg.sessionVariablesExtra;
+ }
+ )
+ ];
+
+ # A dummy entry acting as a boundary between the activation
+ # script's "check" and the "write" phases.
+ home.activation.writeBoundary = hm.dag.entryAnywhere "";
+
+ # Install packages to the user environment.
+ #
+ # Note, sometimes our target may not allow modification of the Nix
+ # store and then we cannot rely on `nix-env -i`. This is the case,
+ # for example, if we are running as a NixOS module and building a
+ # virtual machine. Then we must instead rely on an external
+ # mechanism for installing packages, which in NixOS is provided by
+ # the `users.users.<name?>.packages` option. The activation
+ # command is still needed since some modules need to run their
+ # activation commands after the packages are guaranteed to be
+ # installed.
+ #
+ # In case the user has moved from a user-install of Home Manager
+ # to a submodule managed one we attempt to uninstall the
+ # `home-manager-path` package if it is installed.
+ home.activation.installPackages = hm.dag.entryAfter ["writeBoundary"] (
+ if config.submoduleSupport.externalPackageInstall
+ then
+ ''
+ if nix-env -q | grep '^home-manager-path$'; then
+ $DRY_RUN_CMD nix-env -e home-manager-path
+ fi
+ ''
+ else
+ ''
+ $DRY_RUN_CMD nix-env -i ${cfg.path}
+ ''
+ );
+
+ home.activationPackage =
+ let
+ mkCmd = res: ''
+ noteEcho Activating ${res.name}
+ ${res.data}
+ '';
+ sortedCommands = hm.dag.topoSort cfg.activation;
+ activationCmds =
+ if sortedCommands ? result then
+ concatStringsSep "\n" (map mkCmd sortedCommands.result)
+ else
+ abort ("Dependency cycle in activation script: "
+ + builtins.toJSON sortedCommands);
+
+ # Programs that always should be available on the activation
+ # script's PATH.
+ activationBinPaths = lib.makeBinPath [
+ pkgs.bash
+ pkgs.coreutils
+ pkgs.diffutils # For `cmp` and `diff`.
+ pkgs.findutils
+ pkgs.gnugrep
+ pkgs.gnused
+ pkgs.ncurses # For `tput`.
+ ]
+ + optionalString (!cfg.emptyActivationPath) "\${PATH:+:}$PATH";
+
+ activationScript = pkgs.writeScript "activation-script" ''
+ #!${pkgs.runtimeShell}
+
+ set -eu
+ set -o pipefail
+
+ cd $HOME
+
+ export PATH="${activationBinPaths}"
+
+ . ${./lib-bash/color-echo.sh}
+
+ ${builtins.readFile ./lib-bash/activation-init.sh}
+
+ ${activationCmds}
+ '';
+ in
+ pkgs.runCommand
+ "home-manager-generation"
+ {
+ preferLocalBuild = true;
+ allowSubstitutes = false;
+ }
+ ''
+ mkdir -p $out
+
+ cp ${activationScript} $out/activate
+
+ substituteInPlace $out/activate \
+ --subst-var-by GENERATION_DIR $out
+
+ ln -s ${config.home-files} $out/home-files
+ ln -s ${cfg.path} $out/home-path
+
+ ${cfg.extraBuilderCommands}
+ '';
+
+ home.path = pkgs.buildEnv {
+ name = "home-manager-path";
+
+ paths = cfg.packages;
+ inherit (cfg) extraOutputsToInstall;
+
+ postBuild = cfg.extraProfileCommands;
+
+ meta = {
+ description = "Environment of packages installed through home-manager";
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/lib-bash/activation-init.sh b/infra/libkookie/home-manager/modules/lib-bash/activation-init.sh
new file mode 100755
index 000000000000..f95008ee75b4
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/lib-bash/activation-init.sh
@@ -0,0 +1,85 @@
+#!/usr/bin/env bash
+
+function setupVars() {
+ local nixStateDir="${NIX_STATE_DIR:-/nix/var/nix}"
+ local profilesPath="$nixStateDir/profiles/per-user/$USER"
+ local gcPath="$nixStateDir/gcroots/per-user/$USER"
+
+ genProfilePath="$profilesPath/home-manager"
+ newGenPath="@GENERATION_DIR@";
+ newGenGcPath="$gcPath/current-home"
+
+ local greatestGenNum
+ greatestGenNum=$( \
+ nix-env --list-generations --profile "$genProfilePath" \
+ | tail -1 \
+ | sed -E 's/ *([[:digit:]]+) .*/\1/')
+
+ if [[ -n $greatestGenNum ]] ; then
+ oldGenNum=$greatestGenNum
+ newGenNum=$((oldGenNum + 1))
+ else
+ newGenNum=1
+ fi
+
+ if [[ -e $profilesPath/home-manager ]] ; then
+ oldGenPath="$(readlink -e "$profilesPath/home-manager")"
+ fi
+
+ $VERBOSE_ECHO "Sanity checking oldGenNum and oldGenPath"
+ if [[ -v oldGenNum && ! -v oldGenPath
+ || ! -v oldGenNum && -v oldGenPath ]]; then
+ errorEcho "Invalid profile number and current profile values! These"
+ errorEcho "must be either both empty or both set but are now set to"
+ errorEcho " '${oldGenNum:-}' and '${oldGenPath:-}'"
+ errorEcho "If you don't mind losing previous profile generations then"
+ errorEcho "the easiest solution is probably to run"
+ errorEcho " rm $profilesPath/home-manager*"
+ errorEcho " rm $gcPath/current-home"
+ errorEcho "and trying home-manager switch again. Good luck!"
+ exit 1
+ fi
+}
+
+if [[ -v VERBOSE ]]; then
+ export VERBOSE_ECHO=echo
+ export VERBOSE_ARG="--verbose"
+else
+ export VERBOSE_ECHO=true
+ export VERBOSE_ARG=""
+fi
+
+echo "Starting home manager activation"
+
+# Verify that we can connect to the Nix store and/or daemon. This will
+# also create the necessary directories in profiles and gcroots.
+$VERBOSE_ECHO "Sanity checking Nix"
+nix-build --expr '{}' --no-out-link
+
+setupVars
+
+if [[ -v DRY_RUN ]] ; then
+ echo "This is a dry run"
+ export DRY_RUN_CMD=echo
+else
+ $VERBOSE_ECHO "This is a live run"
+ export DRY_RUN_CMD=""
+fi
+
+if [[ -v VERBOSE ]]; then
+ echo -n "Using Nix version: "
+ nix-env --version
+fi
+
+$VERBOSE_ECHO "Activation variables:"
+if [[ -v oldGenNum ]] ; then
+ $VERBOSE_ECHO " oldGenNum=$oldGenNum"
+ $VERBOSE_ECHO " oldGenPath=$oldGenPath"
+else
+ $VERBOSE_ECHO " oldGenNum undefined (first run?)"
+ $VERBOSE_ECHO " oldGenPath undefined (first run?)"
+fi
+$VERBOSE_ECHO " newGenPath=$newGenPath"
+$VERBOSE_ECHO " newGenNum=$newGenNum"
+$VERBOSE_ECHO " newGenGcPath=$newGenGcPath"
+$VERBOSE_ECHO " genProfilePath=$genProfilePath"
diff --git a/infra/libkookie/home-manager/modules/lib-bash/color-echo.sh b/infra/libkookie/home-manager/modules/lib-bash/color-echo.sh
new file mode 100644
index 000000000000..ef708b29c4db
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/lib-bash/color-echo.sh
@@ -0,0 +1,37 @@
+# The check for terminal output and color support is heavily inspired
+# by https://unix.stackexchange.com/a/10065.
+
+function setupColors() {
+ normalColor=""
+ errorColor=""
+ warnColor=""
+ noteColor=""
+
+ # Check if stdout is a terminal.
+ if [[ -t 1 ]]; then
+ # See if it supports colors.
+ local ncolors
+ ncolors=$(tput colors)
+
+ if [[ -n "$ncolors" && "$ncolors" -ge 8 ]]; then
+ normalColor="$(tput sgr0)"
+ errorColor="$(tput bold)$(tput setaf 1)"
+ warnColor="$(tput setaf 3)"
+ noteColor="$(tput bold)$(tput setaf 6)"
+ fi
+ fi
+}
+
+setupColors
+
+function errorEcho() {
+ echo "${errorColor}$*${normalColor}"
+}
+
+function warnEcho() {
+ echo "${warnColor}$*${normalColor}"
+}
+
+function noteEcho() {
+ echo "${noteColor}$*${normalColor}"
+}
diff --git a/infra/libkookie/home-manager/modules/lib/dag.nix b/infra/libkookie/home-manager/modules/lib/dag.nix
new file mode 100644
index 000000000000..cbe34129652a
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/lib/dag.nix
@@ -0,0 +1,117 @@
+# A generalization of Nixpkgs's `strings-with-deps.nix`.
+#
+# The main differences from the Nixpkgs version are
+#
+# - not specific to strings, i.e., any payload is OK,
+#
+# - the addition of the function `dagEntryBefore` indicating a
+# "wanted by" relationship.
+
+{ lib }:
+
+with lib;
+
+rec {
+
+ emptyDag = { };
+
+ isDag = dag:
+ let isEntry = e: (e ? data) && (e ? after) && (e ? before);
+ in builtins.isAttrs dag && all (x: x) (mapAttrsToList (n: isEntry) dag);
+
+ # Takes an attribute set containing entries built by
+ # dagEntryAnywhere, dagEntryAfter, and dagEntryBefore to a
+ # topologically sorted list of entries.
+ #
+ # Internally this function uses the `toposort` function in
+ # `<nixpkgs/lib/lists.nix>` and its value is accordingly.
+ #
+ # Specifically, the result on success is
+ #
+ # { result = [{name = ?; data = ?;} …] }
+ #
+ # For example
+ #
+ # nix-repl> dagTopoSort {
+ # a = dagEntryAnywhere "1";
+ # b = dagEntryAfter ["a" "c"] "2";
+ # c = dagEntryBefore ["d"] "3";
+ # d = dagEntryBefore ["e"] "4";
+ # e = dagEntryAnywhere "5";
+ # } == {
+ # result = [
+ # { data = "1"; name = "a"; }
+ # { data = "3"; name = "c"; }
+ # { data = "2"; name = "b"; }
+ # { data = "4"; name = "d"; }
+ # { data = "5"; name = "e"; }
+ # ];
+ # }
+ # true
+ #
+ # And the result on error is
+ #
+ # {
+ # cycle = [ {after = ?; name = ?; data = ?} … ];
+ # loops = [ {after = ?; name = ?; data = ?} … ];
+ # }
+ #
+ # For example
+ #
+ # nix-repl> dagTopoSort {
+ # a = dagEntryAnywhere "1";
+ # b = dagEntryAfter ["a" "c"] "2";
+ # c = dagEntryAfter ["d"] "3";
+ # d = dagEntryAfter ["b"] "4";
+ # e = dagEntryAnywhere "5";
+ # } == {
+ # cycle = [
+ # { after = ["a" "c"]; data = "2"; name = "b"; }
+ # { after = ["d"]; data = "3"; name = "c"; }
+ # { after = ["b"]; data = "4"; name = "d"; }
+ # ];
+ # loops = [
+ # { after = ["a" "c"]; data = "2"; name = "b"; }
+ # ];
+ # } == {}
+ # true
+ dagTopoSort = dag:
+ let
+ dagBefore = dag: name:
+ mapAttrsToList (n: v: n)
+ (filterAttrs (n: v: any (a: a == name) v.before) dag);
+ normalizedDag = mapAttrs (n: v: {
+ name = n;
+ data = v.data;
+ after = v.after ++ dagBefore dag n;
+ }) dag;
+ before = a: b: any (c: a.name == c) b.after;
+ sorted = toposort before (mapAttrsToList (n: v: v) normalizedDag);
+ in if sorted ? result then {
+ result = map (v: { inherit (v) name data; }) sorted.result;
+ } else
+ sorted;
+
+ # Applies a function to each element of the given DAG.
+ dagMap = f: dag: mapAttrs (n: v: v // { data = f n v.data; }) dag;
+
+ # Create a DAG entry with no particular dependency information.
+ dagEntryAnywhere = data: {
+ inherit data;
+ before = [ ];
+ after = [ ];
+ };
+
+ dagEntryBetween = before: after: data: { inherit data before after; };
+
+ dagEntryAfter = after: data: {
+ inherit data after;
+ before = [ ];
+ };
+
+ dagEntryBefore = before: data: {
+ inherit data before;
+ after = [ ];
+ };
+
+}
diff --git a/infra/libkookie/home-manager/modules/lib/default.nix b/infra/libkookie/home-manager/modules/lib/default.nix
new file mode 100644
index 000000000000..7c2c72f709c1
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/lib/default.nix
@@ -0,0 +1,26 @@
+{ lib }:
+
+rec {
+ dag =
+ let
+ d = import ./dag.nix { inherit lib; };
+ in
+ {
+ empty = d.emptyDag;
+ isDag = d.isDag;
+ topoSort = d.dagTopoSort;
+ map = d.dagMap;
+ entryAnywhere = d.dagEntryAnywhere;
+ entryBetween = d.dagEntryBetween;
+ entryAfter = d.dagEntryAfter;
+ entryBefore = d.dagEntryBefore;
+ };
+
+ gvariant = import ./gvariant.nix { inherit lib; };
+ maintainers = import ./maintainers.nix;
+ strings = import ./strings.nix { inherit lib; };
+ types = import ./types.nix { inherit dag gvariant lib; };
+
+ shell = import ./shell.nix { inherit lib; };
+ zsh = import ./zsh.nix { inherit lib; };
+}
diff --git a/infra/libkookie/home-manager/modules/lib/file-type.nix b/infra/libkookie/home-manager/modules/lib/file-type.nix
new file mode 100644
index 000000000000..56a3a1286a01
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/lib/file-type.nix
@@ -0,0 +1,112 @@
+{ homeDirectory, lib, pkgs }:
+
+with lib;
+
+{
+ # Constructs a type suitable for a `home.file` like option. The
+ # target path may be either absolute or relative, in which case it
+ # is relative the `basePath` argument (which itself must be an
+ # absolute path).
+ #
+ # Arguments:
+ # - basePathDesc docbook compatible description of the base path
+ # - basePath the file base path
+ fileType = basePathDesc: basePath: types.attrsOf (types.submodule (
+ { name, config, ... }: {
+ options = {
+ target = mkOption {
+ type = types.str;
+ apply = p:
+ let
+ absPath = if hasPrefix "/" p then p else "${basePath}/${p}";
+ in
+ removePrefix (homeDirectory + "/") absPath;
+ defaultText = literalExample "<name>";
+ description = ''
+ Path to target file relative to ${basePathDesc}.
+ '';
+ };
+
+ text = mkOption {
+ default = null;
+ type = types.nullOr types.lines;
+ description = ''
+ Text of the file. If this option is null then
+ <link linkend="opt-home.file._name_.source">home.file.&lt;name?&gt;.source</link>
+ must be set.
+ '';
+ };
+
+ source = mkOption {
+ type = types.path;
+ description = ''
+ Path of the source file or directory. If
+ <link linkend="opt-home.file._name_.text">home.file.&lt;name?&gt;.text</link>
+ is non-null then this option will automatically point to a file
+ containing that text.
+ '';
+ };
+
+ executable = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = ''
+ Set the execute bit. If <literal>null</literal>, defaults to the mode
+ of the <varname>source</varname> file or to <literal>false</literal>
+ for files created through the <varname>text</varname> option.
+ '';
+ };
+
+ recursive = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ If the file source is a directory, then this option
+ determines whether the directory should be recursively
+ linked to the target location. This option has no effect
+ if the source is a file.
+ </para><para>
+ If <literal>false</literal> (the default) then the target
+ will be a symbolic link to the source directory. If
+ <literal>true</literal> then the target will be a
+ directory structure matching the source's but whose leafs
+ are symbolic links to the files of the source directory.
+ '';
+ };
+
+ onChange = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Shell commands to run when file has changed between
+ generations. The script will be run
+ <emphasis>after</emphasis> the new files have been linked
+ into place.
+ '';
+ };
+
+ force = mkOption {
+ type = types.bool;
+ default = false;
+ visible = false;
+ description = ''
+ Whether the target path should be unconditionally replaced
+ by the managed file source. Warning, this will silently
+ delete the target regardless of whether it is a file or
+ link.
+ '';
+ };
+ };
+
+ config = {
+ target = mkDefault name;
+ source = mkIf (config.text != null) (
+ mkDefault (pkgs.writeTextFile {
+ inherit (config) executable text;
+ name = hm.strings.storeFileName name;
+ })
+ );
+ };
+ }
+ ));
+}
diff --git a/infra/libkookie/home-manager/modules/lib/gvariant.nix b/infra/libkookie/home-manager/modules/lib/gvariant.nix
new file mode 100644
index 000000000000..92aa7d98371a
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/lib/gvariant.nix
@@ -0,0 +1,156 @@
+# A partial and basic implementation of GVariant formatted strings.
+#
+# Note, this API is not considered fully stable and it might therefore
+# change in backwards incompatible ways without prior notice.
+
+{ lib }:
+
+with lib;
+
+let
+
+ mkPrimitive = t: v: {
+ _type = "gvariant";
+ type = t;
+ value = v;
+ __toString = self: "@${self.type} ${toString self.value}";
+ };
+
+ type = {
+ arrayOf = t: "a${t}";
+ maybeOf = t: "m${t}";
+ tupleOf = ts: "(${concatStrings ts})";
+ string = "s";
+ boolean = "b";
+ uchar = "y";
+ int16 = "n";
+ uint16 = "q";
+ int32 = "i";
+ uint32 = "u";
+ int64 = "x";
+ uint64 = "t";
+ double = "d";
+ };
+
+ # Returns the GVariant type of a given Nix value. If no type can be
+ # found for the value then the empty string is returned.
+ typeOf = v:
+ with type;
+ if builtins.isBool v then
+ boolean
+ else if builtins.isInt v then
+ int32
+ else if builtins.isFloat v then
+ double
+ else if builtins.isString v then
+ string
+ else if builtins.isList v then
+ let elemType = elemTypeOf v;
+ in if elemType == "" then "" else arrayOf elemType
+ else if builtins.isAttrs v && v ? type then
+ v.type
+ else
+ "";
+
+ elemTypeOf = vs:
+ if builtins.isList vs then
+ if vs == [ ] then "" else typeOf (head vs)
+ else
+ "";
+
+ mkMaybe = elemType: elem:
+ mkPrimitive (type.maybeOf elemType) elem // {
+ __toString = self:
+ if self.value == null then
+ "@${self.type} nothing"
+ else
+ "just ${toString self.value}";
+ };
+
+in rec {
+
+ inherit type typeOf;
+
+ isArray = hasPrefix "a";
+ isMaybe = hasPrefix "m";
+ isTuple = hasPrefix "(";
+
+ # Returns the GVariant value that most closely matches the given Nix
+ # value. If no GVariant value can be found then `null` is returned.
+ #
+ # No support for dictionaries, maybe types, or variants.
+ mkValue = v:
+ if builtins.isBool v then
+ mkBoolean v
+ else if builtins.isInt v then
+ mkInt32 v
+ else if builtins.isFloat v then
+ mkDouble v
+ else if builtins.isString v then
+ mkString v
+ else if builtins.isList v then
+ if v == [ ] then mkArray type.string [ ] else mkArray (elemTypeOf v) v
+ else if builtins.isAttrs v && (v._type or "") == "gvariant" then
+ v
+ else
+ null;
+
+ mkArray = elemType: elems:
+ mkPrimitive (type.arrayOf elemType) (map mkValue elems) // {
+ __toString = self:
+ "@${self.type} [${concatMapStringsSep "," toString self.value}]";
+ };
+
+ mkEmptyArray = elemType: mkArray elemType [ ];
+
+ mkNothing = elemType: mkMaybe elemType null;
+
+ mkJust = elem: let gvarElem = mkValue elem; in mkMaybe gvarElem.type gvarElem;
+
+ mkTuple = elems:
+ let
+ gvarElems = map mkValue elems;
+ tupleType = type.tupleOf (map (e: e.type) gvarElems);
+ in mkPrimitive tupleType gvarElems // {
+ __toString = self:
+ "@${self.type} (${concatMapStringsSep "," toString self.value})";
+ };
+
+ mkBoolean = v:
+ mkPrimitive type.boolean v // {
+ __toString = self: if self.value then "true" else "false";
+ };
+
+ mkString = v:
+ mkPrimitive type.string v // {
+ __toString = self: "'${escape [ "'" "\\" ] self.value}'";
+ };
+
+ mkObjectpath = v:
+ mkPrimitive type.string v // {
+ __toString = self: "objectpath '${escape [ "'" ] self.value}'";
+ };
+
+ mkUchar = mkPrimitive type.uchar;
+
+ mkInt16 = mkPrimitive type.int16;
+
+ mkUint16 = mkPrimitive type.uint16;
+
+ mkInt32 = v:
+ mkPrimitive type.int32 v // {
+ __toString = self: toString self.value;
+ };
+
+ mkUint32 = mkPrimitive type.uint32;
+
+ mkInt64 = mkPrimitive type.int64;
+
+ mkUint64 = mkPrimitive type.uint64;
+
+ mkDouble = v:
+ mkPrimitive type.double v // {
+ __toString = self: toString self.value;
+ };
+
+}
diff --git a/infra/libkookie/home-manager/modules/lib/maintainers.nix b/infra/libkookie/home-manager/modules/lib/maintainers.nix
new file mode 100644
index 000000000000..de1808015cef
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/lib/maintainers.nix
@@ -0,0 +1,50 @@
+# Home Manager maintainers.
+#
+# This attribute set contains Home Manager module maintainers that do
+# not have an entry in the Nixpkgs maintainer list [1]. Entries here
+# are expected to be follow the same format as described in [1].
+#
+# [1] https://github.com/NixOS/nixpkgs/blob/fca0d6e093c82b31103dc0dacc48da2a9b06e24b/maintainers/maintainer-list.nix#LC1
+
+{
+ justinlovinger = {
+ name = "Justin Lovinger";
+ email = "git@justinlovinger.com";
+ github = "JustinLovinger";
+ githubId = 7183441;
+ };
+ owm111 = {
+ email = "7798336+owm111@users.noreply.github.com";
+ name = "Owen McGrath";
+ github = "owm111";
+ githubId = 7798336;
+ };
+ cwyc = {
+ email = "cwyc@users.noreply.github.com";
+ name = "cwyc";
+ github = "cwyc";
+ githubId = 16950437;
+ };
+ olmokramer = {
+ name = "Olmo Kramer";
+ email = "olmokramer@users.noreply.github.com";
+ github = "olmokramer";
+ githubId = 3612514;
+ };
+ matrss = {
+ name = "Matthias Riße";
+ email = "matrss@users.noreply.github.com";
+ github = "matrss";
+ githubId = 9308656;
+ };
+ seylerius = {
+ email = "sable@seyleri.us";
+ name = "Sable Seyler";
+ github = "seylerius";
+ githubId = 1145981;
+ keys = [{
+ logkeyid = "rsa4096/0x68BF2EAE6D91CAFF";
+ fingerprint = "F0E0 0311 126A CD72 4392 25E6 68BF 2EAE 6D91 CAFF";
+ }];
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/lib/shell.nix b/infra/libkookie/home-manager/modules/lib/shell.nix
new file mode 100644
index 000000000000..5e5743f51ea6
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/lib/shell.nix
@@ -0,0 +1,11 @@
+{ lib }:
+
+rec {
+ # Produces a Bourne shell like variable export statement.
+ export = n: v: ''export ${n}="${toString v}"'';
+
+ # Given an attribute set containing shell variable names and their
+ # assignment, this function produces a string containing an export
+ # statement for each set entry.
+ exportAll = vars: lib.concatStringsSep "\n" (lib.mapAttrsToList export vars);
+}
diff --git a/infra/libkookie/home-manager/modules/lib/stdlib-extended.nix b/infra/libkookie/home-manager/modules/lib/stdlib-extended.nix
new file mode 100644
index 000000000000..93f2397cee82
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/lib/stdlib-extended.nix
@@ -0,0 +1,7 @@
+# Just a convenience function that returns the given Nixpkgs standard
+# library extended with the HM library.
+
+nixpkgsLib:
+
+let mkHmLib = import ./.;
+in nixpkgsLib.extend (self: super: { hm = mkHmLib { lib = super; }; })
diff --git a/infra/libkookie/home-manager/modules/lib/strings.nix b/infra/libkookie/home-manager/modules/lib/strings.nix
new file mode 100644
index 000000000000..fe7b2fa30615
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/lib/strings.nix
@@ -0,0 +1,22 @@
+{ lib }:
+
+with lib;
+
+{
+ # Figures out a valid Nix store name for the given path.
+ storeFileName = path:
+ let
+ # All characters that are considered safe. Note "-" is not
+ # included to avoid "-" followed by digit being interpreted as a
+ # version.
+ safeChars = [ "+" "." "_" "?" "=" ] ++ lowerChars ++ upperChars
+ ++ stringToCharacters "0123456789";
+
+ empties = l: genList (x: "") (length l);
+
+ unsafeInName =
+ stringToCharacters (replaceStrings safeChars (empties safeChars) path);
+
+ safeName = replaceStrings unsafeInName (empties unsafeInName) path;
+ in "hm_" + safeName;
+}
diff --git a/infra/libkookie/home-manager/modules/lib/types-dag.nix b/infra/libkookie/home-manager/modules/lib/types-dag.nix
new file mode 100644
index 000000000000..2efb12645d4d
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/lib/types-dag.nix
@@ -0,0 +1,99 @@
+{ dag, lib }:
+
+with lib;
+
+let
+
+ isDagEntry = e: isAttrs e && (e ? data) && (e ? after) && (e ? before);
+
+ dagContentType = elemType:
+ types.submodule ({ name, ... }: {
+ options = {
+ data = mkOption { type = elemType; };
+ after = mkOption { type = with types; uniq (listOf str); };
+ before = mkOption { type = with types; uniq (listOf str); };
+ };
+ config = mkIf (elemType.name == "submodule") {
+ data._module.args.dagName = name;
+ };
+ });
+
+in rec {
+ # A directed acyclic graph of some inner type.
+ #
+ # Note, if the element type is a submodule then the `name` argument
+ # will always be set to the string "data" since it picks up the
+ # internal structure of the DAG values. To give access to the
+ # "actual" attribute name a new submodule argument is provided with
+ # the name `dagName`.
+ dagOf = elemType:
+ let
+ convertAllToDags = let
+ maybeConvert = n: v: if isDagEntry v then v else dag.entryAnywhere v;
+ in map (def: def // { value = mapAttrs maybeConvert def.value; });
+
+ attrEquivalent = types.attrsOf (dagContentType elemType);
+ in mkOptionType rec {
+ name = "dagOf";
+ description = "DAG of ${elemType.description}s";
+ check = isAttrs;
+ merge = loc: defs: attrEquivalent.merge loc (convertAllToDags defs);
+ getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "<name>" ]);
+ getSubModules = elemType.getSubModules;
+ substSubModules = m: dagOf (elemType.substSubModules m);
+ functor = (defaultFunctor name) // { wrapped = elemType; };
+ };
+
+ # A directed acyclic graph of some inner type OR a list of that
+ # inner type. This is a temporary hack for use by the
+ # `programs.ssh.matchBlocks` and is only guaranteed to be vaguely
+ # correct!
+ #
+ # In particular, adding a dependency on one of the "unnamed-N-M"
+ # entries generated by a list value is almost guaranteed to destroy
+ # the list's order.
+ #
+ # This function will be removed in version 20.09.
+ listOrDagOf = elemType:
+ let
+ paddedIndexStr = list: i:
+ let padWidth = stringLength (toString (length list));
+ in fixedWidthNumber padWidth i;
+
+ convertAll = loc: defs:
+ let
+ convertListValue = namePrefix: def:
+ let
+ vs = def.value;
+ pad = paddedIndexStr vs;
+ makeEntry = i: v: nameValuePair "${namePrefix}.${pad i}" v;
+ warning = ''
+ In file ${def.file}
+ a list is being assigned to the option '${
+ concatStringsSep "." loc
+ }'.
+ This will soon be an error due to the list form being deprecated.
+ Please use the attribute set form instead with DAG functions to
+ express the desired order of entries.
+ '';
+ in warn warning (listToAttrs (imap1 makeEntry vs));
+
+ convertValue = i: def:
+ if isList def.value then
+ convertListValue "unnamed-${paddedIndexStr defs i}" def
+ else
+ def.value;
+ in imap1 (i: def: def // { value = convertValue i def; }) defs;
+
+ dagType = dagOf elemType;
+ in mkOptionType rec {
+ name = "listOrDagOf";
+ description = "list or DAG of ${elemType.description}s";
+ check = x: isList x || dagType.check x;
+ merge = loc: defs: dagType.merge loc (convertAll loc defs);
+ getSubOptions = dagType.getSubOptions;
+ getSubModules = dagType.getSubModules;
+ substSubModules = m: listOrDagOf (elemType.substSubModules m);
+ functor = (defaultFunctor name) // { wrapped = elemType; };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/lib/types.nix b/infra/libkookie/home-manager/modules/lib/types.nix
new file mode 100644
index 000000000000..64a6b4a34fa0
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/lib/types.nix
@@ -0,0 +1,90 @@
+{ lib, dag ? import ./dag.nix { inherit lib; }
+, gvariant ? import ./gvariant.nix { inherit lib; } }:
+
+with lib;
+
+let
+
+ typesDag = import ./types-dag.nix { inherit dag lib; };
+
+ # Needed since the type is called gvariant and its merge attribute
+ # must refer back to the type.
+ gvar = gvariant;
+
+in rec {
+
+ inherit (typesDag) dagOf listOrDagOf;
+
+ selectorFunction = mkOptionType {
+ name = "selectorFunction";
+ description = "Function that takes an attribute set and returns a list"
+ + " containing a selection of the values of the input set";
+ check = isFunction;
+ merge = _loc: defs: as: concatMap (select: select as) (getValues defs);
+ };
+
+ overlayFunction = mkOptionType {
+ name = "overlayFunction";
+ description = "An overlay function, takes self and super and returns"
+ + " an attribute set overriding the desired attributes.";
+ check = isFunction;
+ merge = _loc: defs: self: super:
+ foldl' (res: def: mergeAttrs res (def.value self super)) { } defs;
+ };
+
+ fontType = types.submodule {
+ options = {
+ package = mkOption {
+ type = types.nullOr types.package;
+ default = null;
+ example = literalExample "pkgs.dejavu_fonts";
+ description = ''
+ Package providing the font. This package will be installed
+ to your profile. If <literal>null</literal> then the font
+ is assumed to already be available in your profile.
+ '';
+ };
+
+ name = mkOption {
+ type = types.str;
+ example = "DejaVu Sans 8";
+ description = ''
+ The family name and size of the font within the package.
+ '';
+ };
+ };
+ };
+
+ gvariant = mkOptionType rec {
+ name = "gvariant";
+ description = "GVariant value";
+ check = v: gvar.mkValue v != null;
+ merge = loc: defs:
+ let
+ vdefs = map (d: d // { value = gvar.mkValue d.value; }) defs;
+ vals = map (d: d.value) vdefs;
+ defTypes = map (x: x.type) vals;
+ sameOrNull = x: y: if x == y then y else null;
+ # A bit naive to just check the first entry…
+ sharedDefType = foldl' sameOrNull (head defTypes) defTypes;
+ allChecked = all (x: check x) vals;
+ in if sharedDefType == null then
+ throw ("Cannot merge definitions of `${showOption loc}' with"
+ + " mismatched GVariant types given in"
+ + " ${showFiles (getFiles defs)}.")
+ else if gvar.isArray sharedDefType && allChecked then
+ (types.listOf gvariant).merge loc
+ (map (d: d // { value = d.value.value; }) vdefs)
+ else if gvar.isTuple sharedDefType && allChecked then
+ mergeOneOption loc defs
+ else if gvar.isMaybe sharedDefType && allChecked then
+ mergeOneOption loc defs
+ else if gvar.type.string == sharedDefType && allChecked then
+ types.str.merge loc defs
+ else if gvar.type.double == sharedDefType && allChecked then
+ types.float.merge loc defs
+ else
+ mergeDefaultOption loc defs;
+ };
+
+}
diff --git a/infra/libkookie/home-manager/modules/lib/zsh.nix b/infra/libkookie/home-manager/modules/lib/zsh.nix
new file mode 100644
index 000000000000..c6901350f50d
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/lib/zsh.nix
@@ -0,0 +1,30 @@
+{ lib }:
+
+rec {
+ # Produces a Zsh shell like value
+ toZshValue = v:
+ if builtins.isBool v then
+ if v then "true" else "false"
+ else if builtins.isString v then
+ ''"${v}"''
+ else if builtins.isList v then
+ "(${lib.concatStringsSep " " (map toZshValue v)})"
+ else
+ ''"${toString v}"'';
+
+ # Produces a Zsh shell like definition statement
+ define = n: v: "${n}=${toZshValue v}";
+
+ # Given an attribute set containing shell variable names and their
+ # assignments, this function produces a string containing a definition
+ # statement for each set entry.
+ defineAll = vars: lib.concatStringsSep "\n" (lib.mapAttrsToList define vars);
+
+ # Produces a Zsh shell like export statement
+ export = n: v: "export ${define n v}";
+
+ # Given an attribute set containing shell variable names and their
+ # assignments, this function produces a string containing an export
+ # statement for each set entry.
+ exportAll = vars: lib.concatStringsSep "\n" (lib.mapAttrsToList export vars);
+}
diff --git a/infra/libkookie/home-manager/modules/manual.nix b/infra/libkookie/home-manager/modules/manual.nix
new file mode 100644
index 000000000000..ab01c45003eb
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/manual.nix
@@ -0,0 +1,68 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.manual;
+
+ docs = import ../doc { inherit lib pkgs; };
+
+in
+
+{
+ options = {
+ manual.html.enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to install the HTML manual. This also installs the
+ <command>home-manager-help</command> tool, which opens a local
+ copy of the Home Manager manual in the system web browser.
+ '';
+ };
+
+ manual.manpages.enable = mkOption {
+ type = types.bool;
+ default = true;
+ example = false;
+ description = ''
+ Whether to install the configuration manual page. The manual can
+ be reached by <command>man home-configuration.nix</command>.
+ </para><para>
+ When looking at the manual page pretend that all references to
+ NixOS stuff are actually references to Home Manager stuff.
+ Thanks!
+ '';
+ };
+
+ manual.json.enable = mkOption {
+ type = types.bool;
+ default = false;
+ example = true;
+ description = ''
+ Whether to install a JSON formatted list of all Home Manager
+ options. This can be located at
+ <filename>&lt;profile directory&gt;/share/doc/home-manager/options.json</filename>,
+ and may be used for navigating definitions, auto-completing,
+ and other miscellaneous tasks.
+ '';
+ };
+ };
+
+ config = {
+ home.packages = mkMerge [
+ (mkIf cfg.html.enable [ docs.manual.html docs.manual.htmlOpenTool ])
+ (mkIf cfg.manpages.enable [ docs.manPages ])
+ (mkIf cfg.json.enable [ docs.options.json ])
+ ];
+
+ # Whether a dependency on nmd should be introduced.
+ home.extraBuilderCommands =
+ mkIf (cfg.html.enable || cfg.manpages.enable || cfg.json.enable) ''
+ mkdir $out/lib
+ ln -s ${docs.nmdSrc} $out/lib/nmd
+ '';
+ };
+
+}
diff --git a/infra/libkookie/home-manager/modules/misc/dconf.nix b/infra/libkookie/home-manager/modules/misc/dconf.nix
new file mode 100644
index 000000000000..5fc7748a76b0
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/misc/dconf.nix
@@ -0,0 +1,73 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.dconf;
+
+ toDconfIni = generators.toINI { mkKeyValue = mkIniKeyValue; };
+
+ mkIniKeyValue = key: value:
+ "${key}=${toString (hm.gvariant.mkValue value)}";
+
+in
+
+{
+ meta.maintainers = [ maintainers.gnidorah maintainers.rycee ];
+
+ options = {
+ dconf = {
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ visible = false;
+ description = ''
+ Whether to enable dconf settings.
+ '';
+ };
+
+ settings = mkOption {
+ type = with types; attrsOf (attrsOf hm.types.gvariant);
+ default = {};
+ example = literalExample ''
+ {
+ "org/gnome/calculator" = {
+ button-mode = "programming";
+ show-thousands = true;
+ base = 10;
+ word-size = 64;
+ window-position = lib.hm.gvariant.mkTuple [100 100];
+ };
+ }
+ '';
+ description = ''
+ Settings to write to the dconf configuration system.
+ '';
+ };
+ };
+ };
+
+ config = mkIf (cfg.enable && cfg.settings != {}) {
+ home.activation.dconfSettings = hm.dag.entryAfter ["installPackages"] (
+ let
+ iniFile = pkgs.writeText "hm-dconf.ini" (toDconfIni cfg.settings);
+ in
+ ''
+ if [[ -v DBUS_SESSION_BUS_ADDRESS ]]; then
+ DCONF_DBUS_RUN_SESSION=""
+ else
+ DCONF_DBUS_RUN_SESSION="${pkgs.dbus}/bin/dbus-run-session"
+ fi
+
+ if [[ -v DRY_RUN ]]; then
+ echo $DCONF_DBUS_RUN_SESSION ${pkgs.dconf}/bin/dconf load / "<" ${iniFile}
+ else
+ $DCONF_DBUS_RUN_SESSION ${pkgs.dconf}/bin/dconf load / < ${iniFile}
+ fi
+
+ unset DCONF_DBUS_RUN_SESSION
+ ''
+ );
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/misc/debug.nix b/infra/libkookie/home-manager/modules/misc/debug.nix
new file mode 100644
index 000000000000..d27d496b4239
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/misc/debug.nix
@@ -0,0 +1,26 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+ options.home = {
+ enableDebugInfo = mkEnableOption "" // {
+ description = ''
+ Some Nix-packages provide debug symbols for
+ <command>gdb</command> in the <literal>debug</literal>-output.
+ This option ensures that those are automatically fetched from
+ the binary cache if available and <command>gdb</command> is
+ configured to find those symbols.
+ '';
+ };
+ };
+
+ config = mkIf config.home.enableDebugInfo {
+ home.extraOutputsToInstall = [ "debug" ];
+
+ home.sessionVariables = {
+ NIX_DEBUG_INFO_DIRS =
+ "$NIX_DEBUG_INFO_DIRS\${NIX_DEBUG_INFO_DIRS:+:}${config.home.profileDirectory}/lib/debug";
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/misc/fontconfig.nix b/infra/libkookie/home-manager/modules/misc/fontconfig.nix
new file mode 100644
index 000000000000..795ab3a74f63
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/misc/fontconfig.nix
@@ -0,0 +1,105 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.fonts.fontconfig;
+
+ profileDirectory = config.home.profileDirectory;
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ imports = [
+ (mkRenamedOptionModule [ "fonts" "fontconfig" "enableProfileFonts" ] [
+ "fonts"
+ "fontconfig"
+ "enable"
+ ])
+ ];
+
+ options = {
+ fonts.fontconfig = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable fontconfig configuration. This will, for
+ example, allow fontconfig to discover fonts and
+ configurations installed through
+ <varname>home.packages</varname> and
+ <command>nix-env</command>.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ # Create two dummy files in /lib/fontconfig to make sure that
+ # buildEnv creates a real directory path. These files are removed
+ # in home.extraProfileCommands below so the packages will not
+ # become "runtime" dependencies.
+ home.packages = [
+ (pkgs.writeTextFile {
+ name = "hm-dummy1";
+ destination = "/lib/fontconfig/hm-dummy1";
+ text = "dummy";
+ })
+
+ (pkgs.writeTextFile {
+ name = "hm-dummy2";
+ destination = "/lib/fontconfig/hm-dummy2";
+ text = "dummy";
+ })
+ ];
+
+ home.extraProfileCommands = ''
+ if [[ -d $out/lib/X11/fonts || -d $out/share/fonts ]]; then
+ export FONTCONFIG_FILE="$(pwd)/fonts.conf"
+
+ cat > $FONTCONFIG_FILE << EOF
+ <?xml version='1.0'?>
+ <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
+ <fontconfig>
+ <dir>$out/lib/X11/fonts</dir>
+ <dir>$out/share/fonts</dir>
+ <cachedir>$out/lib/fontconfig/cache</cachedir>
+ </fontconfig>
+ EOF
+
+ ${getBin pkgs.fontconfig}/bin/fc-cache -f
+ rm -f $out/lib/fontconfig/cache/CACHEDIR.TAG
+ rmdir --ignore-fail-on-non-empty -p $out/lib/fontconfig/cache
+
+ rm "$FONTCONFIG_FILE"
+ unset FONTCONFIG_FILE
+ fi
+
+ # Remove hacky dummy files.
+ rm $out/lib/fontconfig/hm-dummy?
+ rmdir --ignore-fail-on-non-empty -p $out/lib/fontconfig
+ '';
+
+ xdg.configFile = {
+ "fontconfig/conf.d/10-hm-fonts.conf".text = ''
+ <?xml version='1.0'?>
+
+ <!-- Generated by Home Manager. -->
+
+ <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
+ <fontconfig>
+ <include ignore_missing="yes">${config.home.path}/etc/fonts/conf.d</include>
+ <include ignore_missing="yes">${config.home.path}/etc/fonts/fonts.conf</include>
+
+ <dir>${config.home.path}/lib/X11/fonts</dir>
+ <dir>${config.home.path}/share/fonts</dir>
+ <dir>${profileDirectory}/lib/X11/fonts</dir>
+ <dir>${profileDirectory}/share/fonts</dir>
+
+ <cachedir>${config.home.path}/lib/fontconfig/cache</cachedir>
+ </fontconfig>
+ '';
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/misc/gtk.nix b/infra/libkookie/home-manager/modules/misc/gtk.nix
new file mode 100644
index 000000000000..bf25aaaf664b
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/misc/gtk.nix
@@ -0,0 +1,164 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.gtk;
+ cfg2 = config.gtk.gtk2;
+ cfg3 = config.gtk.gtk3;
+
+ toGtk3Ini = generators.toINI {
+ mkKeyValue = key: value:
+ let
+ value' = if isBool value then
+ (if value then "true" else "false")
+ else
+ toString value;
+ in "${key}=${value'}";
+ };
+
+ formatGtk2Option = n: v:
+ let
+ v' = if isBool v then
+ (if v then "true" else "false")
+ else if isString v then
+ ''"${v}"''
+ else
+ toString v;
+ in "${n} = ${v'}";
+
+ themeType = types.submodule {
+ options = {
+ package = mkOption {
+ type = types.nullOr types.package;
+ default = null;
+ example = literalExample "pkgs.gnome3.gnome_themes_standard";
+ description = ''
+ Package providing the theme. This package will be installed
+ to your profile. If <literal>null</literal> then the theme
+ is assumed to already be available in your profile.
+ '';
+ };
+
+ name = mkOption {
+ type = types.str;
+ example = "Adwaita";
+ description = "The name of the theme within the package.";
+ };
+ };
+ };
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ imports = [
+ (mkRemovedOptionModule [ "gtk" "gtk3" "waylandSupport" ] ''
+ This options is not longer needed and can be removed.
+ '')
+ ];
+
+ options = {
+ gtk = {
+ enable = mkEnableOption "GTK 2/3 configuration";
+
+ font = mkOption {
+ type = types.nullOr hm.types.fontType;
+ default = null;
+ description = ''
+ The font to use in GTK+ 2/3 applications.
+ '';
+ };
+
+ iconTheme = mkOption {
+ type = types.nullOr themeType;
+ default = null;
+ description = "The icon theme to use.";
+ };
+
+ theme = mkOption {
+ type = types.nullOr themeType;
+ default = null;
+ description = "The GTK+2/3 theme to use.";
+ };
+
+ gtk2 = {
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = "gtk-can-change-accels = 1";
+ description = ''
+ Extra configuration lines to add verbatim to
+ <filename>~/.gtkrc-2.0</filename>.
+ '';
+ };
+ };
+
+ gtk3 = {
+ bookmarks = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "file:///home/jane/Documents" ];
+ description = "Bookmarks in the sidebar of the GTK file browser";
+ };
+
+ extraConfig = mkOption {
+ type = with types; attrsOf (either bool (either int str));
+ default = { };
+ example = {
+ gtk-cursor-blink = false;
+ gtk-recent-files-limit = 20;
+ };
+ description = ''
+ Extra configuration options to add to
+ <filename>~/.config/gtk-3.0/settings.ini</filename>.
+ '';
+ };
+
+ extraCss = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration lines to add verbatim to
+ <filename>~/.config/gtk-3.0/gtk.css</filename>.
+ '';
+ };
+ };
+ };
+ };
+
+ config = mkIf cfg.enable (let
+ ini = optionalAttrs (cfg.font != null) { gtk-font-name = cfg.font.name; }
+ // optionalAttrs (cfg.theme != null) { gtk-theme-name = cfg.theme.name; }
+ // optionalAttrs (cfg.iconTheme != null) {
+ gtk-icon-theme-name = cfg.iconTheme.name;
+ };
+
+ dconfIni = optionalAttrs (cfg.font != null) { font-name = cfg.font.name; }
+ // optionalAttrs (cfg.theme != null) { gtk-theme = cfg.theme.name; }
+ // optionalAttrs (cfg.iconTheme != null) {
+ icon-theme = cfg.iconTheme.name;
+ };
+
+ optionalPackage = opt:
+ optional (opt != null && opt.package != null) opt.package;
+ in {
+ home.packages = optionalPackage cfg.font ++ optionalPackage cfg.theme
+ ++ optionalPackage cfg.iconTheme;
+
+ home.file.".gtkrc-2.0".text =
+ concatStringsSep "\n" (mapAttrsToList formatGtk2Option ini) + "\n"
+ + cfg2.extraConfig;
+
+ xdg.configFile."gtk-3.0/settings.ini".text =
+ toGtk3Ini { Settings = ini // cfg3.extraConfig; };
+
+ xdg.configFile."gtk-3.0/gtk.css".text = cfg3.extraCss;
+
+ xdg.configFile."gtk-3.0/bookmarks" = mkIf (cfg3.bookmarks != [ ]) {
+ text = concatStringsSep "\n" cfg3.bookmarks;
+ };
+
+ dconf.settings."org/gnome/desktop/interface" = dconfIni;
+ });
+}
diff --git a/infra/libkookie/home-manager/modules/misc/lib.nix b/infra/libkookie/home-manager/modules/misc/lib.nix
new file mode 100644
index 000000000000..13c00dc59a64
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/misc/lib.nix
@@ -0,0 +1,14 @@
+{ lib, ... }:
+
+{
+ options = {
+ lib = lib.mkOption {
+ type = lib.types.attrsOf lib.types.attrs;
+ default = { };
+ description = ''
+ This option allows modules to define helper functions,
+ constants, etc.
+ '';
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/misc/news.nix b/infra/libkookie/home-manager/modules/misc/news.nix
new file mode 100644
index 000000000000..db2a15abd56e
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/misc/news.nix
@@ -0,0 +1,1787 @@
+{ config, lib, options, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.news;
+
+ hostPlatform = pkgs.stdenv.hostPlatform;
+
+ entryModule = types.submodule ({ config, ... }: {
+ options = {
+ id = mkOption {
+ internal = true;
+ type = types.str;
+ description = ''
+ A unique entry identifier. By default it is a base16
+ formatted hash of the entry message.
+ '';
+ };
+
+ time = mkOption {
+ internal = true;
+ type = types.str;
+ example = "2017-07-10T21:55:04+00:00";
+ description = ''
+ News entry time stamp in ISO-8601 format. Must be in UTC
+ (ending in '+00:00').
+ '';
+ };
+
+ condition = mkOption {
+ internal = true;
+ default = true;
+ description = "Whether the news entry should be active.";
+ };
+
+ message = mkOption {
+ internal = true;
+ type = types.str;
+ description = "The news entry content.";
+ };
+ };
+
+ config = {
+ id = mkDefault (builtins.hashString "sha256" config.message);
+ };
+ });
+
+in
+
+{
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ news = {
+ display = mkOption {
+ type = types.enum [ "silent" "notify" "show" ];
+ default = "notify";
+ description = ''
+ How unread and relevant news should be presented when
+ running <command>home-manager build</command> and
+ <command>home-manager switch</command>.
+
+ </para><para>
+
+ The options are
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>silent</literal></term>
+ <listitem>
+ <para>
+ Do not print anything during build or switch. The
+ <command>home-manager news</command> command still
+ works for viewing the entries.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>notify</literal></term>
+ <listitem>
+ <para>
+ The number of unread and relevant news entries will be
+ printed to standard output. The <command>home-manager
+ news</command> command can later be used to view the
+ entries.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>show</literal></term>
+ <listitem>
+ <para>
+ A pager showing unread news entries is opened.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ '';
+ };
+
+ entries = mkOption {
+ internal = true;
+ type = types.listOf entryModule;
+ default = [];
+ description = "News entries.";
+ };
+ };
+ };
+
+ config = {
+ # Add news entries in chronological order (i.e., latest time
+ # should be at the bottom of the list). The time should be
+ # formatted as given in the output of
+ #
+ # date --iso-8601=second --universal
+ #
+ news.entries = [
+ {
+ time = "2017-09-01T10:56:28+00:00";
+ message = ''
+ Hello! This is a news entry and it represents an
+ experimental new feature of Home Manager. The idea is to
+ inform you when something of importance happens in Home
+ Manager or its modules.
+
+ We will try to not disturb you about the same news more than
+ once so the next time you run
+
+ home-manager switch
+
+ or
+
+ home-manager build
+
+ it should not notify you about this text again.
+
+ News items may be conditional and will then only show if the
+ condition holds, for example if they are relevant to your
+ configuration.
+
+ If you want to see all relevant news then please use the
+
+ home-manager news
+
+ command.
+
+ Since this is an experimental feature any positive or
+ negative feedback would be greatly appreciated. For example,
+ by commenting in https://git.io/v5BJL.
+ '';
+ }
+
+ {
+ time = "2017-09-10T22:15:19+00:00";
+ condition = config.programs.zsh.enable;
+ message = ''
+ Home Manager now offers its own minimal zsh plugin manager
+ under the 'programs.zsh.plugins' option path. By statically
+ sourcing your plugins it achieves no startup overhead.
+ '';
+ }
+
+ {
+ time = "2017-09-12T13:11:48+00:00";
+ condition = (
+ config.programs.zsh.enable &&
+ config.programs.zsh.shellAliases != {}
+ );
+ message = ''
+ Aliases defined in 'programs.zsh.shellAliases'
+ are now have the highest priority. Such aliases will
+ not be redefined by the code in 'programs.zsh.initExtra'
+ or any external plugins.
+ '';
+ }
+
+ {
+ time = "2017-09-12T14:22:18+00:00";
+ message = ''
+ A new service is available: 'services.blueman-applet'.
+ '';
+ }
+
+ {
+ time = "2017-09-13T11:30:22+00:00";
+ message = ''
+ A new service is available: 'services.compton'.
+ '';
+ }
+
+ {
+ time = "2017-09-20T14:47:14+00:00";
+ message = ''
+ A new service is available: 'services.screen-locker'.
+ '';
+ }
+
+ {
+ time = "2017-09-22T12:09:01+00:00";
+ condition = isString config.programs.git.extraConfig;
+ message = ''
+ The 'programs.git.extraConfig' parameter now accepts
+ attributes instead of strings which allows more flexible
+ configuration.
+
+ The string parameter type will be deprecated in the future,
+ please change your configuration file accordingly.
+
+ For example, if your configuration includes
+
+ programs.git.extraConfig = '''
+ [core]
+ editor = vim
+ ''';
+
+ then you can now change it to
+
+ programs.git.extraConfig = {
+ core = {
+ editor = "vim";
+ };
+ };
+ '';
+ }
+
+ {
+ time = "2017-09-27T07:28:54+00:00";
+ message = ''
+ A new program module is available: 'programs.command-not-found'.
+
+ Note, this differs from the NixOS system command-not-found
+ tool in that NIX_AUTO_INSTALL is not supported.
+ '';
+ }
+
+ {
+ time = "2017-09-28T12:39:36+00:00";
+ message = ''
+ A new program module is available: 'programs.rofi';
+ '';
+ }
+
+ {
+ time = "2017-10-02T11:15:03+00:00";
+ condition = config.services.udiskie.enable;
+ message = ''
+ The udiskie service now defaults to automatically mounting
+ new devices. Previous behavior was to not automatically
+ mount. To restore this previous behavior add
+
+ services.udiskie.automount = false;
+
+ to your Home Manager configuration.
+ '';
+ }
+
+ {
+ time = "2017-10-04T18:36:07+00:00";
+ message = ''
+ A new module is available: 'xsession.windowManager.xmonad'.
+ '';
+ }
+
+ {
+ time = "2017-10-06T08:21:43+00:00";
+ message = ''
+ A new service is available: 'services.polybar'.
+ '';
+ }
+
+ {
+ time = "2017-10-09T16:38:34+00:00";
+ message = ''
+ A new module is available: 'fonts.fontconfig'.
+
+ In particular, the Boolean option
+
+ fonts.fontconfig.enableProfileFonts
+
+ was added for those who do not use NixOS and want to install
+ font packages using 'nix-env' or 'home.packages'. If you are
+ using NixOS then you do not need to enable this option.
+ '';
+ }
+
+ {
+ time = "2017-10-12T11:21:45+00:00";
+ condition = config.programs.zsh.enable;
+ message = ''
+ A new option in zsh module is available: 'programs.zsh.sessionVariables'.
+
+ This option can be used to set zsh specific session variables which
+ will be set only on zsh launch.
+ '';
+ }
+
+ {
+ time = "2017-10-15T13:59:47+00:00";
+ message = ''
+ A new module is available: 'programs.man'.
+
+ This module is enabled by default and makes sure that manual
+ pages are installed for packages in 'home.packages'.
+ '';
+ }
+
+ {
+ time = "2017-10-20T12:15:27+00:00";
+ condition = with config.systemd.user;
+ services != {} || sockets != {} || targets != {} || timers != {};
+ message = ''
+ Home Manager's interaction with systemd is now done using
+ 'systemctl' from Nixpkgs, not the 'systemctl' in '$PATH'.
+
+ If you are using a distribution whose systemd is
+ incompatible with the version in Nixpkgs then you can
+ override this behavior by adding
+
+ systemd.user.systemctlPath = "/usr/bin/systemctl"
+
+ to your configuration. Home Manager will then use your
+ chosen version.
+ '';
+ }
+
+ {
+ time = "2017-10-23T23:10:29+00:00";
+ condition = !config.programs.home-manager.enable;
+ message = ''
+ Unfortunately, due to some internal restructuring it is no
+ longer possible to install the home-manager command when
+ having
+
+ home-manager = import ./home-manager { inherit pkgs; };
+
+ in the '~/.config/nixpkgs/config.nix' package override
+ section. Attempting to use the above override will now
+ result in the error "cannot coerce a set to a string".
+
+ To resolve this please delete the override from the
+ 'config.nix' file and either link the Home Manager overlay
+
+ $ ln -s ~/.config/nixpkgs/home-manager/overlay.nix \
+ ~/.config/nixpkgs/overlays/home-manager.nix
+
+ or add
+
+ programs.home-manager.enable = true;
+
+ to your Home Manager configuration. The latter is
+ recommended as the home-manager tool then is updated
+ automatically whenever you do a switch.
+ '';
+ }
+
+ {
+ time = "2017-10-23T23:26:17+00:00";
+ message = ''
+ A new module is available: 'nixpkgs'.
+
+ Like the identically named NixOS module, this allows you to
+ set Nixpkgs options and define Nixpkgs overlays. Note, the
+ changes you make here will not automatically apply to Nix
+ commands run outside Home Manager.
+ '';
+ }
+
+ {
+ time = "2017-10-28T23:39:55+00:00";
+ message = ''
+ A new module is available: 'xdg'.
+
+ If enabled, this module allows configuration of the XDG base
+ directory paths.
+
+ Whether the module is enabled or not, it also offers the
+ option 'xdg.configFile', which acts much like 'home.file'
+ except the target path is relative to the XDG configuration
+ directory. That is, unless `XDG_CONFIG_HOME` is configured
+ otherwise, the assignment
+
+ xdg.configFile.hello.text = "hello world";
+
+ will result in a file '$HOME/.config/hello'.
+
+ Most modules in Home Manager that previously were hard coded
+ to write configuration to '$HOME/.config' now use this
+ option and will therefore honor the XDG configuration
+ directory.
+ '';
+ }
+
+ {
+ time = "2017-10-31T11:46:07+00:00";
+ message = ''
+ A new window manager module is available: 'xsession.windowManager.i3'.
+ '';
+ }
+
+ {
+ time = "2017-11-12T00:18:59+00:00";
+ message = ''
+ A new program module is available: 'programs.neovim'.
+ '';
+ }
+
+ {
+ time = "2017-11-14T19:56:49+00:00";
+ condition = with config.xsession.windowManager; (
+ i3.enable && i3.config != null && i3.config.startup != []
+ );
+ message = ''
+ A new 'notification' option was added to
+ xsession.windowManager.i3.startup submodule.
+
+ Startup commands are now executed with the startup-notification
+ support enabled by default. Please, set 'notification' to false
+ where --no-startup-id option is necessary.
+ '';
+ }
+
+ {
+ time = "2017-11-17T10:36:10+00:00";
+ condition = config.xsession.windowManager.i3.enable;
+ message = ''
+ The i3 window manager module has been extended with the following options:
+
+ i3.config.keycodebindings
+ i3.config.window.commands
+ i3.config.window.hideEdgeBorders
+ i3.config.focus.mouseWarping
+ '';
+ }
+
+ {
+ time = "2017-11-26T21:57:23+00:00";
+ message = ''
+ Two new modules are available:
+
+ 'services.kbfs' and 'services.keybase'
+ '';
+ }
+
+ {
+ time = "2017-12-07T22:23:11+00:00";
+ message = ''
+ A new module is available: 'services.parcellite'
+ '';
+ }
+
+ {
+ time = "2017-12-11T17:23:12+00:00";
+ condition = config.home.activation ? reloadSystemd;
+ message = ''
+ The Boolean option 'systemd.user.startServices' is now
+ available. When enabled the current naive systemd unit
+ reload logic is replaced by a more sophisticated one that
+ attempts to automatically start, stop, and restart units as
+ necessary.
+ '';
+ }
+
+ {
+ time = "2018-02-02T11:15:00+00:00";
+ message = ''
+ A new program configuration is available: 'programs.mercurial'
+ '';
+ }
+
+ {
+ time = "2018-02-03T10:00:00+00:00";
+ message = ''
+ A new module is available: 'services.stalonetray'
+ '';
+ }
+
+ {
+ time = "2018-02-04T22:58:49+00:00";
+ condition = config.xsession.enable;
+ message = ''
+ A new option 'xsession.pointerCursor' is now available. It
+ allows specifying the pointer cursor theme and size. The
+ settings will be applied in the xsession, Xresources, and
+ GTK configurations.
+ '';
+ }
+
+ {
+ time = "2018-02-06T20:23:34+00:00";
+ message = ''
+ It is now possible to use Home Manager as a NixOS module.
+ This allows you to prepare user environments from the system
+ configuration file, which often is more convenient than
+ using the 'home-manager' tool. It also opens up additional
+ possibilities, for example, to automatically configure user
+ environments in NixOS declarative containers or on systems
+ deployed through NixOps.
+
+ This feature should be considered experimental for now and
+ some critial limitations apply. For example, it is currently
+ not possible to use 'nixos-rebuild build-vm' when using the
+ Home Manager NixOS module. That said, it should be
+ reasonably robust and stable for simpler use cases.
+
+ To make Home Manager available in your NixOS system
+ configuration you can add
+
+ imports = [
+ "''${builtins.fetchTarball https://github.com/nix-community/home-manager/archive/master.tar.gz}/nixos"
+ ];
+
+ to your 'configuration.nix' file. This will introduce a new
+ NixOS option called 'home-manager.users' whose type is an
+ attribute set mapping user names to Home Manager
+ configurations.
+
+ For example, a NixOS configuration may include the lines
+
+ users.users.eve.isNormalUser = true;
+ home-manager.users.eve = {
+ home.packages = [ pkgs.atool pkgs.httpie ];
+ programs.bash.enable = true;
+ };
+
+ and after a 'nixos-rebuild switch' the user eve's
+ environment should include a basic Bash configuration and
+ the packages atool and httpie.
+
+ More detailed documentation on the intricacies of this new
+ feature is slowly forthcoming.
+ '';
+ }
+
+ {
+ time = "2018-02-09T21:14:42+00:00";
+ condition = with config.programs.rofi; enable && colors != null;
+ message = ''
+ The new and preferred way to configure the rofi theme is
+ using rasi themes through the 'programs.rofi.theme' option.
+ This option can take as value either the name of a
+ pre-installed theme or the path to a theme file.
+
+ A rasi theme can be generated from an Xresources config
+ using 'rofi -dump-theme'.
+
+ The option 'programs.rofi.colors' is still supported but may
+ become deprecated and removed in the future.
+ '';
+ }
+
+ {
+ time = "2018-02-19T21:45:26+00:00";
+ message = ''
+ A new module is available: 'programs.pidgin'
+ '';
+ }
+
+ {
+ time = "2018-03-04T06:54:26+00:00";
+ message = ''
+ A new module is available: 'services.unclutter'
+ '';
+ }
+
+ {
+ time = "2018-03-07T21:38:27+00:00";
+ message = ''
+ A new module is available: 'programs.fzf'.
+ '';
+ }
+
+ {
+ time = "2018-03-25T06:49:57+00:00";
+ condition = with config.programs.ssh; enable && matchBlocks != {};
+ message = ''
+ Options set through the 'programs.ssh' module are now placed
+ at the end of the SSH configuration file. This was done to
+ make it possible to override global options such as
+ 'ForwardAgent' or 'Compression' inside a host match block.
+
+ If you truly need to override an SSH option across all match
+ blocks then the new option
+
+ programs.ssh.extraOptionOverrides
+
+ can be used.
+ '';
+ }
+
+ {
+ time = "2018-04-19T07:42:01+00:00";
+ message = ''
+ A new module is available: 'programs.autorandr'.
+ '';
+ }
+
+ {
+ time = "2018-04-19T15:44:55+00:00";
+ condition = config.programs.git.enable;
+ message = ''
+ A new option 'programs.git.includes' is available. Additional
+ Git configuration files may be included via
+
+ programs.git.includes = [
+ { path = "~/path/to/config.inc"; }
+ ];
+
+ or conditionally via
+
+ programs.git.includes = [
+ { path = "~/path/to/config.inc"; condition = "gitdir:~/src/"; }
+ ];
+
+ and the corresponding '[include]' or '[includeIf]' sections will be
+ appended to the main Git configuration file.
+ '';
+ }
+
+ {
+ time = "2018-05-01T20:49:31+00:00";
+ message = ''
+ A new module is available: 'services.mbsync'.
+ '';
+ }
+ {
+ time = "2018-05-03T12:34:47+00:00";
+ message = ''
+ A new module is available: 'services.flameshot'.
+ '';
+ }
+
+ {
+ time = "2018-05-18T18:34:15+00:00";
+ message = ''
+ A new module is available: 'qt'
+
+ At the moment this module allows you to set up Qt to use the
+ GTK+ theme, and not much else.
+ '';
+ }
+
+ {
+ time = "2018-06-05T01:36:45+00:00";
+ message = ''
+ A new module is available: 'services.kdeconnect'.
+ '';
+ }
+
+ {
+ time = "2018-06-09T09:11:59+00:00";
+ message = ''
+ A new module is available: `programs.newsboat`.
+ '';
+ }
+
+ {
+ time = "2018-07-01T14:33:15+00:00";
+ message = ''
+ A new module is available: 'accounts.email'.
+
+ As the name suggests, this new module offers a number of
+ options for configuring email accounts. This, for example,
+ includes the email address and owner's real name but also
+ server settings for IMAP and SMTP.
+
+ The intent is to have a central location for account
+ specific configuration that other modules can use.
+
+ Note, this module is still somewhat experimental and its
+ structure should not be seen as final. Feedback is greatly
+ appreciated, both positive and negative.
+ '';
+ }
+
+ {
+ time = "2018-07-01T16:07:04+00:00";
+ message = ''
+ A new module is available: 'programs.mbsync'.
+ '';
+ }
+
+ {
+ time = "2018-07-01T16:12:20+00:00";
+ message = ''
+ A new module is available: 'programs.notmuch'.
+ '';
+ }
+
+ {
+ time = "2018-07-07T15:48:56+00:00";
+ message = ''
+ A new module is available: 'xsession.windowManager.awesome'.
+ '';
+ }
+
+ {
+ time = "2018-07-18T20:14:11+00:00";
+ message = ''
+ A new module is available: 'services.mpd'.
+ '';
+ }
+
+ {
+ time = "2018-07-31T13:33:39+00:00";
+ message = ''
+ A new module is available: 'services.status-notifier-watcher'.
+ '';
+ }
+
+ {
+ time = "2018-07-31T13:47:06+00:00";
+ message = ''
+ A new module is available: 'programs.direnv'.
+ '';
+ }
+
+ {
+ time = "2018-08-17T20:30:14+00:00";
+ message = ''
+ A new module is available: 'programs.fish'.
+ '';
+ }
+
+ {
+ time = "2018-08-18T19:03:42+00:00";
+ condition = config.services.gpg-agent.enable;
+ message = ''
+ A new option is available: 'services.gpg-agent.extraConfig'.
+
+ Extra lines may be appended to $HOME/.gnupg/gpg-agent.conf
+ using this option.
+ '';
+ }
+
+ {
+ time = "2018-08-19T20:46:09+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new modules is available: 'programs.chromium'.
+ '';
+ }
+
+ {
+ time = "2018-08-20T20:27:26+00:00";
+ message = ''
+ A new module is available: 'programs.msmtp'.
+ '';
+ }
+
+ {
+ time = "2018-08-21T20:13:50+00:00";
+ message = ''
+ A new module is available: 'services.pasystray'.
+ '';
+ }
+
+ {
+ time = "2018-08-29T20:27:04+00:00";
+ message = ''
+ A new module is available: 'programs.offlineimap'.
+ '';
+ }
+
+ {
+ time = "2018-09-18T21:25:14+00:00";
+ message = ''
+ A new module is available: 'programs.taskwarrior'.
+ '';
+ }
+
+ {
+ time = "2018-09-18T21:43:54+00:00";
+ message = ''
+ A new module is available: 'programs.zathura'.
+ '';
+ }
+
+ {
+ time = "2018-09-20T19:26:40+00:00";
+ message = ''
+ A new module is available: 'programs.noti'.
+ '';
+ }
+
+ {
+ time = "2018-09-20T22:10:45+00:00";
+ message = ''
+ A new module is available: 'programs.go'.
+ '';
+ }
+
+ {
+ time = "2018-09-27T17:48:08+00:00";
+ message = ''
+ A new module is available: 'programs.obs-studio'.
+ '';
+ }
+
+ {
+ time = "2018-09-28T21:38:48+00:00";
+ message = ''
+ A new module is available: 'programs.alot'.
+ '';
+ }
+
+ {
+ time = "2018-10-20T09:30:57+00:00";
+ message = ''
+ A new module is available: 'programs.urxvt'.
+ '';
+ }
+
+ {
+ time = "2018-11-13T23:08:03+00:00";
+ message = ''
+ A new module is available: 'programs.tmux'.
+ '';
+ }
+
+ {
+ time = "2018-11-18T18:55:15+00:00";
+ message = ''
+ A new module is available: 'programs.astroid'.
+ '';
+ }
+
+ {
+ time = "2018-11-18T21:41:51+00:00";
+ message = ''
+ A new module is available: 'programs.afew'.
+ '';
+ }
+
+ {
+ time = "2018-11-19T00:40:34+00:00";
+ message = ''
+ A new nix-darwin module is available. Use it the same way the NixOS
+ module is used. A major limitation is that Home Manager services don't
+ work, as they depend explicitly on Linux and systemd user services.
+ However, 'home.file' and 'home.packages' do work. Everything else is
+ untested at this time.
+ '';
+ }
+
+ {
+ time = "2018-11-24T16:22:19+00:00";
+ message = ''
+ A new option 'home.stateVersion' is available. Its function
+ is much like the 'system.stateVersion' option in NixOS.
+
+ Briefly, the state version indicates a stable set of option
+ defaults. In the future, whenever Home Manager changes an
+ option default in a way that may cause program breakage it
+ will do so only for the unstable state version, currently
+ 19.03. Once 19.03 becomes the stable version only backwards
+ compatible changes will be made and 19.09 becomes the
+ unstable state version.
+
+ The default value for this option is 18.09 but it may still
+ be a good idea to explicitly add
+
+ home.stateVersion = "18.09";
+
+ to your Home Manager configuration.
+ '';
+ }
+
+ {
+ time = "2018-11-25T22:10:15+00:00";
+ message = ''
+ A new module is available: 'services.nextcloud-client'.
+ '';
+ }
+
+ {
+ time = "2018-11-25T22:55:12+00:00";
+ message = ''
+ A new module is available: 'programs.vscode'.
+ '';
+ }
+
+ {
+ time = "2018-12-04T21:54:38+00:00";
+ condition = config.programs.beets.settings != {};
+ message = ''
+ A new option 'programs.beets.enable' has been added.
+ Starting with state version 19.03 this option defaults to
+ false. For earlier versions it defaults to true if
+ 'programs.beets.settings' is non-empty.
+
+ It is recommended to explicitly add
+
+ programs.beets.enable = true;
+
+ to your configuration.
+ '';
+ }
+
+ {
+ time = "2018-12-12T21:02:05+00:00";
+ message = ''
+ A new module is available: 'programs.jq'.
+ '';
+ }
+
+ {
+ time = "2018-12-24T16:26:16+00:00";
+ message = ''
+ A new module is available: 'dconf'.
+
+ Note, on NixOS you may need to add
+
+ services.dbus.packages = with pkgs; [ gnome3.dconf ];
+
+ to the system configuration for this module to work as
+ expected. In particular if you get the error message
+
+ The name ca.desrt.dconf was not provided by any .service files
+
+ when activating your Home Manager configuration.
+ '';
+ }
+
+ {
+ time = "2018-12-28T12:32:30+00:00";
+ message = ''
+ A new module is available: 'programs.opam'.
+ '';
+ }
+
+ {
+ time = "2019-01-18T00:21:56+00:00";
+ message = ''
+ A new module is available: 'programs.matplotlib'.
+ '';
+ }
+
+ {
+ time = "2019-01-26T13:20:37+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.xembed-sni-proxy'.
+ '';
+ }
+
+ {
+ time = "2019-01-28T23:36:10+00:00";
+ message = ''
+ A new module is available: 'programs.irssi'.
+ '';
+ }
+
+ {
+ time = "2019-02-09T14:09:58+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.emacs'.
+
+ This module provides a user service that runs the Emacs
+ configured in
+
+ programs.emacs
+
+ as an Emacs daemon.
+ '';
+ }
+
+ {
+ time = "2019-02-16T20:33:56+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ When using Home Manager as a NixOS submodule it is now
+ possible to install packages using the NixOS
+
+ users.users.<name?>.packages
+
+ option. This is enabled by adding
+
+ home-manager.useUserPackages = true;
+
+ to your NixOS system configuration. This mode of operation
+ is necessary if you want to use 'nixos-rebuild build-vm'.
+ '';
+ }
+
+ {
+ time = "2019-02-17T21:11:24+00:00";
+ message = ''
+ A new module is available: 'programs.keychain'.
+ '';
+ }
+
+ {
+ time = "2019-02-24T00:32:23+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new service is available: 'services.mpdris2'.
+ '';
+ }
+
+ {
+ time = "2019-03-19T22:56:20+00:00";
+ message = ''
+ A new module is available: 'programs.bat'.
+ '';
+ }
+
+ {
+ time = "2019-03-19T23:07:34+00:00";
+ message = ''
+ A new module is available: 'programs.lsd'.
+ '';
+ }
+
+ {
+ time = "2019-04-09T20:10:22+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.xcape'.
+ '';
+ }
+
+ {
+ time = "2019-04-11T22:50:10+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ The type used for the systemd unit options under
+
+ systemd.user.services, systemd.user.sockets, etc.
+
+ has been changed to offer more robust merging of configurations.
+
+ If you don't override values within systemd units then you are not
+ affected by this change. Unfortunately, if you do override unit values
+ you may encounter errors due to this change.
+
+ In particular, if you get an error saying that a "unique option" is
+ "defined multiple times" then you need to use 'lib.mkForce'. For
+ example,
+
+ systemd.user.services.foo.Service.ExecStart = "/foo/bar";
+
+ becomes
+
+ systemd.user.services.foo.Service.ExecStart = lib.mkForce "/foo/bar";
+
+ We had to make this change because the old merging was causing too
+ many confusing situations for people. Apologies for potentially
+ breaking your configuration!
+ '';
+ }
+
+ {
+ time = "2019-04-14T15:35:16+00:00";
+ message = ''
+ A new module is available: 'programs.skim'.
+ '';
+ }
+
+ {
+ time = "2019-04-22T12:43:20+00:00";
+ message = ''
+ A new module is available: 'programs.alacritty'.
+ '';
+ }
+
+ {
+ time = "2019-04-26T22:53:48+00:00";
+ condition = config.programs.vscode.enable;
+ message = ''
+ A new module is available: 'programs.vscode.haskell'.
+
+ Enable to add Haskell IDE Engine and syntax highlighting
+ support to your VSCode.
+ '';
+ }
+
+ {
+ time = "2019-05-04T23:56:39+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.rsibreak'.
+ '';
+ }
+
+ {
+ time = "2019-05-07T20:49:29+00:00";
+ message = ''
+ A new module is available: 'programs.mpv'.
+ '';
+ }
+
+ {
+ time = "2019-05-30T17:49:29+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.xsuspender'.
+ '';
+ }
+
+ {
+ time = "2019-06-03T21:47:10+00:00";
+ message = ''
+ A new module is available: 'programs.gpg'.
+ '';
+ }
+
+ {
+ time = "2019-06-09T12:19:18+00:00";
+ message = ''
+ Collisions between unmanaged and managed files can now be
+ automatically resolved by moving the target file to a new
+ path instead of failing the switch operation. To enable
+ this, use the new '-b' command line argument. For example,
+
+ home-manager -b bck switch
+
+ where 'bck' is the suffix to give the moved file. In this
+ case a colliding file 'foo.conf' will be moved to
+ 'foo.conf.bck'.
+ '';
+ }
+
+ {
+ time = "2019-06-19T17:49:29+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: `services.getmail`.
+ '';
+ }
+
+ {
+ time = "2019-07-02T09:27:56+00:00";
+ message = ''
+ A new module is available: 'programs.broot'.
+ '';
+ }
+
+ {
+ time = "2019-07-17T19:30:29+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.taskwarrior-sync'.
+ '';
+ }
+
+ {
+ time = "2019-07-17T20:05:29+00:00";
+ message = ''
+ A new module is available: 'programs.kakoune'.
+ '';
+ }
+
+ {
+ time = "2019-08-08T11:49:35+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.hound'.
+ '';
+ }
+
+ {
+ time = "2019-08-17T12:24:58+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.muchsync'.
+ '';
+ }
+
+ {
+ time = "2019-08-18T14:22:41+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.dwm-status'.
+ '';
+ }
+
+ {
+ time = "2019-08-28T10:18:07+00:00";
+ condition = config.programs.vim.enable;
+ message = ''
+ The 'programs.vim.plugins' option now accepts packages.
+ Specifying them as strings is deprecated.
+ '';
+ }
+
+ {
+ time = "2019-09-17T19:33:49+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.sxhkd'.
+ '';
+ }
+
+ {
+ time = "2019-09-26T21:05:24+00:00";
+ message = ''
+ A new module is available: 'programs.starship'.
+ '';
+ }
+
+ {
+ time = "2019-09-26T21:47:13+00:00";
+ message = ''
+ A new module is available: 'programs.rtorrent'.
+ '';
+ }
+
+ {
+ time = "2019-11-04T20:56:29+00:00";
+ message = ''
+ A new module is available: 'programs.pazi'.
+ '';
+ }
+
+ {
+ time = "2019-11-05T21:54:04+00:00";
+ condition = config.programs.zsh.enable;
+ message = ''
+ The 'programs.zsh.history.path' option behavior and the
+ default value has changed for state version 20.03 and above.
+
+ Specifically, '$HOME' will no longer be prepended to the
+ option value, which allows specifying absolute paths (e.g.
+ using the xdg module). Also, the default value is fixed to
+ '$HOME/.zsh_history' and 'dotDir' path is not prepended to
+ it anymore.
+ '';
+ }
+
+ {
+ time = "2019-11-17T18:47:40+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.lorri'.
+ '';
+ }
+
+ {
+ time = "2019-11-24T17:46:57+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.spotifyd'.
+ '';
+ }
+
+ {
+ time = "2019-11-29T21:18:48+00:00";
+ message = ''
+ A new module is available: 'programs.password-store'.
+ '';
+ }
+
+ {
+ time = "2019-11-29T21:18:48+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.password-store-sync'.
+ '';
+ }
+
+ {
+ time = "2019-11-29T22:46:49+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.unison'.
+ '';
+ }
+
+ {
+ time = "2019-12-01T22:10:23+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'xdg.mime'.
+
+ If enabled, which it is by default, this module will create
+ the XDG mime database and desktop file database caches from
+ programs installed via Home Manager.
+ '';
+ }
+
+ {
+ time = "2019-12-08T19:48:26+00:00";
+ message = ''
+ A new module is available: 'programs.readline'.
+ '';
+ }
+
+ {
+ time = "2020-01-11T11:49:51+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.cbatticon'.
+ '';
+ }
+
+ {
+ time = "2020-01-26T12:42:33+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'xsession.windowManager.bspwm'.
+ '';
+ }
+
+ {
+ time = "2020-01-26T12:49:40+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.grobi'.
+ '';
+ }
+
+ {
+ time = "2020-01-26T19:37:57+00:00";
+ message = ''
+ A new module is available: 'programs.neomutt'.
+ '';
+ }
+
+ {
+ time = "2020-02-23T10:19:48+00:00";
+ message = ''
+ A new module is available: 'programs.kitty'.
+ '';
+ }
+
+ {
+ time = "2020-02-26T21:20:55+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'wayland.windowManager.sway'
+ '';
+ }
+
+ {
+ time = "2020-03-04T18:55:03+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'programs.abook'
+ '';
+ }
+
+ {
+ time = "2020-03-07T11:43:26+00:00";
+ condition = config.programs.fish.enable;
+ message = ''
+ The option 'programs.fish.functions' has been reworked in
+ order to support all available flags, such as
+ '--description', '--on-event', and more.
+ '';
+ }
+
+ {
+ time = "2020-03-07T13:11:43+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ The NixOS module has a new option: 'home-manager.useGlobalPkgs'.
+
+ This enables using the system configuration's 'pkgs'
+ argument in Home Manager.
+
+ To learn more, see the installation section of the manual
+
+ https://nix-community.github.io/home-manager/#sec-install-nixos-module
+ '';
+ }
+
+ {
+ time = "2020-03-07T14:12:50+00:00";
+ message = ''
+ A new module is available: 'programs.lieer'.
+ '';
+ }
+
+ {
+ time = "2020-03-07T14:12:50+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.lieer'.
+ '';
+ }
+
+ {
+ time = "2020-03-15T16:55:28+00:00";
+ condition = config.programs.firefox.enable;
+ message = ''
+ In anticipation of Firefox dropping support for extension
+ sideloading[1], we now install extensions directly to
+ Firefox profiles managed through Home Manager's
+
+ 'programs.firefox.profiles'
+
+ option.
+
+ Unfortunately this will most likely trigger an "Existing
+ file is in the way" error when activating your configuration
+ since Firefox keeps a copy of the add-on in the location
+ Home Manager wants to overwrite. If this is the case, remove
+ the listed '.xpi' files and try again.
+
+ This change also means that extensions installed through
+ Home Manager may disappear from unmanaged profiles in future
+ Firefox releases.
+
+ [1] https://blog.mozilla.org/addons/2019/10/31/firefox-to-discontinue-sideloaded-extensions/
+ '';
+ }
+
+ {
+ time = "2020-03-17T21:56:26+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.keynav'.
+ '';
+ }
+
+ {
+ time = "2020-03-24T22:17:20+00:00";
+ condition = config.services.compton.enable;
+ message = ''
+ The 'services.compton' module has been deprecated and
+ instead the new module 'services.picom' should be used. This
+ is because Nixpkgs no longer packages compton, and instead
+ packages the (mostly) compatible fork called picom.
+
+ The 'services.compton' and 'services.picom' modules have a
+ few differences:
+
+ - 'services.picom' has a new 'experimentalBackends'
+ option.
+
+ - 'vSync' is now a boolean value on 'services.picom', as
+ opposed to the string in 'services.compton'.
+
+ Migrating to the new picom service is simple - just change
+ all references to 'services.compton' to 'services.picom',
+ and adhere to the above changes.
+
+ The deprecated 'services.compton' will eventually be removed
+ in the future. Please update your configurations to use
+ 'services.picom' as soon as possible.
+ '';
+ }
+
+ {
+ time = "2020-04-08T09:33:05+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'targets.genericLinux'.
+
+ When enabled, this module will configure various settings
+ and environment variables to make Home Manager and programs
+ installed through Nix work better on GNU/Linux distributions
+ other than NixOS.
+
+ It should not be enabled if your Home Manager configuration
+ is deployed on a NixOS host.
+ '';
+ }
+
+ {
+ time = "2020-04-08T11:51:15+00:00";
+ message = ''
+ A new module is available: 'programs.qutebrowser'
+ '';
+ }
+
+ {
+ time = "2020-04-09T09:19:38+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.mako'
+ '';
+ }
+
+ {
+ time = "2020-04-23T19:45:26+00:00";
+ message = ''
+ A new module is available: 'programs.lf'
+ '';
+ }
+
+ {
+ time = "2020-04-26T13:46:28+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.pulseeffects'
+ '';
+ }
+
+ {
+ time = "2020-05-03T11:13:07+00:00";
+ message = ''
+ A new module is available: 'programs.i3status'
+ '';
+ }
+
+ {
+ time = "2020-05-03T11:21:42+00:00";
+ message = ''
+ A new module is available: 'programs.aria2'
+ '';
+ }
+
+ {
+ time = "2020-05-04T21:19:43+00:00";
+ condition = config.programs.git.enable;
+ message = ''
+ The Git module now supports the 'delta' syntax highlighter.
+
+ It can be enabled through the option 'programs.git.delta.enable'.
+ '';
+ }
+
+ {
+ time = "2020-05-12T20:09:54+00:00";
+ message = ''
+ A new module is available: 'programs.dircolors'
+ '';
+ }
+
+ {
+ time = "2020-05-26T17:13:58+00:00";
+ message = ''
+ A new module is available: 'programs.zoxide'
+ '';
+ }
+
+ {
+ time = "2020-06-03T17:46:11+00:00";
+ condition = config.programs.ssh.enable;
+ message = ''
+ The ssh module now supports the 'ServerAliveCountMax' option
+ both globally through
+
+ programs.ssh.serverAliveCountMax
+
+ and per match blocks
+
+ programs.ssh.matchBlocks.<name>.serverAliveCountMax
+ '';
+ }
+
+ {
+ time = "2020-06-11T18:06:37+00:00";
+ condition = hostPlatform.isLinux && config.services.emacs.enable;
+ message = ''
+ The Emacs service now supports systemd socket activation.
+
+ It can be enabled through the option 'services.emacs.socketActivation.enable'.
+ '';
+ }
+
+ {
+ time = "2020-06-12T17:48:01+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.clipmenu'
+ '';
+ }
+
+ {
+ time = "2020-06-12T07:08:09+00:00";
+ condition = config.programs.bash.enable;
+ message = ''
+ A new module is available: 'programs.powerline-go'
+ '';
+ }
+
+ {
+ time = "2020-06-14T13:30:19+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'service.fluidsynth'
+ '';
+ }
+
+ {
+ time = "2020-06-17T22:17:52+00:00";
+ condition = config.programs.git.enable;
+ message = ''
+ Since May 1, 2020 string values in Git configurations are
+ automatically escaped. If you have any manually escaped characters,
+ then you may need to restore them to their unescaped form to avoid
+ double escaping.
+
+ In other words, if you now have something along the lines of
+
+ programs.git.aliases.hello = '''"!echo $'Hello\\nWorld'"''';
+
+ you must replace it by the unescaped form
+
+ programs.git.aliases.hello = "!echo $'Hello\nWorld'";
+
+ Apologies for the belated notification!
+ '';
+ }
+
+ {
+ time = "2020-06-23T20:06:39+00:00";
+ message = ''
+ A new module is available: 'programs.ne'
+ '';
+ }
+
+ {
+ time = "2020-07-24T15:03:11+00:00";
+ message = ''
+ A new module is available: 'programs.nushell'.
+ '';
+ }
+
+ {
+ time = "2020-07-25T21:04:59+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.dropbox'.
+ '';
+ }
+
+ {
+ time = "2020-08-13T22:15:27+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'programs.waybar'
+ '';
+ }
+
+ {
+ time = "2020-08-14T22:44:20+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.kanshi'
+ '';
+ }
+
+ {
+ time = "2020-08-25T22:14:01+00:00";
+ message = ''
+ A new module is available: 'programs.mcfly'
+ '';
+ }
+
+ {
+ time = "2020-09-01T18:38:18+00:00";
+ message = ''
+ A new module is available: 'programs.ncmpcpp'
+ '';
+ }
+
+ {
+ time = "2020-09-11T10:06:47+00:00";
+ condition = hostPlatform.isLinux && config.targets.genericLinux.enable;
+ message = ''
+ A new option 'targets.genericLinux.extraXdgDataDirs' is available
+ to setup the user environment with the OS's data files.
+
+ This is useful for example to get Bash completion for
+ 'systemctl' which shouldn't be installed through Home Manager.
+
+ This is also useful to have non Home Manager applications
+ available in menus.
+ '';
+ }
+
+ {
+ time = "2020-09-09T06:54:59+00:00";
+ condition = config.programs.man.enable;
+ message = ''
+ A new option 'programs.man.generateCaches' was added to
+ support the apropos command.
+ '';
+ }
+
+ {
+ time = "2020-09-22T21:03:28+00:00";
+ message = ''
+ A new module is available: 'programs.pet'.
+ '';
+ }
+
+ {
+ time = "2020-09-29T21:21:44+00:00";
+ message = ''
+ A new module is available: 'programs.mu'.
+ '';
+ }
+
+ {
+ time = "2020-10-08T21:28:16+00:00";
+ message = ''
+ A new module is available: 'programs.autojump'
+
+ The option `programs.bash.enableAutojump` is deprecated and this new
+ module should be used instead.
+ '';
+ }
+
+ {
+ time = "2020-10-12T00:12:23+00:00";
+ condition = config.programs.zsh.enable;
+ message = ''
+ A new zsh submodule is available: 'programs.zsh.prezto'.
+ '';
+ }
+
+ {
+ time = "2020-10-22T21:10:38+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.gammastep'.
+ '';
+ }
+
+ {
+ time = "2020-10-22T21:30:42+00:00";
+ message = ''
+ A new module is available: 'programs.gh'.
+ '';
+ }
+
+ {
+ time = "2020-11-01T11:17:02+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.caffeine'.
+ '';
+ }
+
+ {
+ time = "2020-11-05T22:59:21+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'programs.i3status-rust'.
+ '';
+ }
+
+ {
+ time = "2020-11-14T13:02:40+00:00";
+ condition = config.programs.broot.enable;
+ message = ''
+ The 'programs.broot.verbs' option is now a list rather than an
+ attribute set. To migrate, move the keys of the attrset into the
+ list items' 'invocation' keys. For example,
+
+ programs.broot.verbs = {
+ "p" = { execution = ":parent"; };
+ };
+
+ becomes
+
+ programs.broot.verbs = [
+ {
+ invocation = "p";
+ execution = ":parent";
+ }
+ ];
+ '';
+ }
+
+ {
+ time = "2020-12-01T20:46:14+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.wlsunset'.
+ '';
+ }
+
+ {
+ time = "2020-12-09T22:34:33+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.pbgopy'.
+ '';
+ }
+
+ {
+ time = "2020-12-18T22:22:25+00:00";
+ message = ''
+ A new module is available: 'programs.rofi.pass'.
+ '';
+ }
+ ];
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/misc/nixpkgs.nix b/infra/libkookie/home-manager/modules/misc/nixpkgs.nix
new file mode 100644
index 000000000000..511dbec10b21
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/misc/nixpkgs.nix
@@ -0,0 +1,152 @@
+# Adapted from Nixpkgs.
+
+{ config, lib, pkgs, pkgsPath, ... }:
+
+with lib;
+
+let
+
+ isConfig = x:
+ builtins.isAttrs x || builtins.isFunction x;
+
+ optCall = f: x:
+ if builtins.isFunction f
+ then f x
+ else f;
+
+ mergeConfig = lhs_: rhs_:
+ let
+ lhs = optCall lhs_ { inherit pkgs; };
+ rhs = optCall rhs_ { inherit pkgs; };
+ in
+ lhs // rhs //
+ optionalAttrs (lhs ? packageOverrides) {
+ packageOverrides = pkgs:
+ optCall lhs.packageOverrides pkgs //
+ optCall (attrByPath ["packageOverrides"] ({}) rhs) pkgs;
+ } //
+ optionalAttrs (lhs ? perlPackageOverrides) {
+ perlPackageOverrides = pkgs:
+ optCall lhs.perlPackageOverrides pkgs //
+ optCall (attrByPath ["perlPackageOverrides"] ({}) rhs) pkgs;
+ };
+
+ configType = mkOptionType {
+ name = "nixpkgs-config";
+ description = "nixpkgs config";
+ check = x:
+ let traceXIfNot = c:
+ if c x then true
+ else lib.traceSeqN 1 x false;
+ in traceXIfNot isConfig;
+ merge = args: fold (def: mergeConfig def.value) {};
+ };
+
+ overlayType = mkOptionType {
+ name = "nixpkgs-overlay";
+ description = "nixpkgs overlay";
+ check = builtins.isFunction;
+ merge = lib.mergeOneOption;
+ };
+
+ _pkgs = import pkgsPath (
+ filterAttrs (n: v: v != null) config.nixpkgs
+ );
+
+in
+
+{
+ options.nixpkgs = {
+ config = mkOption {
+ default = null;
+ example = { allowBroken = true; };
+ type = types.nullOr configType;
+ description = ''
+ The configuration of the Nix Packages collection. (For
+ details, see the Nixpkgs documentation.) It allows you to set
+ package configuration options.
+
+ </para><para>
+
+ If <literal>null</literal>, then configuration is taken from
+ the fallback location, for example,
+ <filename>~/.config/nixpkgs/config.nix</filename>.
+
+ </para><para>
+
+ Note, this option will not apply outside your Home Manager
+ configuration like when installing manually through
+ <command>nix-env</command>. If you want to apply it both
+ inside and outside Home Manager you can put it in a separate
+ file and include something like
+
+ <programlisting language="nix">
+ nixpkgs.config = import ./nixpkgs-config.nix;
+ xdg.configFile."nixpkgs/config.nix".source = ./nixpkgs-config.nix;
+ </programlisting>
+
+ in your Home Manager configuration.
+ '';
+ };
+
+ overlays = mkOption {
+ default = null;
+ example = literalExample
+ ''
+ [ (self: super: {
+ openssh = super.openssh.override {
+ hpnSupport = true;
+ withKerberos = true;
+ kerberos = self.libkrb5;
+ };
+ };
+ ) ]
+ '';
+ type = types.nullOr (types.listOf overlayType);
+ description = ''
+ List of overlays to use with the Nix Packages collection. (For
+ details, see the Nixpkgs documentation.) It allows you to
+ override packages globally. This is a function that takes as
+ an argument the <emphasis>original</emphasis> Nixpkgs. The
+ first argument should be used for finding dependencies, and
+ the second should be used for overriding recipes.
+
+ </para><para>
+
+ If <literal>null</literal>, then the overlays are taken from
+ the fallback location, for example,
+ <filename>~/.config/nixpkgs/overlays</filename>.
+
+ </para><para>
+
+ Like <varname>nixpkgs.config</varname> this option only
+ applies within the Home Manager configuration. See
+ <varname>nixpkgs.config</varname> for a suggested setup that
+ works both internally and externally.
+ '';
+ };
+
+ system = mkOption {
+ type = types.str;
+ example = "i686-linux";
+ internal = true;
+ description = ''
+ Specifies the Nix platform type for which the user environment
+ should be built. If unset, it defaults to the platform type of
+ your host system. Specifying this option is useful when doing
+ distributed multi-platform deployment, or when building
+ virtual machines.
+ '';
+ };
+ };
+
+ config = {
+ _module.args = {
+ pkgs = mkOverride modules.defaultPriority _pkgs;
+ pkgs_i686 =
+ if _pkgs.stdenv.isLinux && _pkgs.stdenv.hostPlatform.isx86
+ then _pkgs.pkgsi686Linux
+ else { };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/misc/numlock.nix b/infra/libkookie/home-manager/modules/misc/numlock.nix
new file mode 100644
index 000000000000..c823f6dbdd21
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/misc/numlock.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.xsession.numlock;
+
+in {
+ meta.maintainers = [ maintainers.evanjs ];
+
+ options = { xsession.numlock.enable = mkEnableOption "Num Lock"; };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.numlockx = {
+ Unit = {
+ Description = "NumLockX";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ ExecStart = "${pkgs.numlockx}/bin/numlockx";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/misc/pam.nix b/infra/libkookie/home-manager/modules/misc/pam.nix
new file mode 100644
index 000000000000..f54f4b95089c
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/misc/pam.nix
@@ -0,0 +1,32 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ vars = config.pam.sessionVariables;
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ pam.sessionVariables = mkOption {
+ default = { };
+ type = types.attrs;
+ example = { EDITOR = "vim"; };
+ description = ''
+ Environment variables that will be set for the PAM session.
+ The variable values must be as described in
+ <citerefentry>
+ <refentrytitle>pam_env.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry>.
+ '';
+ };
+ };
+
+ config = mkIf (vars != { }) {
+ home.file.".pam_environment".text = concatStringsSep "\n"
+ (mapAttrsToList (n: v: ''${n} OVERRIDE="${toString v}"'') vars) + "\n";
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/misc/qt.nix b/infra/libkookie/home-manager/modules/misc/qt.nix
new file mode 100644
index 000000000000..ff38f842c810
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/misc/qt.nix
@@ -0,0 +1,68 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.qt;
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ imports = [
+ (mkChangedOptionModule [ "qt" "useGtkTheme" ] [ "qt" "platformTheme" ]
+ (config:
+ if getAttrFromPath [ "qt" "useGtkTheme" ] config then "gtk" else null))
+ ];
+
+ options = {
+ qt = {
+ enable = mkEnableOption "Qt 4 and 5 configuration";
+
+ platformTheme = mkOption {
+ type = types.nullOr (types.enum [ "gtk" "gnome" ]);
+ default = null;
+ example = "gnome";
+ relatedPackages =
+ [ "qgnomeplatform" [ "libsForQt5" "qtstyleplugins" ] ];
+ description = ''
+ Selects the platform theme to use for Qt applications.</para>
+ <para>The options are
+ <variablelist>
+ <varlistentry>
+ <term><literal>gtk</literal></term>
+ <listitem><para>Use GTK theme with
+ <link xlink:href="https://github.com/qt/qtstyleplugins">qtstyleplugins</link>
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>gnome</literal></term>
+ <listitem><para>Use GNOME theme with
+ <link xlink:href="https://github.com/FedoraQt/QGnomePlatform">qgnomeplatform</link>
+ </para></listitem>
+ </varlistentry>
+ </variablelist>
+ '';
+ };
+ };
+ };
+
+ config = mkIf (cfg.enable && cfg.platformTheme != null) {
+ home.sessionVariables.QT_QPA_PLATFORMTHEME =
+ if cfg.platformTheme == "gnome" then "gnome" else "gtk2";
+
+ home.packages = if cfg.platformTheme == "gnome" then
+ [ pkgs.qgnomeplatform ]
+ else
+ [ pkgs.libsForQt5.qtstyleplugins ];
+
+ xsession.importedVariables = [ "QT_QPA_PLATFORMTHEME" ];
+
+ # Enable GTK+ style for Qt4 in either case.
+ # It doesn’t support the platform theme packages.
+ home.activation.useGtkThemeInQt4 = hm.dag.entryAfter [ "writeBoundary" ] ''
+ $DRY_RUN_CMD ${pkgs.crudini}/bin/crudini $VERBOSE_ARG \
+ --set "${config.xdg.configHome}/Trolltech.conf" Qt style GTK+
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/misc/submodule-support.nix b/infra/libkookie/home-manager/modules/misc/submodule-support.nix
new file mode 100644
index 000000000000..ff80291cadf4
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/misc/submodule-support.nix
@@ -0,0 +1,32 @@
+{ lib, ... }:
+
+with lib;
+
+{
+ meta.maintainers = [ maintainers.rycee ];
+
+ options.submoduleSupport = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ internal = true;
+ description = ''
+ Whether the Home Manager module system is used as a submodule
+ in, for example, NixOS or nix-darwin.
+ '';
+ };
+
+ externalPackageInstall = mkOption {
+ type = types.bool;
+ default = false;
+ internal = true;
+ description = ''
+ Whether the packages of <option>home.packages</option> are
+ installed separately from the Home Manager activation script.
+ In NixOS, for example, this may be accomplished by installing
+ the packages through
+ <option>users.users.‹name?›.packages</option>.
+ '';
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/misc/tmpfiles.nix b/infra/libkookie/home-manager/modules/misc/tmpfiles.nix
new file mode 100644
index 000000000000..c46fe2c553aa
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/misc/tmpfiles.nix
@@ -0,0 +1,49 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.systemd.user.tmpfiles;
+
+in {
+ meta.maintainers = [ maintainers.dawidsowa ];
+
+ options.systemd.user.tmpfiles.rules = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "L /home/user/Documents - - - - /mnt/data/Documents" ];
+ description = ''
+ Rules for creating and cleaning up temporary files
+ automatically. See
+ <citerefentry>
+ <refentrytitle>tmpfiles.d</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry>
+ for the exact format.
+ '';
+ };
+
+ config = mkIf (cfg.rules != [ ]) {
+ xdg = {
+ dataFile."user-tmpfiles.d/home-manager.conf" = {
+ text = ''
+ # This file is created automatically and should not be modified.
+ # Please change the option ‘systemd.user.tmpfiles.rules’ instead.
+ ${concatStringsSep "\n" cfg.rules}
+ '';
+ onChange = "${pkgs.systemd}/bin/systemd-tmpfiles --user --create";
+ };
+ configFile = {
+ "systemd/user/basic.target.wants/systemd-tmpfiles-setup.service".source =
+ "${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-setup.service";
+ "systemd/user/systemd-tmpfiles-setup.service".source =
+ "${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-setup.service";
+ "systemd/user/timers.target.wants/systemd-tmpfiles-clean.timer".source =
+ "${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-clean.timer";
+ "systemd/user/systemd-tmpfiles-clean.service".source =
+ "${pkgs.systemd}/example/systemd/user/systemd-tmpfiles-clean.service";
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/misc/version.nix b/infra/libkookie/home-manager/modules/misc/version.nix
new file mode 100644
index 000000000000..9c742f4847cd
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/misc/version.nix
@@ -0,0 +1,24 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+ options = {
+ home.stateVersion = mkOption {
+ type = types.enum [ "18.09" "19.03" "19.09" "20.03" "20.09" "21.03" ];
+ default = "18.09";
+ description = ''
+ It is occasionally necessary for Home Manager to change
+ configuration defaults in a way that is incompatible with
+ stateful data. This could, for example, include switching the
+ default data format or location of a file.
+ </para><para>
+ The <emphasis>state version</emphasis> indicates which default
+ settings are in effect and will therefore help avoid breaking
+ program configurations. Switching to a higher state version
+ typically requires performing some manual steps, such as data
+ conversion or moving files.
+ '';
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/misc/vte.nix b/infra/libkookie/home-manager/modules/misc/vte.nix
new file mode 100644
index 000000000000..fbe38c0163e7
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/misc/vte.nix
@@ -0,0 +1,51 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ meta.maintainers = [ maintainers.rycee ];
+
+ options.programs = let
+ description = ''
+ Whether to enable integration with terminals using the VTE
+ library. This will let the terminal track the current working
+ directory.
+ '';
+ in {
+ bash.enableVteIntegration = mkEnableOption "" // { inherit description; };
+
+ zsh.enableVteIntegration = mkEnableOption "" // { inherit description; };
+ };
+
+ config = mkMerge [
+ (mkIf config.programs.bash.enableVteIntegration {
+ # Unfortunately we have to do a little dance here to fix two
+ # problems with the upstream vte.sh file:
+ #
+ # - It does `PROMPT_COMMAND="__vte_prompt_command"` which
+ # clobbers any previously assigned prompt command.
+ #
+ # - Its `__vte_prompt_command` function runs commands that will
+ # overwrite the exit status of the command the user ran.
+ programs.bash.initExtra = ''
+ __HM_PROMPT_COMMAND="''${PROMPT_COMMAND:+''${PROMPT_COMMAND%;};}__hm_vte_prompt_command"
+ . ${pkgs.vte}/etc/profile.d/vte.sh
+ if [[ $(type -t __vte_prompt_command) = function ]]; then
+ __hm_vte_prompt_command() {
+ local old_exit_status=$?
+ __vte_prompt_command
+ return $old_exit_status
+ }
+ PROMPT_COMMAND="$__HM_PROMPT_COMMAND"
+ fi
+ unset __HM_PROMPT_COMMAND
+ '';
+ })
+
+ (mkIf config.programs.zsh.enableVteIntegration {
+ programs.zsh.initExtra = ''
+ . ${pkgs.vte}/etc/profile.d/vte.sh
+ '';
+ })
+ ];
+}
diff --git a/infra/libkookie/home-manager/modules/misc/xdg-mime-apps.nix b/infra/libkookie/home-manager/modules/misc/xdg-mime-apps.nix
new file mode 100644
index 000000000000..81d2ba0fcbe2
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/misc/xdg-mime-apps.nix
@@ -0,0 +1,88 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.xdg.mimeApps;
+
+ strListOrSingleton = with types;
+ coercedTo (either (listOf str) str) toList (listOf str);
+
+in {
+ meta.maintainers = with maintainers; [ pacien ];
+
+ options.xdg.mimeApps = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to manage <filename>$XDG_CONFIG_HOME/mimeapps.list</filename>.
+ </para>
+ <para>
+ The generated file is read-only.
+ '';
+ };
+
+ # descriptions from
+ # https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-1.0.1.html
+
+ associations.added = mkOption {
+ type = types.attrsOf strListOrSingleton;
+ default = { };
+ example = literalExample ''
+ {
+ "mimetype1" = [ "foo1.desktop" "foo2.desktop" "foo3.desktop" ];
+ "mimetype2" = "foo4.desktop";
+ }
+ '';
+ description = ''
+ Defines additional associations of applications with
+ mimetypes, as if the .desktop file was listing this mimetype
+ in the first place.
+ '';
+ };
+
+ associations.removed = mkOption {
+ type = types.attrsOf strListOrSingleton;
+ default = { };
+ example = { "mimetype1" = "foo5.desktop"; };
+ description = ''
+ Removes associations of applications with mimetypes, as if the
+ .desktop file was <emphasis>not</emphasis> listing this
+ mimetype in the first place.
+ '';
+ };
+
+ defaultApplications = mkOption {
+ type = types.attrsOf strListOrSingleton;
+ default = { };
+ example = literalExample ''
+ {
+ "mimetype1" = [ "default1.desktop" "default2.desktop" ];
+ }
+ '';
+ description = ''
+ The default application to be used for a given mimetype. This
+ is, for instance, the one that will be started when
+ double-clicking on a file in a file manager. If the
+ application is no longer installed, the next application in
+ the list is attempted, and so on.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ # Deprecated but still used by some applications.
+ xdg.dataFile."applications/mimeapps.list".source =
+ config.xdg.configFile."mimeapps.list".source;
+
+ xdg.configFile."mimeapps.list".text =
+ let joinValues = mapAttrs (n: concatStringsSep ";");
+ in generators.toINI { } {
+ "Added Associations" = joinValues cfg.associations.added;
+ "Removed Associations" = joinValues cfg.associations.removed;
+ "Default Applications" = joinValues cfg.defaultApplications;
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/misc/xdg-mime.nix b/infra/libkookie/home-manager/modules/misc/xdg-mime.nix
new file mode 100644
index 000000000000..5999e1299c98
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/misc/xdg-mime.nix
@@ -0,0 +1,55 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.xdg.mime;
+
+in {
+ options = {
+ xdg.mime.enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to install programs and files to support the
+ XDG Shared MIME-info specification and XDG MIME Applications
+ specification at
+ <link xlink:href="https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html"/>
+ and
+ <link xlink:href="https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html"/>,
+ respectively.
+ '';
+ };
+ };
+
+ config = mkIf config.xdg.mime.enable {
+ home.packages = [
+ # Explicitly install package to provide basic mime types.
+ pkgs.shared-mime-info
+
+ # Make sure the target directories will be real directories.
+ (pkgs.runCommandLocal "dummy-xdg-mime-dirs1" { } ''
+ mkdir -p $out/share/{applications,mime/packages}
+ '')
+ (pkgs.runCommandLocal "dummy-xdg-mime-dirs2" { } ''
+ mkdir -p $out/share/{applications,mime/packages}
+ '')
+ ];
+
+ home.extraProfileCommands = ''
+ if [[ -w $out/share/mime && -w $out/share/mime/packages && -d $out/share/mime/packages ]]; then
+ XDG_DATA_DIRS=$out/share \
+ PKGSYSTEM_ENABLE_FSYNC=0 \
+ ${pkgs.buildPackages.shared-mime-info}/bin/update-mime-database \
+ -V $out/share/mime > /dev/null
+ fi
+
+ if [[ -w $out/share/applications ]]; then
+ ${pkgs.buildPackages.desktop-file-utils}/bin/update-desktop-database \
+ $out/share/applications
+ fi
+ '';
+ };
+
+}
diff --git a/infra/libkookie/home-manager/modules/misc/xdg-user-dirs.nix b/infra/libkookie/home-manager/modules/misc/xdg-user-dirs.nix
new file mode 100644
index 000000000000..a1db6b115a1a
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/misc/xdg-user-dirs.nix
@@ -0,0 +1,110 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.xdg.userDirs;
+
+in {
+ meta.maintainers = with maintainers; [ pacien ];
+
+ imports = [
+ (mkRenamedOptionModule [ "xdg" "userDirs" "publishShare" ] [
+ "xdg"
+ "userDirs"
+ "publicShare"
+ ])
+ ];
+
+ options.xdg.userDirs = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to manage <filename>$XDG_CONFIG_HOME/user-dirs.dirs</filename>.
+ </para>
+ <para>
+ The generated file is read-only.
+ '';
+ };
+
+ # Well-known directory list from
+ # https://gitlab.freedesktop.org/xdg/xdg-user-dirs/blob/master/man/user-dirs.dirs.xml
+
+ desktop = mkOption {
+ type = types.str;
+ default = "$HOME/Desktop";
+ description = "The Desktop directory.";
+ };
+
+ documents = mkOption {
+ type = types.str;
+ default = "$HOME/Documents";
+ description = "The Documents directory.";
+ };
+
+ download = mkOption {
+ type = types.str;
+ default = "$HOME/Downloads";
+ description = "The Downloads directory.";
+ };
+
+ music = mkOption {
+ type = types.str;
+ default = "$HOME/Music";
+ description = "The Music directory.";
+ };
+
+ pictures = mkOption {
+ type = types.str;
+ default = "$HOME/Pictures";
+ description = "The Pictures directory.";
+ };
+
+ publicShare = mkOption {
+ type = types.str;
+ default = "$HOME/Public";
+ description = "The Public share directory.";
+ };
+
+ templates = mkOption {
+ type = types.str;
+ default = "$HOME/Templates";
+ description = "The Templates directory.";
+ };
+
+ videos = mkOption {
+ type = types.str;
+ default = "$HOME/Videos";
+ description = "The Videos directory.";
+ };
+
+ extraConfig = mkOption {
+ type = with types; attrsOf str;
+ default = { };
+ example = { XDG_MISC_DIR = "$HOME/Misc"; };
+ description = "Other user directories.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ xdg.configFile."user-dirs.dirs".text = let
+ options = {
+ XDG_DESKTOP_DIR = cfg.desktop;
+ XDG_DOCUMENTS_DIR = cfg.documents;
+ XDG_DOWNLOAD_DIR = cfg.download;
+ XDG_MUSIC_DIR = cfg.music;
+ XDG_PICTURES_DIR = cfg.pictures;
+ XDG_PUBLICSHARE_DIR = cfg.publicShare;
+ XDG_TEMPLATES_DIR = cfg.templates;
+ XDG_VIDEOS_DIR = cfg.videos;
+ } // cfg.extraConfig;
+
+ # For some reason, these need to be wrapped with quotes to be valid.
+ wrapped = mapAttrs (_: value: ''"${value}"'') options;
+ in generators.toKeyValue { } wrapped;
+
+ xdg.configFile."user-dirs.conf".text = "enabled=False";
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/misc/xdg.nix b/infra/libkookie/home-manager/modules/misc/xdg.nix
new file mode 100644
index 000000000000..f207d7d353f7
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/misc/xdg.nix
@@ -0,0 +1,114 @@
+{ options, config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.xdg;
+
+ dag = config.lib.dag;
+
+ fileType = (import ../lib/file-type.nix {
+ inherit (config.home) homeDirectory;
+ inherit lib pkgs;
+ }).fileType;
+
+ defaultCacheHome = "${config.home.homeDirectory}/.cache";
+ defaultConfigHome = "${config.home.homeDirectory}/.config";
+ defaultDataHome = "${config.home.homeDirectory}/.local/share";
+
+ getXdgDir = name: fallback:
+ let
+ value = builtins.getEnv name;
+ in
+ if value != "" then value else fallback;
+
+in
+
+{
+ options.xdg = {
+ enable = mkEnableOption "management of XDG base directories";
+
+ cacheHome = mkOption {
+ type = types.path;
+ defaultText = "~/.cache";
+ description = ''
+ Absolute path to directory holding application caches.
+ '';
+ };
+
+ configFile = mkOption {
+ type = fileType "<varname>xdg.configHome</varname>" cfg.configHome;
+ default = {};
+ description = ''
+ Attribute set of files to link into the user's XDG
+ configuration home.
+ '';
+ };
+
+ configHome = mkOption {
+ type = types.path;
+ defaultText = "~/.config";
+ description = ''
+ Absolute path to directory holding application configurations.
+ '';
+ };
+
+ dataFile = mkOption {
+ type = fileType "<varname>xdg.dataHome</varname>" cfg.dataHome;
+ default = {};
+ description = ''
+ Attribute set of files to link into the user's XDG
+ data home.
+ '';
+ };
+
+ dataHome = mkOption {
+ type = types.path;
+ defaultText = "~/.local/share";
+ description = ''
+ Absolute path to directory holding application data.
+ '';
+ };
+ };
+
+ config = mkMerge [
+ (mkIf cfg.enable {
+ xdg.cacheHome = mkDefault defaultCacheHome;
+ xdg.configHome = mkDefault defaultConfigHome;
+ xdg.dataHome = mkDefault defaultDataHome;
+
+ home.sessionVariables = {
+ XDG_CACHE_HOME = cfg.cacheHome;
+ XDG_CONFIG_HOME = cfg.configHome;
+ XDG_DATA_HOME = cfg.dataHome;
+ };
+ })
+
+ # Legacy non-deterministic setup.
+ (mkIf (!cfg.enable && versionOlder config.home.stateVersion "20.09") {
+ xdg.cacheHome = getXdgDir "XDG_CACHE_HOME" defaultCacheHome;
+ xdg.configHome = getXdgDir "XDG_CONFIG_HOME" defaultConfigHome;
+ xdg.dataHome = getXdgDir "XDG_DATA_HOME" defaultDataHome;
+ })
+
+ # "Modern" deterministic setup.
+ (mkIf (!cfg.enable && versionAtLeast config.home.stateVersion "20.09") {
+ xdg.cacheHome = mkDefault defaultCacheHome;
+ xdg.configHome = mkDefault defaultConfigHome;
+ xdg.dataHome = mkDefault defaultDataHome;
+ })
+
+ {
+ home.file = mkMerge [
+ (mapAttrs'
+ (name: file: nameValuePair "${config.xdg.configHome}/${name}" file)
+ cfg.configFile)
+ (mapAttrs'
+ (name: file: nameValuePair "${config.xdg.dataHome}/${name}" file)
+ cfg.dataFile)
+ { "${config.xdg.cacheHome}/.keep".text = ""; }
+ ];
+ }
+ ];
+}
diff --git a/infra/libkookie/home-manager/modules/modules.nix b/infra/libkookie/home-manager/modules/modules.nix
new file mode 100644
index 000000000000..292037d93194
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/modules.nix
@@ -0,0 +1,232 @@
+{ pkgs
+
+ # Note, this should be "the standard library" + HM extensions.
+, lib
+
+ # Whether to enable module type checking.
+, check ? true
+
+# If disabled, the pkgs attribute passed to this function is used instead.
+, useNixpkgsModule ? true
+}:
+
+with lib;
+
+let
+
+ hostPlatform = pkgs.stdenv.hostPlatform;
+
+ loadModule = file: { condition ? true }: {
+ inherit file condition;
+ };
+
+ allModules = [
+ (loadModule ./accounts/email.nix { })
+ (loadModule ./files.nix { })
+ (loadModule ./home-environment.nix { })
+ (loadModule ./manual.nix { })
+ (loadModule ./misc/dconf.nix { })
+ (loadModule ./misc/debug.nix { })
+ (loadModule ./misc/fontconfig.nix { })
+ (loadModule ./misc/gtk.nix { })
+ (loadModule ./misc/lib.nix { })
+ (loadModule ./misc/news.nix { })
+ (loadModule ./misc/nixpkgs.nix { condition = useNixpkgsModule; })
+ (loadModule ./misc/numlock.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./misc/pam.nix { })
+ (loadModule ./misc/qt.nix { })
+ (loadModule ./misc/submodule-support.nix { })
+ (loadModule ./misc/tmpfiles.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./misc/version.nix { })
+ (loadModule ./misc/vte.nix { })
+ (loadModule ./misc/xdg-mime.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./misc/xdg-mime-apps.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./misc/xdg-user-dirs.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./misc/xdg.nix { })
+ (loadModule ./programs/abook.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./programs/afew.nix { })
+ (loadModule ./programs/alacritty.nix { })
+ (loadModule ./programs/alot.nix { })
+ (loadModule ./programs/aria2.nix { })
+ (loadModule ./programs/astroid.nix { })
+ (loadModule ./programs/autojump.nix { })
+ (loadModule ./programs/autorandr.nix { })
+ (loadModule ./programs/bash.nix { })
+ (loadModule ./programs/bat.nix { })
+ (loadModule ./programs/beets.nix { })
+ (loadModule ./programs/broot.nix { })
+ (loadModule ./programs/browserpass.nix { })
+ (loadModule ./programs/chromium.nix { })
+ (loadModule ./programs/command-not-found/command-not-found.nix { })
+ (loadModule ./programs/dircolors.nix { })
+ (loadModule ./programs/direnv.nix { })
+ (loadModule ./programs/eclipse.nix { })
+ (loadModule ./programs/emacs.nix { })
+ (loadModule ./programs/feh.nix { })
+ (loadModule ./programs/firefox.nix { })
+ (loadModule ./programs/fish.nix { })
+ (loadModule ./programs/fzf.nix { })
+ (loadModule ./programs/getmail.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./programs/gh.nix { })
+ (loadModule ./programs/git.nix { })
+ (loadModule ./programs/gnome-terminal.nix { })
+ (loadModule ./programs/go.nix { })
+ (loadModule ./programs/gpg.nix { })
+ (loadModule ./programs/home-manager.nix { })
+ (loadModule ./programs/htop.nix { })
+ (loadModule ./programs/i3status.nix { })
+ (loadModule ./programs/i3status-rust.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./programs/info.nix { })
+ (loadModule ./programs/irssi.nix { })
+ (loadModule ./programs/lieer.nix { })
+ (loadModule ./programs/jq.nix { })
+ (loadModule ./programs/kakoune.nix { })
+ (loadModule ./programs/keychain.nix { })
+ (loadModule ./programs/kitty.nix { })
+ (loadModule ./programs/lesspipe.nix { })
+ (loadModule ./programs/lf.nix { })
+ (loadModule ./programs/lsd.nix { })
+ (loadModule ./programs/man.nix { })
+ (loadModule ./programs/matplotlib.nix { })
+ (loadModule ./programs/mbsync.nix { })
+ (loadModule ./programs/mcfly.nix { })
+ (loadModule ./programs/mercurial.nix { })
+ (loadModule ./programs/mpv.nix { })
+ (loadModule ./programs/msmtp.nix { })
+ (loadModule ./programs/mu.nix { })
+ (loadModule ./programs/ncmpcpp.nix { })
+ (loadModule ./programs/ne.nix { })
+ (loadModule ./programs/neomutt.nix { })
+ (loadModule ./programs/neovim.nix { })
+ (loadModule ./programs/newsboat.nix { })
+ (loadModule ./programs/noti.nix { })
+ (loadModule ./programs/notmuch.nix { })
+ (loadModule ./programs/nushell.nix { })
+ (loadModule ./programs/obs-studio.nix { })
+ (loadModule ./programs/offlineimap.nix { })
+ (loadModule ./programs/opam.nix { })
+ (loadModule ./programs/password-store.nix { })
+ (loadModule ./programs/pazi.nix { })
+ (loadModule ./programs/pet.nix { })
+ (loadModule ./programs/pidgin.nix { })
+ (loadModule ./programs/powerline-go.nix { })
+ (loadModule ./programs/qutebrowser.nix { })
+ (loadModule ./programs/readline.nix { })
+ (loadModule ./programs/rofi.nix { })
+ (loadModule ./programs/rofi-pass.nix { })
+ (loadModule ./programs/rtorrent.nix { })
+ (loadModule ./programs/skim.nix { })
+ (loadModule ./programs/starship.nix { })
+ (loadModule ./programs/ssh.nix { })
+ (loadModule ./programs/taskwarrior.nix { })
+ (loadModule ./programs/termite.nix { })
+ (loadModule ./programs/texlive.nix { })
+ (loadModule ./programs/tmux.nix { })
+ (loadModule ./programs/urxvt.nix { })
+ (loadModule ./programs/vim.nix { })
+ (loadModule ./programs/vscode.nix { })
+ (loadModule ./programs/vscode/haskell.nix { })
+ (loadModule ./programs/waybar.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./programs/z-lua.nix { })
+ (loadModule ./programs/zathura.nix { })
+ (loadModule ./programs/zoxide.nix { })
+ (loadModule ./programs/zplug.nix { })
+ (loadModule ./programs/zsh.nix { })
+ (loadModule ./programs/zsh/prezto.nix { })
+ (loadModule ./services/blueman-applet.nix { })
+ (loadModule ./services/caffeine.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/cbatticon.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/clipmenu.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/compton.nix { })
+ (loadModule ./services/dropbox.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/dunst.nix { })
+ (loadModule ./services/dwm-status.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/emacs.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/flameshot.nix { })
+ (loadModule ./services/fluidsynth.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/gammastep.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/getmail.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/gnome-keyring.nix { })
+ (loadModule ./services/gpg-agent.nix { })
+ (loadModule ./services/grobi.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/hound.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/imapnotify.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/kanshi.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/kbfs.nix { })
+ (loadModule ./services/kdeconnect.nix { })
+ (loadModule ./services/keepassx.nix { })
+ (loadModule ./services/keybase.nix { })
+ (loadModule ./services/keynav.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/lieer.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/lorri.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/mako.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/mbsync.nix { })
+ (loadModule ./services/mpd.nix { })
+ (loadModule ./services/mpdris2.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/muchsync.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/network-manager-applet.nix { })
+ (loadModule ./services/nextcloud-client.nix { })
+ (loadModule ./services/owncloud-client.nix { })
+ (loadModule ./services/parcellite.nix { })
+ (loadModule ./services/password-store-sync.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/pasystray.nix { })
+ (loadModule ./services/pbgopy.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/picom.nix { })
+ (loadModule ./services/polybar.nix { })
+ (loadModule ./services/pulseeffects.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/random-background.nix { })
+ (loadModule ./services/redshift.nix { })
+ (loadModule ./services/rsibreak.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/screen-locker.nix { })
+ (loadModule ./services/stalonetray.nix { })
+ (loadModule ./services/status-notifier-watcher.nix { })
+ (loadModule ./services/spotifyd.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/sxhkd.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/syncthing.nix { })
+ (loadModule ./services/taffybar.nix { })
+ (loadModule ./services/tahoe-lafs.nix { })
+ (loadModule ./services/taskwarrior-sync.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/udiskie.nix { })
+ (loadModule ./services/unclutter.nix { })
+ (loadModule ./services/unison.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/window-managers/awesome.nix { })
+ (loadModule ./services/window-managers/bspwm/default.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/window-managers/i3-sway/i3.nix { })
+ (loadModule ./services/window-managers/i3-sway/sway.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/window-managers/xmonad.nix { })
+ (loadModule ./services/wlsunset.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/xcape.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/xembed-sni-proxy.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/xscreensaver.nix { })
+ (loadModule ./services/xsuspender.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./systemd.nix { })
+ (loadModule ./targets/darwin.nix { condition = hostPlatform.isDarwin; })
+ (loadModule ./targets/generic-linux.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./xcursor.nix { })
+ (loadModule ./xresources.nix { })
+ (loadModule ./xsession.nix { })
+ (loadModule (pkgs.path + "/nixos/modules/misc/assertions.nix") { })
+ (loadModule (pkgs.path + "/nixos/modules/misc/meta.nix") { })
+ ];
+
+ modules = map (getAttr "file") (filter (getAttr "condition") allModules);
+
+ pkgsModule = { config, ... }: {
+ config = {
+ _module.args.baseModules = modules;
+ _module.args.pkgsPath = lib.mkDefault (
+ if versionAtLeast config.home.stateVersion "20.09" then
+ pkgs.path
+ else
+ <nixpkgs>);
+ _module.args.pkgs = lib.mkDefault pkgs;
+ _module.check = check;
+ lib = lib.hm;
+ } // optionalAttrs useNixpkgsModule {
+ nixpkgs.system = mkDefault pkgs.system;
+ };
+ };
+
+in
+
+ modules ++ [ pkgsModule ]
diff --git a/infra/libkookie/home-manager/modules/programs/abook.nix b/infra/libkookie/home-manager/modules/programs/abook.nix
new file mode 100644
index 000000000000..4ddc080ad51a
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/abook.nix
@@ -0,0 +1,40 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.abook;
+
+in {
+ options.programs.abook = {
+ enable = mkEnableOption "Abook";
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ field pager = Pager
+ view CONTACT = name, email
+ set autosave=true
+ '';
+ description = ''
+ Extra lines added to <filename>$HOME/.config/abook/abookrc</filename>.
+ Available configuration options are described in the abook repository:
+ <link xlink:href="https://sourceforge.net/p/abook/git/ci/master/tree/sample.abookrc" />.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.abook ];
+ xdg.configFile."abook/abookrc" = mkIf (cfg.extraConfig != "") {
+ text = ''
+ # Generated by Home Manager.
+ # See http://abook.sourceforge.net/
+
+ ${cfg.extraConfig}
+ '';
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/afew.nix b/infra/libkookie/home-manager/modules/programs/afew.nix
new file mode 100644
index 000000000000..99bae88c0ee4
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/afew.nix
@@ -0,0 +1,52 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.afew;
+
+in
+
+{
+ options.programs.afew = {
+ enable = mkEnableOption "the afew initial tagging script for Notmuch";
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = ''
+ [SpamFilter]
+ [KillThreadsFilter]
+ [ListMailsFilter]
+ [ArchiveSentMailsFilter]
+ [InboxFilter]
+ '';
+ example = ''
+ [SpamFilter]
+
+ [Filter.0]
+ query = from:pointyheaded@boss.com
+ tags = -new;+boss
+ message = Message from above
+
+ [InboxFilter]
+ '';
+ description = ''
+ Extra lines added to afew configuration file. Available
+ configuration options are described in the afew manual:
+ <link xlink:href="https://afew.readthedocs.io/en/latest/configuration.html" />.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.afew ];
+
+ xdg.configFile."afew/config".text = ''
+ # Generated by Home Manager.
+ # See https://afew.readthedocs.io/
+
+ ${cfg.extraConfig}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/alacritty.nix b/infra/libkookie/home-manager/modules/programs/alacritty.nix
new file mode 100644
index 000000000000..9c3e8e75dc40
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/alacritty.nix
@@ -0,0 +1,63 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.alacritty;
+ yamlFormat = pkgs.formats.yaml { };
+in {
+ options = {
+ programs.alacritty = {
+ enable = mkEnableOption "Alacritty";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.alacritty;
+ defaultText = literalExample "pkgs.alacritty";
+ description = "The Alacritty package to install.";
+ };
+
+ settings = mkOption {
+ type = yamlFormat.type;
+ default = { };
+ example = literalExample ''
+ {
+ window.dimensions = {
+ lines = 3;
+ columns = 200;
+ };
+ key_bindings = [
+ {
+ key = "K";
+ mods = "Control";
+ chars = "\\x0c";
+ }
+ ];
+ }
+ '';
+ description = ''
+ Configuration written to
+ <filename>~/.config/alacritty/alacritty.yml</filename>. See
+ <link xlink:href="https://github.com/jwilm/alacritty/blob/master/alacritty.yml"/>
+ for the default configuration.
+ '';
+ };
+ };
+ };
+
+ config = mkMerge [
+ (mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+
+ xdg.configFile."alacritty/alacritty.yml" = mkIf (cfg.settings != { }) {
+ # TODO: Replace by the generate function but need to figure out how to
+ # handle the escaping first.
+ #
+ # source = yamlFormat.generate "alacritty.yml" cfg.settings;
+
+ text =
+ replaceStrings [ "\\\\" ] [ "\\" ] (builtins.toJSON cfg.settings);
+ };
+ })
+ ];
+}
diff --git a/infra/libkookie/home-manager/modules/programs/alot-accounts.nix b/infra/libkookie/home-manager/modules/programs/alot-accounts.nix
new file mode 100644
index 000000000000..89ae28f9c8ed
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/alot-accounts.nix
@@ -0,0 +1,58 @@
+pkgs:
+{ config, lib, ... }:
+
+with lib;
+
+{
+ options.alot = {
+ sendMailCommand = mkOption {
+ type = types.nullOr types.str;
+ description = ''
+ Command to send a mail. If msmtp is enabled for the account,
+ then this is set to
+ <command>msmtpq --read-envelope-from --read-recipients</command>.
+ '';
+ };
+
+ contactCompletion = mkOption {
+ type = types.attrsOf types.str;
+ default = {
+ type = "shellcommand";
+ command =
+ "'${pkgs.notmuch}/bin/notmuch address --format=json --output=recipients date:6M..'";
+ regexp = "'\\[?{" + ''
+ "name": "(?P<name>.*)", "address": "(?P<email>.+)", "name-addr": ".*"''
+ + "}[,\\]]?'";
+ shellcommand_external_filtering = "False";
+ };
+ example = literalExample ''
+ {
+ type = "shellcommand";
+ command = "abook --mutt-query";
+ regexp = "'^(?P<email>[^@]+@[^\t]+)\t+(?P<name>[^\t]+)'";
+ ignorecase = "True";
+ }
+ '';
+ description = ''
+ Contact completion configuration as expected per alot.
+ See <link xlink:href="http://alot.readthedocs.io/en/latest/configuration/contacts_completion.html">alot's wiki</link> for
+ explanation about possible values.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra settings to add to this Alot account configuration.
+ '';
+ };
+ };
+
+ config = mkIf config.notmuch.enable {
+ alot.sendMailCommand = mkOptionDefault (if config.msmtp.enable then
+ "msmtpq --read-envelope-from --read-recipients"
+ else
+ null);
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/alot.nix b/infra/libkookie/home-manager/modules/programs/alot.nix
new file mode 100644
index 000000000000..e907cd3e0ac5
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/alot.nix
@@ -0,0 +1,237 @@
+# alot config loader is sensitive to leading space !
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.alot;
+
+ alotAccounts =
+ filter (a: a.notmuch.enable) (attrValues config.accounts.email.accounts);
+
+ boolStr = v: if v then "True" else "False";
+
+ mkKeyValue = key: value:
+ let value' = if isBool value then boolStr value else toString value;
+ in "${key} = ${value'}";
+
+ mk2ndLevelSectionName = name: "[" + name + "]";
+
+ tagSubmodule = types.submodule {
+ options = {
+ translated = mkOption {
+ type = types.nullOr types.str;
+ description = ''
+ Fixed string representation for this tag. The tag can be
+ hidden from view, if the key translated is set to
+ <literal>""</literal>, the empty string.
+ '';
+ };
+
+ translation = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ A pair of strings that define a regular substitution to
+ compute the string representation on the fly using
+ <literal>re.sub</literal>.
+ '';
+ };
+
+ normal = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "'','', 'white','light red', 'white','#d66'";
+ description = ''
+ How to display the tag when unfocused.
+ See <link xlink:href="https://alot.readthedocs.io/en/latest/configuration/theming.html#tagstring-formatting"/>.
+ '';
+ };
+
+ focus = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "How to display the tag when focused.";
+ };
+ };
+ };
+
+ accountStr = account:
+ with account;
+ concatStringsSep "\n" ([ "[[${name}]]" ]
+ ++ mapAttrsToList (n: v: n + "=" + v) ({
+ address = address;
+ realname = realName;
+ sendmail_command =
+ optionalString (alot.sendMailCommand != null) alot.sendMailCommand;
+ sent_box = "maildir" + "://" + maildir.absPath + "/" + folders.sent;
+ draft_box = "maildir" + "://" + maildir.absPath + "/" + folders.drafts;
+ } // optionalAttrs (aliases != [ ]) {
+ aliases = concatStringsSep "," aliases;
+ } // optionalAttrs (gpg != null) {
+ gpg_key = gpg.key;
+ encrypt_by_default = if gpg.encryptByDefault then "all" else "none";
+ sign_by_default = boolStr gpg.signByDefault;
+ } // optionalAttrs (signature.showSignature != "none") {
+ signature = pkgs.writeText "signature.txt" signature.text;
+ signature_as_attachment = boolStr (signature.showSignature == "attach");
+ }) ++ [ alot.extraConfig ] ++ [ "[[[abook]]]" ]
+ ++ mapAttrsToList (n: v: n + "=" + v) alot.contactCompletion);
+
+ configFile = let
+ bindingsToStr = attrSet:
+ concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${v}") attrSet);
+ in ''
+ # Generated by Home Manager.
+ # See http://alot.readthedocs.io/en/latest/configuration/config_options.html
+
+ ${generators.toKeyValue { inherit mkKeyValue; } cfg.settings}
+ ${cfg.extraConfig}
+ [tags]
+ '' + (let
+ submoduleToAttrs = m:
+ filterAttrs (name: v: name != "_module" && v != null) m;
+ in generators.toINI { mkSectionName = mk2ndLevelSectionName; }
+ (mapAttrs (name: x: submoduleToAttrs x) cfg.tags)) + ''
+ [bindings]
+ ${bindingsToStr cfg.bindings.global}
+
+ [[bufferlist]]
+ ${bindingsToStr cfg.bindings.bufferlist}
+ [[search]]
+ ${bindingsToStr cfg.bindings.search}
+ [[envelope]]
+ ${bindingsToStr cfg.bindings.envelope}
+ [[taglist]]
+ ${bindingsToStr cfg.bindings.taglist}
+ [[thread]]
+ ${bindingsToStr cfg.bindings.thread}
+
+ [accounts]
+
+ ${concatStringsSep "\n\n" (map accountStr alotAccounts)}
+ '';
+
+in {
+ options = {
+ programs.alot = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ example = true;
+ description = ''
+ Whether to enable the Alot mail user agent. Alot uses the
+ Notmuch email system and will therefore be automatically
+ enabled for each email account that is managed by Notmuch.
+ '';
+ };
+
+ hooks = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Content of the hooks file.
+ '';
+ };
+
+ bindings = mkOption {
+ type = types.submodule {
+ options = {
+ global = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ description = "Global keybindings.";
+ };
+
+ bufferlist = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ description = "Bufferlist mode keybindings.";
+ };
+
+ search = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ description = "Search mode keybindings.";
+ };
+
+ envelope = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ description = "Envelope mode keybindings.";
+ };
+
+ taglist = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ description = "Taglist mode keybindings.";
+ };
+
+ thread = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ description = "Thread mode keybindings.";
+ };
+ };
+ };
+ default = { };
+ description = ''
+ Keybindings.
+ '';
+ };
+
+ tags = mkOption {
+ type = types.attrsOf tagSubmodule;
+ default = { };
+ description = "How to display the tags.";
+ };
+
+ settings = mkOption {
+ type = with types;
+ let primitive = either (either (either str int) bool) float;
+ in attrsOf primitive;
+ default = {
+ initial_command = "search tag:inbox AND NOT tag:killed";
+ auto_remove_unread = true;
+ handle_mouse = true;
+ prefer_plaintext = true;
+ };
+ example = literalExample ''
+ {
+ auto_remove_unread = true;
+ ask_subject = false;
+ thread_indent_replies = 2;
+ }
+ '';
+ description = ''
+ Configuration options added to alot configuration file.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra lines added to alot configuration file.
+ '';
+ };
+ };
+
+ accounts.email.accounts = mkOption {
+ type = with types; attrsOf (submodule (import ./alot-accounts.nix pkgs));
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.alot ];
+
+ xdg.configFile."alot/config".text = configFile;
+
+ xdg.configFile."alot/hooks.py" = mkIf (cfg.hooks != "") {
+ text = ''
+ # Generated by Home Manager.
+ '' + cfg.hooks;
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/aria2.nix b/infra/libkookie/home-manager/modules/programs/aria2.nix
new file mode 100644
index 000000000000..d1317ff7616c
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/aria2.nix
@@ -0,0 +1,61 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.aria2;
+
+ formatLine = n: v:
+ let
+ formatValue = v:
+ if builtins.isBool v then
+ (if v then "true" else "false")
+ else
+ toString v;
+ in "${n}=${formatValue v}";
+in {
+ meta.maintainers = [ hm.maintainers.justinlovinger ];
+
+ options.programs.aria2 = {
+ enable = mkEnableOption "aria2";
+
+ settings = mkOption {
+ type = with types; attrsOf (oneOf [ bool float int str ]);
+ default = { };
+ description = ''
+ Options to add to <filename>aria2.conf</filename> file.
+ See
+ <citerefentry>
+ <refentrytitle>aria2c</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ for options.
+ '';
+ example = literalExample ''
+ {
+ listen-port = 60000;
+ dht-listen-port = 60000;
+ seed-ratio = 1.0;
+ max-upload-limit = "50K";
+ ftp-pasv = true;
+ }
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra lines added to <filename>aria2.conf</filename> file.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.aria2 ];
+
+ xdg.configFile."aria2/aria2.conf".text = concatStringsSep "\n" ([ ]
+ ++ mapAttrsToList formatLine cfg.settings
+ ++ optional (cfg.extraConfig != "") cfg.extraConfig);
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/astroid-accounts.nix b/infra/libkookie/home-manager/modules/programs/astroid-accounts.nix
new file mode 100644
index 000000000000..fb803867efbe
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/astroid-accounts.nix
@@ -0,0 +1,32 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+ options.astroid = {
+ enable = mkEnableOption "Astroid";
+
+ sendMailCommand = mkOption {
+ type = types.str;
+ description = ''
+ Command to send a mail. If msmtp is enabled for the account,
+ then this is set to
+ <command>msmtpq --read-envelope-from --read-recipients</command>.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.attrsOf types.anything;
+ default = { };
+ example = { select_query = ""; };
+ description = ''
+ Extra settings to add to this astroid account configuration.
+ '';
+ };
+ };
+
+ config = mkIf config.notmuch.enable {
+ astroid.sendMailCommand = mkIf config.msmtp.enable
+ (mkOptionDefault "msmtpq --read-envelope-from --read-recipients");
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/astroid-config-template.json b/infra/libkookie/home-manager/modules/programs/astroid-config-template.json
new file mode 100644
index 000000000000..87e3f764f9ce
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/astroid-config-template.json
@@ -0,0 +1,113 @@
+{
+ "astroid": {
+ "config": {
+ "version": "11"
+ },
+ "debug": {
+ "dryrun_sending": "false"
+ },
+ "hints": {
+ "level": "0"
+ },
+ "log": {
+ "syslog": "false",
+ "stdout": "true",
+ "level": "info"
+ }
+ },
+ "startup": {
+ "queries": {
+ "inbox": "tag:inbox"
+ }
+ },
+ "terminal": {
+ "height": "10",
+ "font_description": "default"
+ },
+ "thread_index": {
+ "page_jump_rows": "6",
+ "sort_order": "newest",
+ "cell": {
+ "font_description": "default",
+ "line_spacing": "2",
+ "date_length": "10",
+ "message_count_length": "4",
+ "authors_length": "20",
+ "subject_color": "#807d74",
+ "subject_color_selected": "#000000",
+ "background_color_selected": "",
+ "background_color_marked": "#fff584",
+ "background_color_marked_selected": "#bcb559",
+ "tags_length": "80",
+ "tags_upper_color": "#e5e5e5",
+ "tags_lower_color": "#333333",
+ "tags_alpha": "0.5",
+ "hidden_tags": "attachment,flagged,unread"
+ }
+ },
+ "general": {
+ "time": {
+ "clock_format": "local",
+ "same_year": "%b %-e",
+ "diff_year": "%x"
+ }
+ },
+ "editor": {
+ "charset": "utf-8",
+ "save_draft_on_force_quit": "true",
+ "attachment_words": "attach",
+ "attachment_directory": "~",
+ "markdown_processor": "marked"
+ },
+ "mail": {
+ "reply": {
+ "quote_line": "Excerpts from %1's message of %2:",
+ "mailinglist_reply_to_sender": "true"
+ },
+ "forward": {
+ "quote_line": "Forwarding %1's message of %2:",
+ "disposition": "inline"
+ },
+ "sent_tags": "sent",
+ "message_id_fqdn": "",
+ "message_id_user": "",
+ "user_agent": "default",
+ "send_delay": "2",
+ "close_on_success": "false",
+ "format_flowed": "false"
+ },
+ "poll": {
+ "interval": "60",
+ "always_full_refresh": "false"
+ },
+ "attachment": {
+ "external_open_cmd": "xdg-open"
+ },
+ "thread_view": {
+ "open_html_part_external": "false",
+ "preferred_type": "plain",
+ "preferred_html_only": "false",
+ "allow_remote_when_encrypted": "false",
+ "open_external_link": "xdg-open",
+ "default_save_directory": "~",
+ "indent_messages": "false",
+ "gravatar": {
+ "enable": "true"
+ },
+ "mark_unread_delay": "0.5",
+ "expand_flagged": "true"
+ },
+ "crypto": {
+ "gpg": {
+ "path": "gpg2",
+ "always_trust": "true",
+ "enabled": "true"
+ }
+ },
+ "saved_searches": {
+ "show_on_startup": "false",
+ "save_history": "true",
+ "history_lines_to_show": "15",
+ "history_lines": "1000"
+ }
+}
diff --git a/infra/libkookie/home-manager/modules/programs/astroid.nix b/infra/libkookie/home-manager/modules/programs/astroid.nix
new file mode 100644
index 000000000000..8af18f16c1dd
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/astroid.nix
@@ -0,0 +1,127 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+with builtins;
+
+let
+
+ cfg = config.programs.astroid;
+
+ jsonFormat = pkgs.formats.json { };
+
+ astroidAccounts =
+ filterAttrs (n: v: v.astroid.enable) config.accounts.email.accounts;
+
+ boolOpt = b: if b then "true" else "false";
+
+ accountAttr = account:
+ with account;
+ {
+ email = address;
+ name = realName;
+ sendmail = astroid.sendMailCommand;
+ additional_sent_tags = "";
+ default = boolOpt primary;
+ save_drafts_to = "${maildir.absPath}/${folders.drafts}";
+ save_sent = "true";
+ save_sent_to = "${maildir.absPath}/${folders.sent}";
+ select_query = "";
+ } // optionalAttrs (signature.showSignature != "none") {
+ signature_attach = boolOpt (signature.showSignature == "attach");
+ signature_default_on = boolOpt (signature.showSignature != "none");
+ signature_file = pkgs.writeText "signature.txt" signature.text;
+ signature_file_markdown = "false";
+ signature_separate = "true"; # prepends '--\n' to the signature
+ } // optionalAttrs (gpg != null) {
+ always_gpg_sign = boolOpt gpg.signByDefault;
+ gpgkey = gpg.key;
+ } // astroid.extraConfig;
+
+ # See https://github.com/astroidmail/astroid/wiki/Configuration-Reference
+ finalConfig = let
+ template = fromJSON (readFile ./astroid-config-template.json);
+ astroidConfig = foldl' recursiveUpdate template [
+ {
+ astroid.notmuch_config = "${config.xdg.configHome}/notmuch/notmuchrc";
+ accounts = mapAttrs (n: accountAttr) astroidAccounts;
+ crypto.gpg.path = "${pkgs.gnupg}/bin/gpg";
+ }
+ cfg.extraConfig
+ cfg.externalEditor
+ ];
+ in astroidConfig;
+
+in {
+ options = {
+ programs.astroid = {
+ enable = mkEnableOption "Astroid";
+
+ pollScript = mkOption {
+ type = types.str;
+ default = "";
+ example = "mbsync gmail";
+ description = ''
+ Script to run to fetch/update mails.
+ '';
+ };
+
+ externalEditor = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ # Converts it into JSON that can be merged into the configuration.
+ apply = cmd:
+ optionalAttrs (cmd != null) {
+ editor = {
+ "external_editor" = "true";
+ "cmd" = cmd;
+ };
+ };
+ example =
+ "nvim-qt -- -c 'set ft=mail' '+set fileencoding=utf-8' '+set ff=unix' '+set enc=utf-8' '+set fo+=w' %1";
+ description = ''
+ You can use <code>%1</code>, <code>%2</code>, and
+ <code>%3</code> to refer respectively to:
+ <orderedlist numeration="arabic">
+ <listitem><para>file name</para></listitem>
+ <listitem><para>server name</para></listitem>
+ <listitem><para>socket ID</para></listitem>
+ </orderedlist>
+ See <link xlink:href='https://github.com/astroidmail/astroid/wiki/Customizing-editor' />.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = jsonFormat.type;
+ default = { };
+ example = literalExample ''
+ {
+ poll.interval = 0;
+ }
+ '';
+ description = ''
+ JSON config that will override the default Astroid configuration.
+ '';
+ };
+ };
+
+ accounts.email.accounts = mkOption {
+ type = with types; attrsOf (submodule (import ./astroid-accounts.nix));
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.astroid ];
+
+ xdg.configFile."astroid/config".source =
+ jsonFormat.generate "astroid-config" finalConfig;
+
+ xdg.configFile."astroid/poll.sh" = {
+ executable = true;
+ text = ''
+ # Generated by Home Manager
+
+ ${cfg.pollScript}
+ '';
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/autojump.nix b/infra/libkookie/home-manager/modules/programs/autojump.nix
new file mode 100644
index 000000000000..db3bdaf593c0
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/autojump.nix
@@ -0,0 +1,56 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.autojump;
+ package = pkgs.autojump;
+
+in {
+ meta.maintainers = [ maintainers.evanjs ];
+
+ options.programs.autojump = {
+ enable = mkEnableOption "autojump";
+
+ enableBashIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Bash integration.
+ '';
+ };
+
+ enableZshIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Zsh integration.
+ '';
+ };
+
+ enableFishIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Fish integration.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ package ];
+
+ programs.bash.initExtra = mkIf cfg.enableBashIntegration (mkBefore ''
+ . ${package}/share/autojump/autojump.bash
+ '');
+
+ programs.zsh.initExtra = mkIf cfg.enableZshIntegration ''
+ . ${package}/share/autojump/autojump.zsh
+ '';
+
+ programs.fish.promptInit = mkIf cfg.enableFishIntegration ''
+ . ${package}/share/autojump/autojump.fish
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/autorandr.nix b/infra/libkookie/home-manager/modules/programs/autorandr.nix
new file mode 100644
index 000000000000..40cad704db9f
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/autorandr.nix
@@ -0,0 +1,364 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.autorandr;
+
+ matrixOf = n: m: elemType:
+ mkOptionType rec {
+ name = "matrixOf";
+ description =
+ "${toString n}×${toString m} matrix of ${elemType.description}s";
+ check = xss:
+ let listOfSize = l: xs: isList xs && length xs == l;
+ in listOfSize n xss
+ && all (xs: listOfSize m xs && all elemType.check xs) xss;
+ merge = mergeOneOption;
+ getSubOptions = prefix: elemType.getSubOptions (prefix ++ [ "*" "*" ]);
+ getSubModules = elemType.getSubModules;
+ substSubModules = mod: matrixOf n m (elemType.substSubModules mod);
+ functor = (defaultFunctor name) // { wrapped = elemType; };
+ };
+
+ profileModule = types.submodule {
+ options = {
+ fingerprint = mkOption {
+ type = types.attrsOf types.str;
+ description = ''
+ Output name to EDID mapping.
+ Use <code>autorandr --fingerprint</code> to get current setup values.
+ '';
+ default = { };
+ };
+
+ config = mkOption {
+ type = types.attrsOf configModule;
+ description = "Per output profile configuration.";
+ default = { };
+ };
+
+ hooks = mkOption {
+ type = profileHooksModule;
+ description = "Profile hook scripts.";
+ default = { };
+ };
+ };
+ };
+
+ configModule = types.submodule {
+ options = {
+ enable = mkOption {
+ type = types.bool;
+ description = "Whether to enable the output.";
+ default = true;
+ };
+
+ crtc = mkOption {
+ type = types.nullOr types.ints.unsigned;
+ description = "Output video display controller.";
+ default = null;
+ example = 0;
+ };
+
+ primary = mkOption {
+ type = types.bool;
+ description = "Whether output should be marked as primary";
+ default = false;
+ };
+
+ position = mkOption {
+ type = types.str;
+ description = "Output position";
+ default = "";
+ example = "5760x0";
+ };
+
+ mode = mkOption {
+ type = types.str;
+ description = "Output resolution.";
+ default = "";
+ example = "3840x2160";
+ };
+
+ rate = mkOption {
+ type = types.str;
+ description = "Output framerate.";
+ default = "";
+ example = "60.00";
+ };
+
+ gamma = mkOption {
+ type = types.str;
+ description = "Output gamma configuration.";
+ default = "";
+ example = "1.0:0.909:0.833";
+ };
+
+ rotate = mkOption {
+ type = types.nullOr (types.enum [ "normal" "left" "right" "inverted" ]);
+ description = "Output rotate configuration.";
+ default = null;
+ example = "left";
+ };
+
+ transform = mkOption {
+ type = types.nullOr (matrixOf 3 3 types.float);
+ default = null;
+ example = literalExample ''
+ [
+ [ 0.6 0.0 0.0 ]
+ [ 0.0 0.6 0.0 ]
+ [ 0.0 0.0 1.0 ]
+ ]
+ '';
+ description = ''
+ Refer to
+ <citerefentry>
+ <refentrytitle>xrandr</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ for the documentation of the transform matrix.
+ '';
+ };
+
+ dpi = mkOption {
+ type = types.nullOr types.ints.positive;
+ description = "Output DPI configuration.";
+ default = null;
+ example = 96;
+ };
+
+ scale = mkOption {
+ type = types.nullOr (types.submodule {
+ options = {
+ method = mkOption {
+ type = types.enum [ "factor" "pixel" ];
+ description = "Output scaling method.";
+ default = "factor";
+ example = "pixel";
+ };
+
+ x = mkOption {
+ type = types.either types.float types.ints.positive;
+ description = "Horizontal scaling factor/pixels.";
+ };
+
+ y = mkOption {
+ type = types.either types.float types.ints.positive;
+ description = "Vertical scaling factor/pixels.";
+ };
+ };
+ });
+ description = ''
+ Output scale configuration.
+ </para><para>
+ Either configure by pixels or a scaling factor. When using pixel method the
+ <citerefentry>
+ <refentrytitle>xrandr</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ option
+ <parameter class="command">--scale-from</parameter>
+ will be used; when using factor method the option
+ <parameter class="command">--scale</parameter>
+ will be used.
+ </para><para>
+ This option is a shortcut version of the transform option and they are mutually
+ exclusive.
+ '';
+ default = null;
+ example = literalExample ''
+ {
+ x = 1.25;
+ y = 1.25;
+ }
+ '';
+ };
+ };
+ };
+
+ hookType = types.lines;
+
+ globalHooksModule = types.submodule {
+ options = {
+ postswitch = mkOption {
+ type = types.attrsOf hookType;
+ description = "Postswitch hook executed after mode switch.";
+ default = { };
+ };
+
+ preswitch = mkOption {
+ type = types.attrsOf hookType;
+ description = "Preswitch hook executed before mode switch.";
+ default = { };
+ };
+
+ predetect = mkOption {
+ type = types.attrsOf hookType;
+ description = ''
+ Predetect hook executed before autorandr attempts to run xrandr.
+ '';
+ default = { };
+ };
+ };
+ };
+
+ profileHooksModule = types.submodule {
+ options = {
+ postswitch = mkOption {
+ type = hookType;
+ description = "Postswitch hook executed after mode switch.";
+ default = "";
+ };
+
+ preswitch = mkOption {
+ type = hookType;
+ description = "Preswitch hook executed before mode switch.";
+ default = "";
+ };
+
+ predetect = mkOption {
+ type = hookType;
+ description = ''
+ Predetect hook executed before autorandr attempts to run xrandr.
+ '';
+ default = "";
+ };
+ };
+ };
+
+ hookToFile = folder: name: hook:
+ nameValuePair "autorandr/${folder}/${name}" {
+ source = "${pkgs.writeShellScriptBin "hook" hook}/bin/hook";
+ };
+ profileToFiles = name: profile:
+ with profile;
+ mkMerge ([
+ {
+ "autorandr/${name}/setup".text = concatStringsSep "\n"
+ (mapAttrsToList fingerprintToString fingerprint);
+ "autorandr/${name}/config".text =
+ concatStringsSep "\n" (mapAttrsToList configToString profile.config);
+ }
+ (mkIf (hooks.postswitch != "")
+ (listToAttrs [ (hookToFile name "postswitch" hooks.postswitch) ]))
+ (mkIf (hooks.preswitch != "")
+ (listToAttrs [ (hookToFile name "preswitch" hooks.preswitch) ]))
+ (mkIf (hooks.predetect != "")
+ (listToAttrs [ (hookToFile name "predetect" hooks.predetect) ]))
+ ]);
+ fingerprintToString = name: edid: "${name} ${edid}";
+ configToString = name: config:
+ if config.enable then
+ concatStringsSep "\n" ([ "output ${name}" ]
+ ++ optional (config.position != "") "pos ${config.position}"
+ ++ optional (config.crtc != null) "crtc ${toString config.crtc}"
+ ++ optional config.primary "primary"
+ ++ optional (config.dpi != null) "dpi ${toString config.dpi}"
+ ++ optional (config.gamma != "") "gamma ${config.gamma}"
+ ++ optional (config.mode != "") "mode ${config.mode}"
+ ++ optional (config.rate != "") "rate ${config.rate}"
+ ++ optional (config.rotate != null) "rotate ${config.rotate}"
+ ++ optional (config.transform != null) ("transform "
+ + concatMapStringsSep "," toString (flatten config.transform))
+ ++ optional (config.scale != null)
+ ((if config.scale.method == "factor" then "scale" else "scale-from")
+ + " ${toString config.scale.x}x${toString config.scale.y}"))
+ else ''
+ output ${name}
+ off
+ '';
+
+in {
+ options = {
+ programs.autorandr = {
+ enable = mkEnableOption "Autorandr";
+
+ hooks = mkOption {
+ type = globalHooksModule;
+ description = "Global hook scripts";
+ default = { };
+ example = literalExample ''
+ {
+ postswitch = {
+ "notify-i3" = "''${pkgs.i3}/bin/i3-msg restart";
+ "change-background" = readFile ./change-background.sh;
+ "change-dpi" = '''
+ case "$AUTORANDR_CURRENT_PROFILE" in
+ default)
+ DPI=120
+ ;;
+ home)
+ DPI=192
+ ;;
+ work)
+ DPI=144
+ ;;
+ *)
+ echo "Unknown profle: $AUTORANDR_CURRENT_PROFILE"
+ exit 1
+ esac
+
+ echo "Xft.dpi: $DPI" | ''${pkgs.xorg.xrdb}/bin/xrdb -merge
+ '''
+ };
+ }
+ '';
+ };
+
+ profiles = mkOption {
+ type = types.attrsOf profileModule;
+ description = "Autorandr profiles specification.";
+ default = { };
+ example = literalExample ''
+ {
+ "work" = {
+ fingerprint = {
+ eDP1 = "<EDID>";
+ DP1 = "<EDID>";
+ };
+ config = {
+ eDP1.enable = false;
+ DP1 = {
+ enable = true;
+ crtc = 0;
+ primary = true;
+ position = "0x0";
+ mode = "3840x2160";
+ gamma = "1.0:0.909:0.833";
+ rate = "60.00";
+ rotate = "left";
+ };
+ };
+ hooks.postswitch = readFile ./work-postswitch.sh;
+ };
+ }
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = flatten (mapAttrsToList (profile:
+ { config, ... }:
+ mapAttrsToList (output: opts: {
+ assertion = opts.scale == null || opts.transform == null;
+ message = ''
+ Cannot use the profile output options 'scale' and 'transform' simultaneously.
+ Check configuration for: programs.autorandr.profiles.${profile}.config.${output}
+ '';
+ }) config) cfg.profiles);
+
+ home.packages = [ pkgs.autorandr ];
+ xdg.configFile = mkMerge ([
+ (mapAttrs' (hookToFile "postswitch.d") cfg.hooks.postswitch)
+ (mapAttrs' (hookToFile "preswitch.d") cfg.hooks.preswitch)
+ (mapAttrs' (hookToFile "predetect.d") cfg.hooks.predetect)
+ (mkMerge (mapAttrsToList profileToFiles cfg.profiles))
+ ]);
+ };
+
+ meta.maintainers = [ maintainers.uvnikita ];
+}
diff --git a/infra/libkookie/home-manager/modules/programs/bash.nix b/infra/libkookie/home-manager/modules/programs/bash.nix
new file mode 100644
index 000000000000..6338f5e4a59e
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/bash.nix
@@ -0,0 +1,220 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.bash;
+
+in
+
+{
+ meta.maintainers = [ maintainers.rycee ];
+
+ imports = [
+ (mkRenamedOptionModule [ "programs" "bash" "enableAutojump" ] [
+ "programs"
+ "autojump"
+ "enable"
+ ])
+ ];
+
+ options = {
+ programs.bash = {
+ enable = mkEnableOption "GNU Bourne-Again SHell";
+
+ historySize = mkOption {
+ type = types.int;
+ default = 10000;
+ description = "Number of history lines to keep in memory.";
+ };
+
+ historyFile = mkOption {
+ type = types.str;
+ default = "$HOME/.bash_history";
+ description = "Location of the bash history file.";
+ };
+
+ historyFileSize = mkOption {
+ type = types.int;
+ default = 100000;
+ description = "Number of history lines to keep on file.";
+ };
+
+ historyControl = mkOption {
+ type = types.listOf (types.enum [
+ "erasedups"
+ "ignoredups"
+ "ignorespace"
+ ]);
+ default = [];
+ description = "Controlling how commands are saved on the history list.";
+ };
+
+ historyIgnore = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [ "ls" "cd" "exit" ];
+ description = "List of commands that should not be saved to the history list.";
+ };
+
+ shellOptions = mkOption {
+ type = types.listOf types.str;
+ default = [
+ # Append to history file rather than replacing it.
+ "histappend"
+
+ # check the window size after each command and, if
+ # necessary, update the values of LINES and COLUMNS.
+ "checkwinsize"
+
+ # Extended globbing.
+ "extglob"
+ "globstar"
+
+ # Warn if closing shell with running jobs.
+ "checkjobs"
+ ];
+ description = "Shell options to set.";
+ };
+
+ sessionVariables = mkOption {
+ default = {};
+ type = types.attrs;
+ example = { MAILCHECK = 30; };
+ description = ''
+ Environment variables that will be set for the Bash session.
+ '';
+ };
+
+ shellAliases = mkOption {
+ default = {};
+ type = types.attrsOf types.str;
+ example = literalExample ''
+ {
+ ll = "ls -l";
+ ".." = "cd ..";
+ }
+ '';
+ description = ''
+ An attribute set that maps aliases (the top level attribute names in
+ this option) to command strings or directly to build outputs.
+ '';
+ };
+
+ profileExtra = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Extra commands that should be run when initializing a login
+ shell.
+ '';
+ };
+
+ bashrcExtra = mkOption {
+ # Hide for now, may want to rename in the future.
+ visible = false;
+ default = "";
+ type = types.lines;
+ description = ''
+ Extra commands that should be added to
+ <filename>~/.bashrc</filename>.
+ '';
+ };
+
+ initExtra = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Extra commands that should be run when initializing an
+ interactive shell.
+ '';
+ };
+
+ logoutExtra = mkOption {
+ default = "";
+ type = types.lines;
+ description = ''
+ Extra commands that should be run when logging out of an
+ interactive shell.
+ '';
+ };
+ };
+ };
+
+ config = (
+ let
+ aliasesStr = concatStringsSep "\n" (
+ mapAttrsToList (k: v: "alias ${k}=${escapeShellArg v}") cfg.shellAliases
+ );
+
+ shoptsStr = concatStringsSep "\n" (
+ map (v: "shopt -s ${v}") cfg.shellOptions
+ );
+
+ sessionVarsStr = config.lib.shell.exportAll cfg.sessionVariables;
+
+ historyControlStr =
+ concatStringsSep "\n" (mapAttrsToList (n: v: "${n}=${v}") (
+ {
+ HISTFILE = "\"${cfg.historyFile}\"";
+ HISTFILESIZE = toString cfg.historyFileSize;
+ HISTSIZE = toString cfg.historySize;
+ }
+ // optionalAttrs (cfg.historyControl != []) {
+ HISTCONTROL = concatStringsSep ":" cfg.historyControl;
+ }
+ // optionalAttrs (cfg.historyIgnore != []) {
+ HISTIGNORE = concatStringsSep ":" cfg.historyIgnore;
+ }
+ ));
+ in mkIf cfg.enable {
+ programs.bash.bashrcExtra = ''
+ # Commands that should be applied only for interactive shells.
+ if [[ $- == *i* ]]; then
+ ${historyControlStr}
+
+ ${shoptsStr}
+
+ ${aliasesStr}
+
+ ${cfg.initExtra}
+ fi
+ '';
+
+ home.file.".bash_profile".text = ''
+ # -*- mode: sh -*-
+
+ # include .profile if it exists
+ [[ -f ~/.profile ]] && . ~/.profile
+
+ # include .bashrc if it exists
+ [[ -f ~/.bashrc ]] && . ~/.bashrc
+ '';
+
+ home.file.".profile".text = ''
+ # -*- mode: sh -*-
+
+ . "${config.home.profileDirectory}/etc/profile.d/hm-session-vars.sh"
+
+ ${sessionVarsStr}
+
+ ${cfg.profileExtra}
+ '';
+
+ home.file.".bashrc".text = ''
+ # -*- mode: sh -*-
+
+ ${cfg.bashrcExtra}
+ '';
+
+ home.file.".bash_logout" = mkIf (cfg.logoutExtra != "") {
+ text = ''
+ # -*- mode: sh -*-
+
+ ${cfg.logoutExtra}
+ '';
+ };
+ }
+ );
+}
diff --git a/infra/libkookie/home-manager/modules/programs/bat.nix b/infra/libkookie/home-manager/modules/programs/bat.nix
new file mode 100644
index 000000000000..e2b30ea93338
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/bat.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.bat;
+
+in {
+ meta.maintainers = [ maintainers.marsam ];
+
+ options.programs.bat = {
+ enable = mkEnableOption "bat, a cat clone with wings";
+
+ config = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ example = {
+ theme = "TwoDark";
+ pager = "less -FR";
+ };
+ description = ''
+ Bat configuration.
+ '';
+ };
+
+ themes = mkOption {
+ type = types.attrsOf types.lines;
+ default = { };
+ example = literalExample ''
+ {
+ dracula = builtins.readFile (pkgs.fetchFromGitHub {
+ owner = "dracula";
+ repo = "sublime"; # Bat uses sublime syntax for its themes
+ rev = "26c57ec282abcaa76e57e055f38432bd827ac34e";
+ sha256 = "019hfl4zbn4vm4154hh3bwk6hm7bdxbr1hdww83nabxwjn99ndhv";
+ } + "/Dracula.tmTheme");
+ }
+ '';
+ description = ''
+ Additional themes to provide.
+ '';
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.bat ];
+
+ xdg.configFile = mkMerge ([{
+ "bat/config" = mkIf (cfg.config != { }) {
+ text = concatStringsSep "\n"
+ (mapAttrsToList (n: v: ''--${n}="${v}"'') cfg.config);
+ };
+ }] ++ flip mapAttrsToList cfg.themes
+ (name: body: { "bat/themes/${name}.tmTheme" = { text = body; }; }));
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/beets.nix b/infra/libkookie/home-manager/modules/programs/beets.nix
new file mode 100644
index 000000000000..6eb183dd1e07
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/beets.nix
@@ -0,0 +1,60 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.beets;
+
+ yamlFormat = pkgs.formats.yaml { };
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ programs.beets = {
+ enable = mkOption {
+ type = types.bool;
+ default = if versionAtLeast config.home.stateVersion "19.03" then
+ false
+ else
+ cfg.settings != { };
+ defaultText = "false";
+ description = ''
+ Whether to enable the beets music library manager. This
+ defaults to <literal>false</literal> for state
+ version ≥ 19.03. For earlier versions beets is enabled if
+ <option>programs.beets.settings</option> is non-empty.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.beets;
+ defaultText = literalExample "pkgs.beets";
+ example =
+ literalExample "(pkgs.beets.override { enableCheck = true; })";
+ description = ''
+ The <literal>beets</literal> package to use.
+ Can be used to specify extensions.
+ '';
+ };
+
+ settings = mkOption {
+ type = yamlFormat.type;
+ default = { };
+ description = ''
+ Configuration written to
+ <filename>~/.config/beets/config.yaml</filename>
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+
+ xdg.configFile."beets/config.yaml".source =
+ yamlFormat.generate "beets-config" cfg.settings;
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/broot.nix b/infra/libkookie/home-manager/modules/programs/broot.nix
new file mode 100644
index 000000000000..e711e8ae255f
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/broot.nix
@@ -0,0 +1,274 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.broot;
+
+ configFile = config:
+ pkgs.runCommand "conf.toml" {
+ buildInputs = [ pkgs.remarshal ];
+ preferLocalBuild = true;
+ allowSubstitutes = false;
+ } ''
+ remarshal -if json -of toml \
+ < ${pkgs.writeText "verbs.json" (builtins.toJSON config)} \
+ > $out
+ '';
+
+ brootConf = {
+ verbs = cfg.verbs;
+ skin = cfg.skin;
+ };
+
+in {
+ meta.maintainers = [ maintainers.aheaume ];
+
+ options.programs.broot = {
+ enable = mkEnableOption "Broot, a better way to navigate directories";
+
+ enableBashIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Bash integration.
+ '';
+ };
+
+ enableZshIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Zsh integration.
+ '';
+ };
+
+ enableFishIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Fish integration.
+ '';
+ };
+
+ verbs = mkOption {
+ type = with types; listOf (attrsOf (either bool str));
+ default = [
+ {
+ invocation = "p";
+ execution = ":parent";
+ }
+ {
+ invocation = "edit";
+ shortcut = "e";
+ execution = "$EDITOR {file}";
+ }
+ {
+ invocation = "create {subpath}";
+ execution = "$EDITOR {directory}/{subpath}";
+ }
+ {
+ invocation = "view";
+ execution = "less {file}";
+ }
+ ];
+ example = literalExample ''
+ [
+ { invocation = "p"; execution = ":parent"; }
+ { invocation = "edit"; shortcut = "e"; execution = "$EDITOR {file}" ; }
+ { invocation = "create {subpath}"; execution = "$EDITOR {directory}/{subpath}"; }
+ { invocation = "view"; execution = "less {file}"; }
+ {
+ invocation = "blop {name}\\.{type}";
+ execution = "/bin/mkdir {parent}/{type} && /usr/bin/nvim {parent}/{type}/{name}.{type}";
+ from_shell = true;
+ }
+ ]
+ '';
+ description = ''
+ Define new verbs. For more information, see
+ <link xlink:href="https://dystroy.org/broot/documentation/configuration/#verb-definition-attributes"/>.
+ </para><para>
+ The possible attributes are:
+ </para>
+
+ <para>
+ <variablelist>
+ <varlistentry>
+ <term><literal>invocation</literal> (optional)</term>
+ <listitem><para>how the verb is called by the user, with placeholders for arguments</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>execution</literal> (mandatory)</term>
+ <listitem><para>how the verb is executed</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>key</literal> (optional)</term>
+ <listitem><para>a keyboard key triggering execution</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>shortcut</literal> (optional)</term>
+ <listitem><para>an alternate way to call the verb (without
+ the arguments part)</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>leave_broot</literal> (optional)</term>
+ <listitem><para>whether to quit broot on execution
+ (default: <literal>true</literal>)</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>from_shell</literal> (optional)</term>
+ <listitem><para>whether the verb must be executed from the
+ parent shell (default:
+ <literal>false</literal>)</para></listitem>
+ </varlistentry>
+ </variablelist>
+ '';
+ };
+
+ skin = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ example = literalExample ''
+ {
+ status_normal_fg = "grayscale(18)";
+ status_normal_bg = "grayscale(3)";
+ status_error_fg = "red";
+ status_error_bg = "yellow";
+ tree_fg = "red";
+ selected_line_bg = "grayscale(7)";
+ permissions_fg = "grayscale(12)";
+ size_bar_full_bg = "red";
+ size_bar_void_bg = "black";
+ directory_fg = "lightyellow";
+ input_fg = "cyan";
+ flag_value_fg = "lightyellow";
+ table_border_fg = "red";
+ code_fg = "lightyellow";
+ }
+ '';
+ description = ''
+ Color configuration.
+ </para><para>
+ Complete list of keys (expected to change before the v1 of broot):
+
+ <itemizedlist>
+ <listitem><para><literal>char_match</literal></para></listitem>
+ <listitem><para><literal>code</literal></para></listitem>
+ <listitem><para><literal>directory</literal></para></listitem>
+ <listitem><para><literal>exe</literal></para></listitem>
+ <listitem><para><literal>file</literal></para></listitem>
+ <listitem><para><literal>file_error</literal></para></listitem>
+ <listitem><para><literal>flag_label</literal></para></listitem>
+ <listitem><para><literal>flag_value</literal></para></listitem>
+ <listitem><para><literal>input</literal></para></listitem>
+ <listitem><para><literal>link</literal></para></listitem>
+ <listitem><para><literal>permissions</literal></para></listitem>
+ <listitem><para><literal>selected_line</literal></para></listitem>
+ <listitem><para><literal>size_bar_full</literal></para></listitem>
+ <listitem><para><literal>size_bar_void</literal></para></listitem>
+ <listitem><para><literal>size_text</literal></para></listitem>
+ <listitem><para><literal>spinner</literal></para></listitem>
+ <listitem><para><literal>status_error</literal></para></listitem>
+ <listitem><para><literal>status_normal</literal></para></listitem>
+ <listitem><para><literal>table_border</literal></para></listitem>
+ <listitem><para><literal>tree</literal></para></listitem>
+ <listitem><para><literal>unlisted</literal></para></listitem>
+ </itemizedlist></para>
+
+ <para>
+ Add <literal>_fg</literal> for a foreground color and
+ <literal>_bg</literal> for a background colors.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.broot ];
+
+ xdg.configFile."broot/conf.toml".source = configFile brootConf;
+
+ # Dummy file to prevent broot from trying to reinstall itself
+ xdg.configFile."broot/launcher/installed-v1".text = "";
+
+ programs.bash.initExtra = mkIf cfg.enableBashIntegration (
+ # Using mkAfter to make it more likely to appear after other
+ # manipulations of the prompt.
+ mkAfter ''
+ # This script was automatically generated by the broot function
+ # More information can be found in https://github.com/Canop/broot
+ # This function starts broot and executes the command
+ # it produces, if any.
+ # It's needed because some shell commands, like `cd`,
+ # have no useful effect if executed in a subshell.
+ function br {
+ f=$(mktemp)
+ (
+ set +e
+ broot --outcmd "$f" "$@"
+ code=$?
+ if [ "$code" != 0 ]; then
+ rm -f "$f"
+ exit "$code"
+ fi
+ )
+ code=$?
+ if [ "$code" != 0 ]; then
+ return "$code"
+ fi
+ d=$(cat "$f")
+ rm -f "$f"
+ eval "$d"
+ }
+ '');
+
+ programs.zsh.initExtra = mkIf cfg.enableZshIntegration ''
+ # This script was automatically generated by the broot function
+ # More information can be found in https://github.com/Canop/broot
+ # This function starts broot and executes the command
+ # it produces, if any.
+ # It's needed because some shell commands, like `cd`,
+ # have no useful effect if executed in a subshell.
+ function br {
+ f=$(mktemp)
+ (
+ set +e
+ broot --outcmd "$f" "$@"
+ code=$?
+ if [ "$code" != 0 ]; then
+ rm -f "$f"
+ exit "$code"
+ fi
+ )
+ code=$?
+ if [ "$code" != 0 ]; then
+ return "$code"
+ fi
+ d=$(cat "$f")
+ rm -f "$f"
+ eval "$d"
+ }
+ '';
+
+ programs.fish.shellInit = mkIf cfg.enableFishIntegration ''
+ # This script was automatically generated by the broot function
+ # More information can be found in https://github.com/Canop/broot
+ # This function starts broot and executes the command
+ # it produces, if any.
+ # It's needed because some shell commands, like `cd`,
+ # have no useful effect if executed in a subshell.
+ function br
+ set f (mktemp)
+ broot --outcmd $f $argv
+ if test $status -ne 0
+ rm -f "$f"
+ return "$code"
+ end
+ set d (cat "$f")
+ rm -f "$f"
+ eval "$d"
+ end
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/browserpass.nix b/infra/libkookie/home-manager/modules/programs/browserpass.nix
new file mode 100644
index 000000000000..10a2883c8714
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/browserpass.nix
@@ -0,0 +1,76 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let browsers = [ "chrome" "chromium" "firefox" "vivaldi" ];
+in {
+ options = {
+ programs.browserpass = {
+ enable = mkEnableOption "the browserpass extension host application";
+
+ browsers = mkOption {
+ type = types.listOf (types.enum browsers);
+ default = browsers;
+ example = [ "firefox" ];
+ description = "Which browsers to install browserpass for";
+ };
+ };
+ };
+
+ config = mkIf config.programs.browserpass.enable {
+ home.file = foldl' (a: b: a // b) { } (concatMap (x:
+ with pkgs.stdenv;
+ if x == "chrome" then
+ let
+ dir = if isDarwin then
+ "Library/Application Support/Google/Chrome/NativeMessagingHosts"
+ else
+ ".config/google-chrome/NativeMessagingHosts";
+ in [{
+ "${dir}/com.github.browserpass.native.json".source =
+ "${pkgs.browserpass}/lib/browserpass/hosts/chromium/com.github.browserpass.native.json";
+ "${dir}/../policies/managed/com.github.browserpass.native.json".source =
+ "${pkgs.browserpass}/lib/browserpass/policies/chromium/com.github.browserpass.native.json";
+ }]
+ else if x == "chromium" then
+ let
+ dir = if isDarwin then
+ "Library/Application Support/Chromium/NativeMessagingHosts"
+ else
+ ".config/chromium/NativeMessagingHosts";
+ in [
+ {
+ "${dir}/com.github.browserpass.native.json".source =
+ "${pkgs.browserpass}/lib/browserpass/hosts/chromium/com.github.browserpass.native.json";
+ }
+ {
+ "${dir}/../policies/managed/com.github.browserpass.native.json".source =
+ "${pkgs.browserpass}/lib/browserpass/policies/chromium/com.github.browserpass.native.json";
+ }
+ ]
+ else if x == "firefox" then
+ let
+ dir = if isDarwin then
+ "Library/Application Support/Mozilla/NativeMessagingHosts"
+ else
+ ".mozilla/native-messaging-hosts";
+ in [{
+ "${dir}/com.github.browserpass.native.json".source =
+ "${pkgs.browserpass}/lib/browserpass/hosts/firefox/com.github.browserpass.native.json";
+ }]
+ else if x == "vivaldi" then
+ let
+ dir = if isDarwin then
+ "Library/Application Support/Vivaldi/NativeMessagingHosts"
+ else
+ ".config/vivaldi/NativeMessagingHosts";
+ in [{
+ "${dir}/com.github.browserpass.native.json".source =
+ "${pkgs.browserpass}/lib/browserpass/hosts/chromium/com.github.browserpass.native.json";
+ "${dir}/../policies/managed/com.github.browserpass.native.json".source =
+ "${pkgs.browserpass}/lib/browserpass/policies/chromium/com.github.browserpass.native.json";
+ }]
+ else
+ throw "unknown browser ${x}") config.programs.browserpass.browsers);
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/chromium.nix b/infra/libkookie/home-manager/modules/programs/chromium.nix
new file mode 100644
index 000000000000..c0017bf708bd
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/chromium.nix
@@ -0,0 +1,95 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ browserModule = defaultPkg: name: visible:
+ let browser = (builtins.parseDrvName defaultPkg.name).name;
+ in {
+ enable = mkOption {
+ inherit visible;
+ default = false;
+ example = true;
+ description = "Whether to enable ${name}.";
+ type = lib.types.bool;
+ };
+
+ package = mkOption {
+ inherit visible;
+ type = types.package;
+ default = defaultPkg;
+ defaultText = literalExample "pkgs.${browser}";
+ description = "The ${name} package to use.";
+ };
+
+ extensions = mkOption {
+ inherit visible;
+ type = types.listOf types.str;
+ default = [ ];
+ example = literalExample ''
+ [
+ "chlffgpmiacpedhhbkiomidkjlcfhogd" # pushbullet
+ "mbniclmhobmnbdlbpiphghaielnnpgdp" # lightshot
+ "gcbommkclmclpchllfjekcdonpmejbdp" # https everywhere
+ "cjpalhdlnbpafiamejdnhcphjbkeiagm" # ublock origin
+ ]
+ '';
+ description = ''
+ List of ${name} extensions to install.
+ To find the extension ID, check its URL on the
+ <link xlink:href="https://chrome.google.com/webstore/category/extensions">Chrome Web Store</link>.
+ '';
+ };
+ };
+
+ browserConfig = cfg:
+ let
+
+ browser = (builtins.parseDrvName cfg.package.name).name;
+
+ darwinDirs = {
+ chromium = "Chromium";
+ google-chrome = "Google/Chrome";
+ google-chrome-beta = "Google/Chrome Beta";
+ google-chrome-dev = "Google/Chrome Dev";
+ brave = "BraveSoftware/Brave-Browser";
+ };
+
+ configDir = if pkgs.stdenv.isDarwin then
+ "Library/Application Support/${getAttr browser darwinDirs}"
+ else
+ "${config.xdg.configHome}/${browser}";
+
+ extensionJson = ext: {
+ name = "${configDir}/External Extensions/${ext}.json";
+ value.text = builtins.toJSON {
+ external_update_url =
+ "https://clients2.google.com/service/update2/crx";
+ };
+ };
+
+ in mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+ home.file = listToAttrs (map extensionJson cfg.extensions);
+ };
+
+in {
+ options.programs = {
+ chromium = browserModule pkgs.chromium "Chromium" true;
+ google-chrome = browserModule pkgs.google-chrome "Google Chrome" false;
+ google-chrome-beta =
+ browserModule pkgs.google-chrome-beta "Google Chrome Beta" false;
+ google-chrome-dev =
+ browserModule pkgs.google-chrome-dev "Google Chrome Dev" false;
+ brave = browserModule pkgs.brave "Brave Browser" false;
+ };
+
+ config = mkMerge [
+ (browserConfig config.programs.chromium)
+ (browserConfig config.programs.google-chrome)
+ (browserConfig config.programs.google-chrome-beta)
+ (browserConfig config.programs.google-chrome-dev)
+ (browserConfig config.programs.brave)
+ ];
+}
diff --git a/infra/libkookie/home-manager/modules/programs/command-not-found/command-not-found.nix b/infra/libkookie/home-manager/modules/programs/command-not-found/command-not-found.nix
new file mode 100644
index 000000000000..b79fde0f6199
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/command-not-found/command-not-found.nix
@@ -0,0 +1,59 @@
+# Adapted from Nixpkgs.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.command-not-found;
+ commandNotFound = pkgs.substituteAll {
+ name = "command-not-found";
+ dir = "bin";
+ src = ./command-not-found.pl;
+ isExecutable = true;
+ inherit (pkgs) perl;
+ inherit (cfg) dbPath;
+ perlFlags = concatStrings (map (path: "-I ${path}/lib/perl5/site_perl ") [
+ pkgs.perlPackages.DBI
+ pkgs.perlPackages.DBDSQLite
+ pkgs.perlPackages.StringShellQuote
+ ]);
+ };
+
+ shInit = commandNotFoundHandlerName: ''
+ # This function is called whenever a command is not found.
+ ${commandNotFoundHandlerName}() {
+ local p=${commandNotFound}/bin/command-not-found
+ if [ -x $p -a -f ${cfg.dbPath} ]; then
+ # Run the helper program.
+ $p "$@"
+ else
+ echo "$1: command not found" >&2
+ return 127
+ fi
+ }
+ '';
+
+in {
+ options.programs.command-not-found = {
+ enable = mkEnableOption "command-not-found hook for interactive shell";
+
+ dbPath = mkOption {
+ default =
+ "/nix/var/nix/profiles/per-user/root/channels/nixos/programs.sqlite";
+ description = ''
+ Absolute path to <filename>programs.sqlite</filename>. By
+ default this file will be provided by your channel
+ (nixexprs.tar.xz).
+ '';
+ type = types.path;
+ };
+ };
+
+ config = mkIf cfg.enable {
+ programs.bash.initExtra = shInit "command_not_found_handle";
+ programs.zsh.initExtra = shInit "command_not_found_handler";
+
+ home.packages = [ commandNotFound ];
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/command-not-found/command-not-found.pl b/infra/libkookie/home-manager/modules/programs/command-not-found/command-not-found.pl
new file mode 100644
index 000000000000..997dfec649b0
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/command-not-found/command-not-found.pl
@@ -0,0 +1,44 @@
+#! @perl@/bin/perl -w @perlFlags@
+
+use strict;
+use DBI;
+use DBD::SQLite;
+use String::ShellQuote;
+use Config;
+
+my $program = $ARGV[0];
+
+my $dbPath = "@dbPath@";
+
+my $dbh = DBI->connect("dbi:SQLite:dbname=$dbPath", "", "")
+ or die "cannot open database `$dbPath'";
+$dbh->{RaiseError} = 0;
+$dbh->{PrintError} = 0;
+
+my $system = $ENV{"NIX_SYSTEM"} // $Config{myarchname};
+
+my $res = $dbh->selectall_arrayref(
+ "select package from Programs where system = ? and name = ?",
+ { Slice => {} }, $system, $program);
+
+if (!defined $res || scalar @$res == 0) {
+ print STDERR "$program: command not found\n";
+} elsif (scalar @$res == 1) {
+ my $package = @$res[0]->{package};
+ if ($ENV{"NIX_AUTO_RUN"} // "") {
+ exec("nix-shell", "-p", $package, "--run", shell_quote("exec", @ARGV));
+ } else {
+ print STDERR <<EOF;
+The program ‘$program’ is currently not installed. You can install it by typing:
+ nix-env -iA nixos.$package
+EOF
+ }
+} else {
+ print STDERR <<EOF;
+The program ‘$program’ is currently not installed. It is provided by
+several packages. You can install it by typing one of the following:
+EOF
+ print STDERR " nix-env -iA nixos.$_->{package}\n" foreach @$res;
+}
+
+exit 127;
diff --git a/infra/libkookie/home-manager/modules/programs/dircolors.nix b/infra/libkookie/home-manager/modules/programs/dircolors.nix
new file mode 100644
index 000000000000..026de72d711b
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/dircolors.nix
@@ -0,0 +1,223 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.dircolors;
+
+ formatLine = n: v: "${n} ${toString v}";
+in {
+ meta.maintainers = [ hm.maintainers.justinlovinger ];
+
+ options.programs.dircolors = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to manage <filename>.dir_colors</filename>
+ and set <code>LS_COLORS</code>.
+ '';
+ };
+
+ enableBashIntegration = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable Bash integration.
+ '';
+ };
+
+ enableFishIntegration = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable Fish integration.
+ '';
+ };
+
+ enableZshIntegration = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable Zsh integration.
+ '';
+ };
+
+ settings = mkOption {
+ type = with types; attrsOf str;
+ default = { };
+ description = ''
+ Options to add to <filename>.dir_colors</filename> file.
+ See <command>dircolors --print-database</command>
+ for options.
+ '';
+ example = literalExample ''
+ {
+ OTHER_WRITABLE = "30;46";
+ ".sh" = "01;32";
+ ".csh" = "01;32";
+ }
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra lines added to <filename>.dir_colors</filename> file.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ # Add default settings from `dircolors --print-database`.
+ programs.dircolors.settings = {
+ RESET = mkDefault "0";
+ DIR = mkDefault "01;34";
+ LINK = mkDefault "01;36";
+ MULTIHARDLINK = mkDefault "00";
+ FIFO = mkDefault "40;33";
+ SOCK = mkDefault "01;35";
+ DOOR = mkDefault "01;35";
+ BLK = mkDefault "40;33;01";
+ CHR = mkDefault "40;33;01";
+ ORPHAN = mkDefault "40;31;01";
+ MISSING = mkDefault "00";
+ SETUID = mkDefault "37;41";
+ SETGID = mkDefault "30;43";
+ CAPABILITY = mkDefault "30;41";
+ STICKY_OTHER_WRITABLE = mkDefault "30;42";
+ OTHER_WRITABLE = mkDefault "34;42";
+ STICKY = mkDefault "37;44";
+ EXEC = mkDefault "01;32";
+ ".tar" = mkDefault "01;31";
+ ".tgz" = mkDefault "01;31";
+ ".arc" = mkDefault "01;31";
+ ".arj" = mkDefault "01;31";
+ ".taz" = mkDefault "01;31";
+ ".lha" = mkDefault "01;31";
+ ".lz4" = mkDefault "01;31";
+ ".lzh" = mkDefault "01;31";
+ ".lzma" = mkDefault "01;31";
+ ".tlz" = mkDefault "01;31";
+ ".txz" = mkDefault "01;31";
+ ".tzo" = mkDefault "01;31";
+ ".t7z" = mkDefault "01;31";
+ ".zip" = mkDefault "01;31";
+ ".z" = mkDefault "01;31";
+ ".dz" = mkDefault "01;31";
+ ".gz" = mkDefault "01;31";
+ ".lrz" = mkDefault "01;31";
+ ".lz" = mkDefault "01;31";
+ ".lzo" = mkDefault "01;31";
+ ".xz" = mkDefault "01;31";
+ ".zst" = mkDefault "01;31";
+ ".tzst" = mkDefault "01;31";
+ ".bz2" = mkDefault "01;31";
+ ".bz" = mkDefault "01;31";
+ ".tbz" = mkDefault "01;31";
+ ".tbz2" = mkDefault "01;31";
+ ".tz" = mkDefault "01;31";
+ ".deb" = mkDefault "01;31";
+ ".rpm" = mkDefault "01;31";
+ ".jar" = mkDefault "01;31";
+ ".war" = mkDefault "01;31";
+ ".ear" = mkDefault "01;31";
+ ".sar" = mkDefault "01;31";
+ ".rar" = mkDefault "01;31";
+ ".alz" = mkDefault "01;31";
+ ".ace" = mkDefault "01;31";
+ ".zoo" = mkDefault "01;31";
+ ".cpio" = mkDefault "01;31";
+ ".7z" = mkDefault "01;31";
+ ".rz" = mkDefault "01;31";
+ ".cab" = mkDefault "01;31";
+ ".wim" = mkDefault "01;31";
+ ".swm" = mkDefault "01;31";
+ ".dwm" = mkDefault "01;31";
+ ".esd" = mkDefault "01;31";
+ ".jpg" = mkDefault "01;35";
+ ".jpeg" = mkDefault "01;35";
+ ".mjpg" = mkDefault "01;35";
+ ".mjpeg" = mkDefault "01;35";
+ ".gif" = mkDefault "01;35";
+ ".bmp" = mkDefault "01;35";
+ ".pbm" = mkDefault "01;35";
+ ".pgm" = mkDefault "01;35";
+ ".ppm" = mkDefault "01;35";
+ ".tga" = mkDefault "01;35";
+ ".xbm" = mkDefault "01;35";
+ ".xpm" = mkDefault "01;35";
+ ".tif" = mkDefault "01;35";
+ ".tiff" = mkDefault "01;35";
+ ".png" = mkDefault "01;35";
+ ".svg" = mkDefault "01;35";
+ ".svgz" = mkDefault "01;35";
+ ".mng" = mkDefault "01;35";
+ ".pcx" = mkDefault "01;35";
+ ".mov" = mkDefault "01;35";
+ ".mpg" = mkDefault "01;35";
+ ".mpeg" = mkDefault "01;35";
+ ".m2v" = mkDefault "01;35";
+ ".mkv" = mkDefault "01;35";
+ ".webm" = mkDefault "01;35";
+ ".ogm" = mkDefault "01;35";
+ ".mp4" = mkDefault "01;35";
+ ".m4v" = mkDefault "01;35";
+ ".mp4v" = mkDefault "01;35";
+ ".vob" = mkDefault "01;35";
+ ".qt" = mkDefault "01;35";
+ ".nuv" = mkDefault "01;35";
+ ".wmv" = mkDefault "01;35";
+ ".asf" = mkDefault "01;35";
+ ".rm" = mkDefault "01;35";
+ ".rmvb" = mkDefault "01;35";
+ ".flc" = mkDefault "01;35";
+ ".avi" = mkDefault "01;35";
+ ".fli" = mkDefault "01;35";
+ ".flv" = mkDefault "01;35";
+ ".gl" = mkDefault "01;35";
+ ".dl" = mkDefault "01;35";
+ ".xcf" = mkDefault "01;35";
+ ".xwd" = mkDefault "01;35";
+ ".yuv" = mkDefault "01;35";
+ ".cgm" = mkDefault "01;35";
+ ".emf" = mkDefault "01;35";
+ ".ogv" = mkDefault "01;35";
+ ".ogx" = mkDefault "01;35";
+ ".aac" = mkDefault "00;36";
+ ".au" = mkDefault "00;36";
+ ".flac" = mkDefault "00;36";
+ ".m4a" = mkDefault "00;36";
+ ".mid" = mkDefault "00;36";
+ ".midi" = mkDefault "00;36";
+ ".mka" = mkDefault "00;36";
+ ".mp3" = mkDefault "00;36";
+ ".mpc" = mkDefault "00;36";
+ ".ogg" = mkDefault "00;36";
+ ".ra" = mkDefault "00;36";
+ ".wav" = mkDefault "00;36";
+ ".oga" = mkDefault "00;36";
+ ".opus" = mkDefault "00;36";
+ ".spx" = mkDefault "00;36";
+ ".xspf" = mkDefault "00;36";
+ };
+
+ home.file.".dir_colors".text = concatStringsSep "\n" ([ ]
+ ++ optional (cfg.extraConfig != "") cfg.extraConfig
+ ++ mapAttrsToList formatLine cfg.settings) + "\n";
+
+ programs.bash.initExtra = mkIf cfg.enableBashIntegration ''
+ eval $(${pkgs.coreutils}/bin/dircolors -b ~/.dir_colors)
+ '';
+
+ programs.fish.shellInit = mkIf cfg.enableFishIntegration ''
+ eval (${pkgs.coreutils}/bin/dircolors -c ~/.dir_colors)
+ '';
+
+ # Set `LS_COLORS` before Oh My Zsh and `initExtra`.
+ programs.zsh.initExtraBeforeCompInit = mkIf cfg.enableZshIntegration ''
+ eval $(${pkgs.coreutils}/bin/dircolors -b ~/.dir_colors)
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/direnv.nix b/infra/libkookie/home-manager/modules/programs/direnv.nix
new file mode 100644
index 000000000000..92a6e6688538
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/direnv.nix
@@ -0,0 +1,100 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.direnv;
+
+ tomlFormat = pkgs.formats.toml { };
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options.programs.direnv = {
+ enable = mkEnableOption "direnv, the environment switcher";
+
+ config = mkOption {
+ type = tomlFormat.type;
+ default = { };
+ description = ''
+ Configuration written to
+ <filename>~/.config/direnv/config.toml</filename>.
+ </para><para>
+ See
+ <citerefentry>
+ <refentrytitle>direnv.toml</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>.
+ for the full list of options.
+ '';
+ };
+
+ stdlib = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Custom stdlib written to
+ <filename>~/.config/direnv/direnvrc</filename>.
+ '';
+ };
+
+ enableBashIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Bash integration.
+ '';
+ };
+
+ enableZshIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Zsh integration.
+ '';
+ };
+
+ enableFishIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Fish integration.
+ '';
+ };
+
+ enableNixDirenvIntegration = mkEnableOption ''
+ <link
+ xlink:href="https://github.com/nix-community/nix-direnv">nix-direnv</link>,
+ a fast, persistent use_nix implementation for direnv'';
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.direnv ];
+
+ xdg.configFile."direnv/config.toml" = mkIf (cfg.config != { }) {
+ source = tomlFormat.generate "direnv-config" cfg.config;
+ };
+
+ xdg.configFile."direnv/direnvrc" = let
+ text = concatStringsSep "\n" (optional (cfg.stdlib != "") cfg.stdlib
+ ++ optional cfg.enableNixDirenvIntegration
+ "source ${pkgs.nix-direnv}/share/nix-direnv/direnvrc");
+ in mkIf (text != "") { inherit text; };
+
+ programs.bash.initExtra = mkIf cfg.enableBashIntegration (
+ # Using mkAfter to make it more likely to appear after other
+ # manipulations of the prompt.
+ mkAfter ''
+ eval "$(${pkgs.direnv}/bin/direnv hook bash)"
+ '');
+
+ programs.zsh.initExtra = mkIf cfg.enableZshIntegration ''
+ eval "$(${pkgs.direnv}/bin/direnv hook zsh)"
+ '';
+
+ programs.fish.shellInit = mkIf cfg.enableFishIntegration ''
+ eval (${pkgs.direnv}/bin/direnv hook fish)
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/eclipse.nix b/infra/libkookie/home-manager/modules/programs/eclipse.nix
new file mode 100644
index 000000000000..21973ab937e6
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/eclipse.nix
@@ -0,0 +1,60 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.eclipse;
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ programs.eclipse = {
+ enable = mkEnableOption "Eclipse";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.eclipses.eclipse-platform;
+ defaultText = literalExample "pkgs.eclipses.eclipse-platform";
+ example = literalExample "pkgs.eclipses.eclipse-java";
+ description = ''
+ The Eclipse package to install.
+ '';
+ };
+
+ enableLombok = mkOption {
+ type = types.bool;
+ default = false;
+ example = true;
+ description = ''
+ Whether to enable the Lombok Java Agent in Eclipse. This is
+ necessary to use the Lombok class annotations.
+ '';
+ };
+
+ jvmArgs = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = "JVM arguments to use for the Eclipse process.";
+ };
+
+ plugins = mkOption {
+ type = types.listOf types.package;
+ default = [ ];
+ description = "Plugins that should be added to Eclipse.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [
+ (pkgs.eclipses.eclipseWithPlugins {
+ eclipse = cfg.package;
+ jvmArgs = cfg.jvmArgs ++ optional cfg.enableLombok
+ "-javaagent:${pkgs.lombok}/share/java/lombok.jar";
+ plugins = cfg.plugins;
+ })
+ ];
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/emacs.nix b/infra/libkookie/home-manager/modules/programs/emacs.nix
new file mode 100644
index 000000000000..b785f71358ce
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/emacs.nix
@@ -0,0 +1,73 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.emacs;
+
+ # Copied from all-packages.nix, with modifications to support
+ # overrides.
+ emacsPackages = let epkgs = pkgs.emacsPackagesFor cfg.package;
+ in epkgs.overrideScope' cfg.overrides;
+
+ emacsWithPackages = emacsPackages.emacsWithPackages;
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ programs.emacs = {
+ enable = mkEnableOption "Emacs";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.emacs;
+ defaultText = literalExample "pkgs.emacs";
+ example = literalExample "pkgs.emacs25-nox";
+ description = "The Emacs package to use.";
+ };
+
+ extraPackages = mkOption {
+ default = self: [ ];
+ type = hm.types.selectorFunction;
+ defaultText = "epkgs: []";
+ example = literalExample "epkgs: [ epkgs.emms epkgs.magit ]";
+ description = ''
+ Extra packages available to Emacs. To get a list of
+ available packages run:
+ <command>nix-env -f '&lt;nixpkgs&gt;' -qaP -A emacsPackages</command>.
+ '';
+ };
+
+ overrides = mkOption {
+ default = self: super: { };
+ type = hm.types.overlayFunction;
+ defaultText = "self: super: {}";
+ example = literalExample ''
+ self: super: rec {
+ haskell-mode = self.melpaPackages.haskell-mode;
+ # ...
+ };
+ '';
+ description = ''
+ Allows overriding packages within the Emacs package set.
+ '';
+ };
+
+ finalPackage = mkOption {
+ type = types.package;
+ visible = false;
+ readOnly = true;
+ description = ''
+ The Emacs package including any overrides and extra packages.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.finalPackage ];
+ programs.emacs.finalPackage = emacsWithPackages cfg.extraPackages;
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/feh.nix b/infra/libkookie/home-manager/modules/programs/feh.nix
new file mode 100644
index 000000000000..e098342b53c7
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/feh.nix
@@ -0,0 +1,81 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.feh;
+
+ bindingsOf = t: with types; attrsOf (nullOr (either t (listOf t)));
+
+ renderBindings = bindings:
+ let
+ enabled = filterAttrs (n: v: v != null) bindings;
+ disabled = filterAttrs (n: v: v == null) bindings;
+ render = mapAttrsToList renderBinding;
+ in concatStringsSep "\n" (render disabled ++ render enabled);
+
+ renderBinding = func: key:
+ if key == null then
+ func
+ else if isList key then
+ concatStringsSep " " ([ func ] ++ map toString key)
+ else
+ "${func} ${toString key}";
+
+in {
+ options.programs.feh = {
+ enable = mkEnableOption "feh - a fast and light image viewer";
+
+ buttons = mkOption {
+ default = { };
+ type = with types; bindingsOf (either str int);
+ example = {
+ zoom_in = 4;
+ zoom_out = "C-4";
+ prev_img = [ 3 "C-3" ];
+ };
+ description = ''
+ Override feh's default mouse button mapping. If you want to disable an
+ action, set its value to null. If you want to bind multiple buttons to
+ an action, set its value to a list.
+ See <link xlink:href="https://man.finalrewind.org/1/feh/#x425554544f4e53"/> for
+ default bindings and available commands.
+ '';
+ };
+
+ keybindings = mkOption {
+ default = { };
+ type = bindingsOf types.str;
+ example = {
+ zoom_in = "plus";
+ zoom_out = "minus";
+ prev_img = [ "h" "Left" ];
+ };
+ description = ''
+ Override feh's default keybindings. If you want to disable a keybinding
+ set its value to null. If you want to bind multiple keys to an action,
+ set its value to a list.
+ See <link xlink:href="https://man.finalrewind.org/1/feh/#x4b455953"/> for
+ default bindings and available commands.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [{
+ assertion = ((filterAttrs (n: v: v == "") cfg.keybindings) == { });
+ message =
+ "To disable a keybinding, use `null` instead of an empty string.";
+ }];
+
+ home.packages = [ pkgs.feh ];
+
+ xdg.configFile."feh/buttons" =
+ mkIf (cfg.buttons != { }) { text = renderBindings cfg.buttons + "\n"; };
+
+ xdg.configFile."feh/keys" = mkIf (cfg.keybindings != { }) {
+ text = renderBindings cfg.keybindings + "\n";
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/firefox.nix b/infra/libkookie/home-manager/modules/programs/firefox.nix
new file mode 100644
index 000000000000..eafeef47f312
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/firefox.nix
@@ -0,0 +1,331 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ inherit (pkgs.stdenv.hostPlatform) isDarwin;
+
+ cfg = config.programs.firefox;
+
+ mozillaConfigPath =
+ if isDarwin
+ then "Library/Application Support/Mozilla"
+ else ".mozilla";
+
+ firefoxConfigPath =
+ if isDarwin
+ then "Library/Application Support/Firefox"
+ else "${mozillaConfigPath}/firefox";
+
+ profilesPath =
+ if isDarwin
+ then "${firefoxConfigPath}/Profiles"
+ else firefoxConfigPath;
+
+ # The extensions path shared by all profiles; will not be supported
+ # by future Firefox versions.
+ extensionPath = "extensions/{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
+
+ extensionsEnvPkg = pkgs.buildEnv {
+ name = "hm-firefox-extensions";
+ paths = cfg.extensions;
+ };
+
+ profiles =
+ flip mapAttrs' cfg.profiles (_: profile:
+ nameValuePair "Profile${toString profile.id}" {
+ Name = profile.name;
+ Path =
+ if isDarwin
+ then "Profiles/${profile.path}"
+ else profile.path;
+ IsRelative = 1;
+ Default = if profile.isDefault then 1 else 0;
+ }
+ ) // {
+ General = {
+ StartWithLastProfile = 1;
+ };
+ };
+
+ profilesIni = generators.toINI {} profiles;
+
+ mkUserJs = prefs: extraPrefs: ''
+ // Generated by Home Manager.
+
+ ${concatStrings (mapAttrsToList (name: value: ''
+ user_pref("${name}", ${builtins.toJSON value});
+ '') prefs)}
+
+ ${extraPrefs}
+ '';
+
+in
+
+{
+ meta.maintainers = [ maintainers.rycee ];
+
+ imports = [
+ (mkRemovedOptionModule ["programs" "firefox" "enableGoogleTalk"]
+ "Support for this option has been removed.")
+ (mkRemovedOptionModule ["programs" "firefox" "enableIcedTea"]
+ "Support for this option has been removed.")
+ ];
+
+ options = {
+ programs.firefox = {
+ enable = mkEnableOption "Firefox";
+
+ package = mkOption {
+ type = types.package;
+ default =
+ if versionAtLeast config.home.stateVersion "19.09"
+ then pkgs.firefox
+ else pkgs.firefox-unwrapped;
+ defaultText = literalExample "pkgs.firefox";
+ description = ''
+ The Firefox package to use. If state version ≥ 19.09 then
+ this should be a wrapped Firefox package. For earlier state
+ versions it should be an unwrapped Firefox package.
+ '';
+ };
+
+ extensions = mkOption {
+ type = types.listOf types.package;
+ default = [];
+ example = literalExample ''
+ with pkgs.nur.repos.rycee.firefox-addons; [
+ https-everywhere
+ privacy-badger
+ ]
+ '';
+ description = ''
+ List of Firefox add-on packages to install. Some
+ pre-packaged add-ons are accessible from NUR,
+ <link xlink:href="https://github.com/nix-community/NUR"/>.
+ Once you have NUR installed run
+
+ <screen language="console">
+ <prompt>$</prompt> <userinput>nix-env -f '&lt;nixpkgs&gt;' -qaP -A nur.repos.rycee.firefox-addons</userinput>
+ </screen>
+
+ to list the available Firefox add-ons.
+
+ </para><para>
+
+ Note that it is necessary to manually enable these
+ extensions inside Firefox after the first installation.
+
+ </para><para>
+
+ Extensions listed here will only be available in Firefox
+ profiles managed through the
+ <link linkend="opt-programs.firefox.profiles">programs.firefox.profiles</link>
+ option. This is due to recent changes in the way Firefox
+ handles extension side-loading.
+ '';
+ };
+
+ profiles = mkOption {
+ type = types.attrsOf (types.submodule ({config, name, ...}: {
+ options = {
+ name = mkOption {
+ type = types.str;
+ default = name;
+ description = "Profile name.";
+ };
+
+ id = mkOption {
+ type = types.ints.unsigned;
+ default = 0;
+ description = ''
+ Profile ID. This should be set to a unique number per profile.
+ '';
+ };
+
+ settings = mkOption {
+ type = with types; attrsOf (either bool (either int str));
+ default = {};
+ example = literalExample ''
+ {
+ "browser.startup.homepage" = "https://nixos.org";
+ "browser.search.region" = "GB";
+ "browser.search.isUS" = false;
+ "distribution.searchplugins.defaultLocale" = "en-GB";
+ "general.useragent.locale" = "en-GB";
+ "browser.bookmarks.showMobileBookmarks" = true;
+ }
+ '';
+ description = "Attribute set of Firefox preferences.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra preferences to add to <filename>user.js</filename>.
+ '';
+ };
+
+ userChrome = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Custom Firefox user chrome CSS.";
+ example = ''
+ /* Hide tab bar in FF Quantum */
+ @-moz-document url("chrome://browser/content/browser.xul") {
+ #TabsToolbar {
+ visibility: collapse !important;
+ margin-bottom: 21px !important;
+ }
+
+ #sidebar-box[sidebarcommand="treestyletab_piro_sakura_ne_jp-sidebar-action"] #sidebar-header {
+ visibility: collapse !important;
+ }
+ }
+ '';
+ };
+
+ userContent = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Custom Firefox user content CSS.";
+ example = ''
+ /* Hide scrollbar in FF Quantum */
+ *{scrollbar-width:none !important}
+ '';
+ };
+
+ path = mkOption {
+ type = types.str;
+ default = name;
+ description = "Profile path.";
+ };
+
+ isDefault = mkOption {
+ type = types.bool;
+ default = config.id == 0;
+ defaultText = "true if profile ID is 0";
+ description = "Whether this is a default profile.";
+ };
+ };
+ }));
+ default = {};
+ description = "Attribute set of Firefox profiles.";
+ };
+
+ enableAdobeFlash = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable the unfree Adobe Flash plugin.";
+ };
+
+ enableGnomeExtensions = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the GNOME Shell native host connector. Note, you
+ also need to set the NixOS option
+ <literal>services.gnome3.chrome-gnome-shell.enable</literal> to
+ <literal>true</literal>.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [
+ (
+ let
+ defaults =
+ catAttrs "name" (filter (a: a.isDefault) (attrValues cfg.profiles));
+ in {
+ assertion = cfg.profiles == {} || length defaults == 1;
+ message =
+ "Must have exactly one default Firefox profile but found "
+ + toString (length defaults)
+ + optionalString (length defaults > 1)
+ (", namely " + concatStringsSep ", " defaults);
+ }
+ )
+
+ (
+ let
+ duplicates =
+ filterAttrs (_: v: length v != 1)
+ (zipAttrs
+ (mapAttrsToList (n: v: { "${toString v.id}" = n; })
+ (cfg.profiles)));
+
+ mkMsg = n: v: " - ID ${n} is used by ${concatStringsSep ", " v}";
+ in {
+ assertion = duplicates == {};
+ message =
+ "Must not have Firefox profiles with duplicate IDs but\n"
+ + concatStringsSep "\n" (mapAttrsToList mkMsg duplicates);
+ }
+ )
+ ];
+
+ home.packages =
+ let
+ # The configuration expected by the Firefox wrapper.
+ fcfg = {
+ enableAdobeFlash = cfg.enableAdobeFlash;
+ enableGnomeExtensions = cfg.enableGnomeExtensions;
+ };
+
+ # A bit of hackery to force a config into the wrapper.
+ browserName = cfg.package.browserName
+ or (builtins.parseDrvName cfg.package.name).name;
+
+ # The configuration expected by the Firefox wrapper builder.
+ bcfg = setAttrByPath [browserName] fcfg;
+
+ package =
+ if isDarwin then
+ cfg.package
+ else if versionAtLeast config.home.stateVersion "19.09" then
+ cfg.package.override { cfg = fcfg; }
+ else
+ (pkgs.wrapFirefox.override { config = bcfg; }) cfg.package { };
+ in
+ [ package ];
+
+ home.file = mkMerge (
+ [{
+ "${mozillaConfigPath}/${extensionPath}" = mkIf (cfg.extensions != []) {
+ source = "${extensionsEnvPkg}/share/mozilla/${extensionPath}";
+ recursive = true;
+ };
+
+ "${firefoxConfigPath}/profiles.ini" = mkIf (cfg.profiles != {}) {
+ text = profilesIni;
+ };
+ }]
+ ++ flip mapAttrsToList cfg.profiles (_: profile: {
+ "${profilesPath}/${profile.path}/chrome/userChrome.css" =
+ mkIf (profile.userChrome != "") {
+ text = profile.userChrome;
+ };
+
+ "${profilesPath}/${profile.path}/chrome/userContent.css" =
+ mkIf (profile.userContent != "") {
+ text = profile.userContent;
+ };
+
+ "${profilesPath}/${profile.path}/user.js" =
+ mkIf (profile.settings != {} || profile.extraConfig != "") {
+ text = mkUserJs profile.settings profile.extraConfig;
+ };
+
+ "${profilesPath}/${profile.path}/extensions" = mkIf (cfg.extensions != []) {
+ source = "${extensionsEnvPkg}/share/mozilla/${extensionPath}";
+ recursive = true;
+ force = true;
+ };
+ })
+ );
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/fish.nix b/infra/libkookie/home-manager/modules/programs/fish.nix
new file mode 100644
index 000000000000..730afa79262c
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/fish.nix
@@ -0,0 +1,460 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.fish;
+
+ pluginModule = types.submodule ({ config, ... }: {
+ options = {
+ src = mkOption {
+ type = types.path;
+ description = ''
+ Path to the plugin folder.
+ </para><para>
+ Relevant pieces will be added to the fish function path and
+ the completion path. The <filename>init.fish</filename> and
+ <filename>key_binding.fish</filename> files are sourced if
+ they exist.
+ '';
+ };
+
+ name = mkOption {
+ type = types.str;
+ description = ''
+ The name of the plugin.
+ '';
+ };
+ };
+ });
+
+ functionModule = types.submodule {
+ options = {
+ body = mkOption {
+ type = types.lines;
+ description = ''
+ The function body.
+ '';
+ };
+
+ argumentNames = mkOption {
+ type = with types; nullOr (either str (listOf str));
+ default = null;
+ description = ''
+ Assigns the value of successive command line arguments to the names
+ given.
+ '';
+ };
+
+ description = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ A description of what the function does, suitable as a completion
+ description.
+ '';
+ };
+
+ wraps = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Causes the function to inherit completions from the given wrapped
+ command.
+ '';
+ };
+
+ onEvent = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Tells fish to run this function when the specified named event is
+ emitted. Fish internally generates named events e.g. when showing the
+ prompt.
+ '';
+ };
+
+ onVariable = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Tells fish to run this function when the specified variable changes
+ value.
+ '';
+ };
+
+ onJobExit = mkOption {
+ type = with types; nullOr (either str int);
+ default = null;
+ description = ''
+ Tells fish to run this function when the job with the specified group
+ ID exits. Instead of a PID, the stringer <literal>caller</literal> can
+ be specified. This is only legal when in a command substitution, and
+ will result in the handler being triggered by the exit of the job
+ which created this command substitution.
+ '';
+ };
+
+ onProcessExit = mkOption {
+ type = with types; nullOr (either str int);
+ default = null;
+ example = "$fish_pid";
+ description = ''
+ Tells fish to run this function when the fish child process with the
+ specified process ID exits. Instead of a PID, for backwards
+ compatibility, <literal>%self</literal> can be specified as an alias
+ for <literal>$fish_pid</literal>, and the function will be run when
+ the current fish instance exits.
+ '';
+ };
+
+ onSignal = mkOption {
+ type = with types; nullOr (either str int);
+ default = null;
+ example = [ "SIGHUP" "HUP" 1 ];
+ description = ''
+ Tells fish to run this function when the specified signal is
+ delievered. The signal can be a signal number or signal name.
+ '';
+ };
+
+ noScopeShadowing = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Allows the function to access the variables of calling functions.
+ '';
+ };
+
+ inheritVariable = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ Snapshots the value of the specified variable and defines a local
+ variable with that same name and value when the function is defined.
+ '';
+ };
+ };
+ };
+
+ abbrsStr = concatStringsSep "\n"
+ (mapAttrsToList (k: v: "abbr --add --global -- ${k} ${escapeShellArg v}")
+ cfg.shellAbbrs);
+
+ aliasesStr = concatStringsSep "\n"
+ (mapAttrsToList (k: v: "alias ${k} ${escapeShellArg v}") cfg.shellAliases);
+
+in {
+ options = {
+ programs.fish = {
+ enable = mkEnableOption "fish, the friendly interactive shell";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.fish;
+ defaultText = literalExample "pkgs.fish";
+ description = ''
+ The fish package to install. May be used to change the version.
+ '';
+ };
+
+ shellAliases = mkOption {
+ type = with types; attrsOf str;
+ default = { };
+ example = literalExample ''
+ {
+ ll = "ls -l";
+ ".." = "cd ..";
+ }
+ '';
+ description = ''
+ An attribute set that maps aliases (the top level attribute names
+ in this option) to command strings or directly to build outputs.
+ '';
+ };
+
+ shellAbbrs = mkOption {
+ type = with types; attrsOf str;
+ default = { };
+ example = {
+ l = "less";
+ gco = "git checkout";
+ };
+ description = ''
+ An attribute set that maps aliases (the top level attribute names
+ in this option) to abbreviations. Abbreviations are expanded with
+ the longer phrase after they are entered.
+ '';
+ };
+
+ shellInit = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Shell script code called during fish shell
+ initialisation.
+ '';
+ };
+
+ loginShellInit = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Shell script code called during fish login shell
+ initialisation.
+ '';
+ };
+
+ interactiveShellInit = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Shell script code called during interactive fish shell
+ initialisation.
+ '';
+ };
+
+ promptInit = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Shell script code used to initialise fish prompt.
+ '';
+ };
+ };
+
+ programs.fish.plugins = mkOption {
+ type = types.listOf pluginModule;
+ default = [ ];
+ example = literalExample ''
+ [
+ {
+ name = "z";
+ src = pkgs.fetchFromGitHub {
+ owner = "jethrokuan";
+ repo = "z";
+ rev = "ddeb28a7b6a1f0ec6dae40c636e5ca4908ad160a";
+ sha256 = "0c5i7sdrsp0q3vbziqzdyqn4fmp235ax4mn4zslrswvn8g3fvdyh";
+ };
+ }
+
+ # oh-my-fish plugins are stored in their own repositories, which
+ # makes them simple to import into home-manager.
+ {
+ name = "fasd";
+ src = pkgs.fetchFromGitHub {
+ owner = "oh-my-fish";
+ repo = "plugin-fasd";
+ rev = "38a5b6b6011106092009549e52249c6d6f501fba";
+ sha256 = "06v37hqy5yrv5a6ssd1p3cjd9y3hnp19d3ab7dag56fs1qmgyhbs";
+ };
+ }
+ ]
+ '';
+ description = ''
+ The plugins to source in
+ <filename>conf.d/99plugins.fish</filename>.
+ '';
+ };
+
+ programs.fish.functions = mkOption {
+ type = with types; attrsOf (either lines functionModule);
+ default = { };
+ example = literalExample ''
+ {
+ __fish_command_not_found_handler = {
+ body = "__fish_default_command_not_found_handler $argv[1]";
+ onEvent = "fish_command_not_found";
+ };
+
+ gitignore = "curl -sL https://www.gitignore.io/api/$argv";
+ }
+ '';
+ description = ''
+ Basic functions to add to fish. For more information see
+ <link xlink:href="https://fishshell.com/docs/current/cmds/function.html"/>.
+ '';
+ };
+
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+ home.packages = [ cfg.package ];
+
+ xdg.dataFile."fish/home-manager_generated_completions".source = let
+ # paths later in the list will overwrite those already linked
+ destructiveSymlinkJoin = args_@{ name, paths, preferLocalBuild ? true
+ , allowSubstitutes ? false, postBuild ? "", ... }:
+ let
+ args = removeAttrs args_ [ "name" "postBuild" ] // {
+ # pass the defaults
+ inherit preferLocalBuild allowSubstitutes;
+ };
+ in pkgs.runCommand name args ''
+ mkdir -p $out
+ for i in $paths; do
+ if [ -z "$(find $i -prune -empty)" ]; then
+ cp -srf $i/* $out
+ fi
+ done
+ ${postBuild}
+ '';
+
+ generateCompletions = package:
+ pkgs.runCommand "${package.name}-fish-completions" {
+ src = package;
+ nativeBuildInputs = [ pkgs.python2 ];
+ buildInputs = [ cfg.package ];
+ preferLocalBuild = true;
+ allowSubstitutes = false;
+ } ''
+ mkdir -p $out
+ if [ -d $src/share/man ]; then
+ find $src/share/man -type f \
+ | xargs python ${cfg.package}/share/fish/tools/create_manpage_completions.py --directory $out \
+ > /dev/null
+ fi
+ '';
+ in destructiveSymlinkJoin {
+ name = "${config.home.username}-fish-completions";
+ paths =
+ let cmp = (a: b: (a.meta.priority or 0) > (b.meta.priority or 0));
+ in map generateCompletions (sort cmp config.home.packages);
+ };
+
+ programs.fish.interactiveShellInit = ''
+ # add completions generated by Home Manager to $fish_complete_path
+ begin
+ set -l joined (string join " " $fish_complete_path)
+ set -l prev_joined (string replace --regex "[^\s]*generated_completions.*" "" $joined)
+ set -l post_joined (string replace $prev_joined "" $joined)
+ set -l prev (string split " " (string trim $prev_joined))
+ set -l post (string split " " (string trim $post_joined))
+ set fish_complete_path $prev "${config.xdg.dataHome}/fish/home-manager_generated_completions" $post
+ end
+ '';
+
+ xdg.configFile."fish/config.fish".text = ''
+ # ~/.config/fish/config.fish: DO NOT EDIT -- this file has been generated
+ # automatically by home-manager.
+
+ # if we haven't sourced the general config, do it
+ if not set -q __fish_general_config_sourced
+
+ set -p fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions
+ fenv source ${config.home.profileDirectory}/etc/profile.d/hm-session-vars.sh > /dev/null
+ set -e fish_function_path[1]
+
+ ${cfg.shellInit}
+ # and leave a note so we don't source this config section again from
+ # this very shell (children will source the general config anew)
+ set -g __fish_general_config_sourced 1
+
+ end
+
+ # if we haven't sourced the login config, do it
+ status --is-login; and not set -q __fish_login_config_sourced
+ and begin
+
+ # Login shell initialisation
+ ${cfg.loginShellInit}
+
+ # and leave a note so we don't source this config section again from
+ # this very shell (children will source the general config anew)
+ set -g __fish_login_config_sourced 1
+
+ end
+
+ # if we haven't sourced the interactive config, do it
+ status --is-interactive; and not set -q __fish_interactive_config_sourced
+ and begin
+
+ # Abbreviations
+ ${abbrsStr}
+
+ # Aliases
+ ${aliasesStr}
+
+ # Prompt initialisation
+ ${cfg.promptInit}
+
+ # Interactive shell intialisation
+ ${cfg.interactiveShellInit}
+
+ # and leave a note so we don't source this config section again from
+ # this very shell (children will source the general config anew,
+ # allowing configuration changes in, e.g, aliases, to propagate)
+ set -g __fish_interactive_config_sourced 1
+
+ end
+ '';
+ }
+ {
+ xdg.configFile = mapAttrs' (name: def: {
+ name = "fish/functions/${name}.fish";
+ value = {
+ text = let
+ modifierStr = n: v: optional (v != null) ''--${n}="${toString v}"'';
+ modifierStrs = n: v: optional (v != null) "--${n}=${toString v}";
+ modifierBool = n: v: optional (v != null && v) "--${n}";
+
+ mods = with def;
+ modifierStr "description" description ++ modifierStr "wraps" wraps
+ ++ modifierStr "on-event" onEvent
+ ++ modifierStr "on-variable" onVariable
+ ++ modifierStr "on-job-exit" onJobExit
+ ++ modifierStr "on-process-exit" onProcessExit
+ ++ modifierStr "on-signal" onSignal
+ ++ modifierBool "no-scope-shadowing" noScopeShadowing
+ ++ modifierStr "inherit-variable" inheritVariable
+ ++ modifierStrs "argument-names" argumentNames;
+
+ modifiers = if isAttrs def then " ${toString mods}" else "";
+ body = if isAttrs def then def.body else def;
+ in ''
+ function ${name}${modifiers}
+ ${body}
+ end
+ '';
+ };
+ }) cfg.functions;
+ }
+
+ # Each plugin gets a corresponding conf.d/plugin-NAME.fish file to load
+ # in the paths and any initialization scripts.
+ (mkIf (length cfg.plugins > 0) {
+ xdg.configFile = mkMerge ((map (plugin: {
+ "fish/conf.d/plugin-${plugin.name}.fish".text = ''
+ # Plugin ${plugin.name}
+ set -l plugin_dir ${plugin.src}
+
+ # Set paths to import plugin components
+ if test -d $plugin_dir/functions
+ set fish_function_path $fish_function_path[1] $plugin_dir/functions $fish_function_path[2..-1]
+ end
+
+ if test -d $plugin_dir/completions
+ set fish_complete_path $fish_complete_path[1] $plugin_dir/completions $fish_complete_path[2..-1]
+ end
+
+ # Source initialization code if it exists.
+ if test -d $plugin_dir/conf.d
+ for f in $plugin_dir/conf.d/*.fish
+ source $f
+ end
+ end
+
+ if test -f $plugin_dir/key_bindings.fish
+ source $plugin_dir/key_bindings.fish
+ end
+
+ if test -f $plugin_dir/init.fish
+ source $plugin_dir/init.fish
+ end
+ '';
+ }) cfg.plugins));
+ })
+ ]);
+}
diff --git a/infra/libkookie/home-manager/modules/programs/fzf.nix b/infra/libkookie/home-manager/modules/programs/fzf.nix
new file mode 100644
index 000000000000..3aee57768ea3
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/fzf.nix
@@ -0,0 +1,146 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.fzf;
+
+in {
+ options.programs.fzf = {
+ enable = mkEnableOption "fzf - a command-line fuzzy finder";
+
+ defaultCommand = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "fd --type f";
+ description = ''
+ The command that gets executed as the default source for fzf
+ when running.
+ '';
+ };
+
+ defaultOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "--height 40%" "--border" ];
+ description = ''
+ Extra command line options given to fzf by default.
+ '';
+ };
+
+ fileWidgetCommand = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "fd --type f";
+ description = ''
+ The command that gets executed as the source for fzf for the
+ CTRL-T keybinding.
+ '';
+ };
+
+ fileWidgetOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "--preview 'head {}'" ];
+ description = ''
+ Command line options for the CTRL-T keybinding.
+ '';
+ };
+
+ changeDirWidgetCommand = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "fd --type d";
+ description = ''
+ The command that gets executed as the source for fzf for the
+ ALT-C keybinding.
+ '';
+ };
+
+ changeDirWidgetOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "--preview 'tree -C {} | head -200'" ];
+ description = ''
+ Command line options for the ALT-C keybinding.
+ '';
+ };
+
+ historyWidgetCommand = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The command that gets executed as the source for fzf for the
+ CTRL-R keybinding.
+ '';
+ };
+
+ historyWidgetOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "--sort" "--exact" ];
+ description = ''
+ Command line options for the CTRL-R keybinding.
+ '';
+ };
+
+ enableBashIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Bash integration.
+ '';
+ };
+
+ enableZshIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Zsh integration.
+ '';
+ };
+
+ enableFishIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Fish integration.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.fzf ];
+
+ home.sessionVariables = mapAttrs (n: v: toString v)
+ (filterAttrs (n: v: v != [ ] && v != null) {
+ FZF_ALT_C_COMMAND = cfg.changeDirWidgetCommand;
+ FZF_ALT_C_OPTS = cfg.changeDirWidgetOptions;
+ FZF_CTRL_R_COMMAND = cfg.historyWidgetCommand;
+ FZF_CTRL_R_OPTS = cfg.historyWidgetOptions;
+ FZF_CTRL_T_COMMAND = cfg.fileWidgetCommand;
+ FZF_CTRL_T_OPTS = cfg.fileWidgetOptions;
+ FZF_DEFAULT_COMMAND = cfg.defaultCommand;
+ FZF_DEFAULT_OPTS = cfg.defaultOptions;
+ });
+
+ programs.bash.initExtra = mkIf cfg.enableBashIntegration ''
+ if [[ :$SHELLOPTS: =~ :(vi|emacs): ]]; then
+ . ${pkgs.fzf}/share/fzf/completion.bash
+ . ${pkgs.fzf}/share/fzf/key-bindings.bash
+ fi
+ '';
+
+ programs.zsh.initExtra = mkIf cfg.enableZshIntegration ''
+ if [[ $options[zle] = on ]]; then
+ . ${pkgs.fzf}/share/fzf/completion.zsh
+ . ${pkgs.fzf}/share/fzf/key-bindings.zsh
+ fi
+ '';
+
+ programs.fish.shellInit = mkIf cfg.enableFishIntegration ''
+ source ${pkgs.fzf}/share/fzf/key-bindings.fish && fzf_key_bindings
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/getmail-accounts.nix b/infra/libkookie/home-manager/modules/programs/getmail-accounts.nix
new file mode 100644
index 000000000000..24eb4fb588a6
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/getmail-accounts.nix
@@ -0,0 +1,49 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+ options.getmail = {
+ enable = mkEnableOption "the getmail mail retriever for this account";
+
+ destinationCommand = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "\${pkgs.maildrop}/bin/maildrop";
+ description = ''
+ Specify a command delivering the incoming mail to your maildir.
+ '';
+ };
+
+ mailboxes = mkOption {
+ type = types.nonEmptyListOf types.str;
+ default = [ ];
+ example = [ "INBOX" "INBOX.spam" ];
+ description = ''
+ A non-empty list of mailboxes. To download all mail you can
+ use the <literal>ALL</literal> mailbox.
+ '';
+ };
+
+ delete = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable if you want to delete read messages from the server. Most
+ users should either enable <literal>delete</literal> or disable
+ <literal>readAll</literal>.
+ '';
+ };
+
+ readAll = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Enable if you want to fetch all, even the read messages from the
+ server. Most users should either enable <literal>delete</literal> or
+ disable <literal>readAll</literal>.
+ '';
+ };
+
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/getmail.nix b/infra/libkookie/home-manager/modules/programs/getmail.nix
new file mode 100644
index 000000000000..f83c469ff245
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/getmail.nix
@@ -0,0 +1,63 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ accounts =
+ filter (a: a.getmail.enable) (attrValues config.accounts.email.accounts);
+
+ renderAccountConfig = account:
+ with account;
+ let
+ passCmd = concatMapStringsSep ", " (x: "'${x}'") passwordCommand;
+ renderedMailboxes =
+ concatMapStringsSep ", " (x: "'${x}'") getmail.mailboxes;
+ retrieverType = if imap.tls.enable then
+ "SimpleIMAPSSLRetriever"
+ else
+ "SimpleIMAPRetriever";
+ destination = if getmail.destinationCommand != null then {
+ destinationType = "MDA_external";
+ destinationPath = getmail.destinationCommand;
+ } else {
+ destinationType = "Maildir";
+ destinationPath = "${maildir.absPath}/";
+ };
+ renderGetmailBoolean = v: if v then "true" else "false";
+ in ''
+ # Generated by Home-Manager.
+ [retriever]
+ type = ${retrieverType}
+ server = ${imap.host}
+ ${optionalString (imap.port != null) "port = ${toString imap.port}"}
+ username = ${userName}
+ password_command = (${passCmd})
+ mailboxes = ( ${renderedMailboxes} )
+
+ [destination]
+ type = ${destination.destinationType}
+ path = ${destination.destinationPath}
+
+ [options]
+ delete = ${renderGetmailBoolean getmail.delete}
+ read_all = ${renderGetmailBoolean getmail.readAll}
+ '';
+ getmailEnabled = length (filter (a: a.getmail.enable) accounts) > 0;
+ # Watch out! This is used by the getmail.service too!
+ renderConfigFilepath = a:
+ ".getmail/getmail${if a.primary then "rc" else a.name}";
+
+in {
+ options = {
+ accounts.email.accounts = mkOption {
+ type = with types; attrsOf (submodule (import ./getmail-accounts.nix));
+ };
+ };
+
+ config = mkIf getmailEnabled {
+ home.file = foldl' (a: b: a // b) { }
+ (map (a: { "${renderConfigFilepath a}".text = renderAccountConfig a; })
+ accounts);
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/gh.nix b/infra/libkookie/home-manager/modules/programs/gh.nix
new file mode 100644
index 000000000000..41d6aa1dec14
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/gh.nix
@@ -0,0 +1,53 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.gh;
+
+in {
+ meta.maintainers = [ maintainers.gerschtli ];
+
+ options.programs.gh = {
+ enable = mkEnableOption "GitHub CLI tool";
+
+ aliases = mkOption {
+ type = with types; attrsOf str;
+ default = { };
+ example = literalExample ''
+ {
+ co = "pr checkout";
+ pv = "pr view";
+ }
+ '';
+ description = ''
+ Aliases that allow you to create nicknames for gh commands.
+ '';
+ };
+
+ editor = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ The editor that gh should run when creating issues, pull requests, etc.
+ If blank, will refer to environment.
+ '';
+ };
+
+ gitProtocol = mkOption {
+ type = types.enum [ "https" "ssh" ];
+ default = "https";
+ description = ''
+ The protocol to use when performing Git operations.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.gitAndTools.gh ];
+
+ xdg.configFile."gh/config.yml".text =
+ builtins.toJSON { inherit (cfg) aliases editor gitProtocol; };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/git.nix b/infra/libkookie/home-manager/modules/programs/git.nix
new file mode 100644
index 000000000000..78346c6abb45
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/git.nix
@@ -0,0 +1,360 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.git;
+
+ # create [section "subsection"] keys from "section.subsection" attrset names
+ mkSectionName = name:
+ let
+ containsQuote = strings.hasInfix ''"'' name;
+ sections = splitString "." name;
+ section = head sections;
+ subsections = tail sections;
+ subsection = concatStringsSep "." subsections;
+ in if containsQuote || subsections == [ ] then
+ name
+ else
+ ''${section} "${subsection}"'';
+
+ mkValueString = v:
+ let
+ escapedV = ''
+ "${
+ replaceStrings [ "\n" " " ''"'' "\\" ] [ "\\n" "\\t" ''\"'' "\\\\" ] v
+ }"'';
+ in generators.mkValueStringDefault { } (if isString v then escapedV else v);
+
+ # generation for multiple ini values
+ mkKeyValue = k: v:
+ let
+ mkKeyValue =
+ generators.mkKeyValueDefault { inherit mkValueString; } " = " k;
+ in concatStringsSep "\n" (map (kv: " " + mkKeyValue kv) (toList v));
+
+ # converts { a.b.c = 5; } to { "a.b".c = 5; } for toINI
+ gitFlattenAttrs = let
+ recurse = path: value:
+ if isAttrs value then
+ mapAttrsToList (name: value: recurse ([ name ] ++ path) value) value
+ else if length path > 1 then {
+ ${concatStringsSep "." (reverseList (tail path))}.${head path} = value;
+ } else {
+ ${head path} = value;
+ };
+ in attrs: foldl recursiveUpdate { } (flatten (recurse [ ] attrs));
+
+ gitToIni = attrs:
+ let toIni = generators.toINI { inherit mkKeyValue mkSectionName; };
+ in toIni (gitFlattenAttrs attrs);
+
+ gitIniType = with types;
+ let
+ primitiveType = either str (either bool int);
+ multipleType = either primitiveType (listOf primitiveType);
+ sectionType = attrsOf multipleType;
+ supersectionType = attrsOf (either multipleType sectionType);
+ in attrsOf supersectionType;
+
+ signModule = types.submodule {
+ options = {
+ key = mkOption {
+ type = types.str;
+ description = "The default GPG signing key fingerprint.";
+ };
+
+ signByDefault = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether commits should be signed by default.";
+ };
+
+ gpgPath = mkOption {
+ type = types.str;
+ default = "${pkgs.gnupg}/bin/gpg2";
+ defaultText = "\${pkgs.gnupg}/bin/gpg2";
+ description = "Path to GnuPG binary to use.";
+ };
+ };
+ };
+
+ includeModule = types.submodule ({ config, ... }: {
+ options = {
+ condition = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Include this configuration only when <varname>condition</varname>
+ matches. Allowed conditions are described in
+ <citerefentry>
+ <refentrytitle>git-config</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>.
+ '';
+ };
+
+ path = mkOption {
+ type = with types; either str path;
+ description = "Path of the configuration file to include.";
+ };
+
+ contents = mkOption {
+ type = types.attrsOf types.anything;
+ default = { };
+ description = ''
+ Configuration to include. If empty then a path must be given.
+ '';
+ };
+ };
+
+ config.path = mkIf (config.contents != { })
+ (mkDefault (pkgs.writeText "contents" (gitToIni config.contents)));
+ });
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ programs.git = {
+ enable = mkEnableOption "Git";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.git;
+ defaultText = literalExample "pkgs.git";
+ description = ''
+ Git package to install. Use <varname>pkgs.gitAndTools.gitFull</varname>
+ to gain access to <command>git send-email</command> for instance.
+ '';
+ };
+
+ userName = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Default user name to use.";
+ };
+
+ userEmail = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Default user email to use.";
+ };
+
+ aliases = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ example = { co = "checkout"; };
+ description = "Git aliases to define.";
+ };
+
+ signing = mkOption {
+ type = types.nullOr signModule;
+ default = null;
+ description = "Options related to signing commits using GnuPG.";
+ };
+
+ extraConfig = mkOption {
+ type = types.either types.lines gitIniType;
+ default = { };
+ example = {
+ core = { whitespace = "trailing-space,space-before-tab"; };
+ url."ssh://git@host".insteadOf = "otherhost";
+ };
+ description = ''
+ Additional configuration to add. The use of string values is
+ deprecated and will be removed in the future.
+ '';
+ };
+
+ iniContent = mkOption {
+ type = gitIniType;
+ internal = true;
+ };
+
+ ignores = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "*~" "*.swp" ];
+ description = "List of paths that should be globally ignored.";
+ };
+
+ attributes = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "*.pdf diff=pdf" ];
+ description = "List of defining attributes set globally.";
+ };
+
+ includes = mkOption {
+ type = types.listOf includeModule;
+ default = [ ];
+ example = literalExample ''
+ [
+ { path = "~/path/to/config.inc"; }
+ {
+ path = "~/path/to/conditional.inc";
+ condition = "gitdir:~/src/dir";
+ }
+ ]
+ '';
+ description = "List of configuration files to include.";
+ };
+
+ lfs = {
+ enable = mkEnableOption "Git Large File Storage";
+
+ skipSmudge = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Skip automatic downloading of objects on clone or pull.
+ This requires a manual <command>git lfs pull</command>
+ every time a new commit is checked out on your repository.
+ '';
+ };
+ };
+
+ delta = {
+ enable = mkEnableOption "" // {
+ description = ''
+ Whether to enable the <command>delta</command> syntax highlighter.
+ See <link xlink:href="https://github.com/dandavison/delta" />.
+ '';
+ };
+
+ options = mkOption {
+ type = with types;
+ let
+ primitiveType = either str (either bool int);
+ sectionType = attrsOf primitiveType;
+ in attrsOf (either primitiveType sectionType);
+ default = { };
+ example = {
+ features = "decorations";
+ whitespace-error-style = "22 reverse";
+ decorations = {
+ commit-decoration-style = "bold yellow box ul";
+ file-style = "bold yellow ul";
+ file-decoration-style = "none";
+ };
+ };
+ description = ''
+ Options to configure delta.
+ '';
+ };
+ };
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+ home.packages = [ cfg.package ];
+
+ programs.git.iniContent.user = {
+ name = mkIf (cfg.userName != null) cfg.userName;
+ email = mkIf (cfg.userEmail != null) cfg.userEmail;
+ };
+
+ xdg.configFile = {
+ "git/config".text = gitToIni cfg.iniContent;
+
+ "git/ignore" = mkIf (cfg.ignores != [ ]) {
+ text = concatStringsSep "\n" cfg.ignores + "\n";
+ };
+
+ "git/attributes" = mkIf (cfg.attributes != [ ]) {
+ text = concatStringsSep "\n" cfg.attributes + "\n";
+ };
+ };
+ }
+
+ {
+ programs.git.iniContent = let
+ hasSmtp = name: account: account.smtp != null;
+
+ genIdentity = name: account:
+ with account;
+ nameValuePair "sendemail.${name}" ({
+ smtpEncryption = if smtp.tls.enable then
+ (if smtp.tls.useStartTls
+ || versionOlder config.home.stateVersion "20.09" then
+ "tls"
+ else
+ "ssl")
+ else
+ "";
+ smtpServer = smtp.host;
+ smtpUser = userName;
+ from = address;
+ } // optionalAttrs (smtp.port != null) {
+ smtpServerPort = smtp.port;
+ });
+ in mapAttrs' genIdentity
+ (filterAttrs hasSmtp config.accounts.email.accounts);
+ }
+
+ (mkIf (cfg.signing != null) {
+ programs.git.iniContent = {
+ user.signingKey = cfg.signing.key;
+ commit.gpgSign = cfg.signing.signByDefault;
+ gpg.program = cfg.signing.gpgPath;
+ };
+ })
+
+ (mkIf (cfg.aliases != { }) { programs.git.iniContent.alias = cfg.aliases; })
+
+ (mkIf (lib.isAttrs cfg.extraConfig) {
+ programs.git.iniContent = cfg.extraConfig;
+ })
+
+ (mkIf (lib.isString cfg.extraConfig) {
+ warnings = [''
+ Using programs.git.extraConfig as a string option is
+ deprecated and will be removed in the future. Please
+ change to using it as an attribute set instead.
+ ''];
+
+ xdg.configFile."git/config".text = cfg.extraConfig;
+ })
+
+ (mkIf (cfg.includes != [ ]) {
+ xdg.configFile."git/config".text = let
+ include = i:
+ with i;
+ if condition != null then {
+ includeIf.${condition}.path = "${path}";
+ } else {
+ include.path = "${path}";
+ };
+ in mkAfter
+ (concatStringsSep "\n" (map gitToIni (map include cfg.includes)));
+ })
+
+ (mkIf cfg.lfs.enable {
+ home.packages = [ pkgs.git-lfs ];
+
+ programs.git.iniContent.filter.lfs =
+ let skipArg = optional cfg.lfs.skipSmudge "--skip";
+ in {
+ clean = "git-lfs clean -- %f";
+ process =
+ concatStringsSep " " ([ "git-lfs" "filter-process" ] ++ skipArg);
+ required = true;
+ smudge = concatStringsSep " "
+ ([ "git-lfs" "smudge" ] ++ skipArg ++ [ "--" "%f" ]);
+ };
+ })
+
+ (mkIf cfg.delta.enable {
+ programs.git.iniContent =
+ let deltaCommand = "${pkgs.gitAndTools.delta}/bin/delta";
+ in {
+ core.pager = deltaCommand;
+ interactive.diffFilter = "${deltaCommand} --color-only";
+ delta = cfg.delta.options;
+ };
+ })
+ ]);
+}
diff --git a/infra/libkookie/home-manager/modules/programs/gnome-terminal.nix b/infra/libkookie/home-manager/modules/programs/gnome-terminal.nix
new file mode 100644
index 000000000000..dec2a10c59e0
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/gnome-terminal.nix
@@ -0,0 +1,332 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.gnome-terminal;
+
+ eraseBinding = types.enum [
+ "auto"
+ "ascii-backspace"
+ "ascii-delete"
+ "delete-sequence"
+ "tty"
+ ];
+
+ backForeSubModule = types.submodule ({ ... }: {
+ options = {
+ foreground = mkOption {
+ type = types.str;
+ description = "The foreground color.";
+ };
+
+ background = mkOption {
+ type = types.str;
+ description = "The background color.";
+ };
+ };
+ });
+
+ profileColorsSubModule = types.submodule ({ ... }: {
+ options = {
+ foregroundColor = mkOption {
+ type = types.str;
+ description = "The foreground color.";
+ };
+
+ backgroundColor = mkOption {
+ type = types.str;
+ description = "The background color.";
+ };
+
+ boldColor = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = "The bold color, null to use same as foreground.";
+ };
+
+ palette = mkOption {
+ type = types.listOf types.str;
+ description = "The terminal palette.";
+ };
+
+ cursor = mkOption {
+ default = null;
+ type = types.nullOr backForeSubModule;
+ description = "The color for the terminal cursor.";
+ };
+
+ highlight = mkOption {
+ default = null;
+ type = types.nullOr backForeSubModule;
+ description = "The colors for the terminal’s highlighted area.";
+ };
+ };
+ });
+
+ profileSubModule = types.submodule ({ name, config, ... }: {
+ options = {
+ default = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Whether this should be the default profile.";
+ };
+
+ visibleName = mkOption {
+ type = types.str;
+ description = "The profile name.";
+ };
+
+ colors = mkOption {
+ default = null;
+ type = types.nullOr profileColorsSubModule;
+ description = "The terminal colors, null to use system default.";
+ };
+
+ cursorBlinkMode = mkOption {
+ default = "system";
+ type = types.enum [ "system" "on" "off" ];
+ description = "The cursor blink mode.";
+ };
+
+ cursorShape = mkOption {
+ default = "block";
+ type = types.enum [ "block" "ibeam" "underline" ];
+ description = "The cursor shape.";
+ };
+
+ font = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = "The font name, null to use system default.";
+ };
+
+ allowBold = mkOption {
+ default = null;
+ type = types.nullOr types.bool;
+ description = ''
+ If <literal>true</literal>, allow applications in the
+ terminal to make text boldface.
+ '';
+ };
+
+ scrollOnOutput = mkOption {
+ default = true;
+ type = types.bool;
+ description = "Whether to scroll when output is written.";
+ };
+
+ showScrollbar = mkOption {
+ default = true;
+ type = types.bool;
+ description = "Whether the scroll bar should be visible.";
+ };
+
+ scrollbackLines = mkOption {
+ default = 10000;
+ type = types.nullOr types.int;
+ description = ''
+ The number of scrollback lines to keep, null for infinite.
+ '';
+ };
+
+ customCommand = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ The command to use to start the shell, or null for default shell.
+ '';
+ };
+
+ loginShell = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Run command as a login shell.";
+ };
+
+ backspaceBinding = mkOption {
+ default = "ascii-delete";
+ type = eraseBinding;
+ description = ''
+ Which string the terminal should send to an application when the user
+ presses the <emphasis>Backspace</emphasis> key.
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>auto</literal></term>
+ <listitem><para>
+ Attempt to determine the right value from the terminal's IO settings.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>ascii-backspace</literal></term>
+ <listitem><para>
+ Send an ASCII backspace character (0x08).
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>ascii-delete</literal></term>
+ <listitem><para>
+ Send an ASCII delete character (0x7F).
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>delete-sequence</literal></term>
+ <listitem><para>
+ Send the <quote>@7</quote> control sequence.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>tty</literal></term>
+ <listitem><para>
+ Send terminal’s <quote>erase</quote> setting.
+ </para></listitem>
+ </varlistentry>
+ </variablelist>
+ '';
+ };
+
+ deleteBinding = mkOption {
+ default = "delete-sequence";
+ type = eraseBinding;
+ description = ''
+ Which string the terminal should send to an application when the user
+ presses the <emphasis>Delete</emphasis> key.
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>auto</literal></term>
+ <listitem><para>
+ Send the <quote>@7</quote> control sequence.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>ascii-backspace</literal></term>
+ <listitem><para>
+ Send an ASCII backspace character (0x08).
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>ascii-delete</literal></term>
+ <listitem><para>
+ Send an ASCII delete character (0x7F).
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>delete-sequence</literal></term>
+ <listitem><para>
+ Send the <quote>@7</quote> control sequence.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>tty</literal></term>
+ <listitem><para>
+ Send terminal’s <quote>erase</quote> setting.
+ </para></listitem>
+ </varlistentry>
+ </variablelist>
+ '';
+ };
+ };
+ });
+
+ buildProfileSet = pcfg:
+ {
+ visible-name = pcfg.visibleName;
+ scrollbar-policy = if pcfg.showScrollbar then "always" else "never";
+ scrollback-lines = pcfg.scrollbackLines;
+ cursor-shape = pcfg.cursorShape;
+ cursor-blink-mode = pcfg.cursorBlinkMode;
+ login-shell = pcfg.loginShell;
+ backspace-binding = pcfg.backspaceBinding;
+ delete-binding = pcfg.deleteBinding;
+ } // (if (pcfg.customCommand != null) then {
+ use-custom-command = true;
+ custom-command = pcfg.customCommand;
+ } else {
+ use-custom-command = false;
+ }) // (if (pcfg.font == null) then {
+ use-system-font = true;
+ } else {
+ use-system-font = false;
+ font = pcfg.font;
+ }) // (if (pcfg.colors == null) then {
+ use-theme-colors = true;
+ } else
+ ({
+ use-theme-colors = false;
+ foreground-color = pcfg.colors.foregroundColor;
+ background-color = pcfg.colors.backgroundColor;
+ palette = pcfg.colors.palette;
+ } // optionalAttrs (pcfg.allowBold != null) {
+ allow-bold = pcfg.allowBold;
+ } // (if (pcfg.colors.boldColor == null) then {
+ bold-color-same-as-fg = true;
+ } else {
+ bold-color-same-as-fg = false;
+ bold-color = pcfg.colors.boldColor;
+ }) // (if (pcfg.colors.cursor != null) then {
+ cursor-colors-set = true;
+ cursor-foreground-color = pcfg.colors.cursor.foreground;
+ cursor-background-color = pcfg.colors.cursor.background;
+ } else {
+ cursor-colors-set = false;
+ }) // (if (pcfg.colors.highlight != null) then {
+ highlight-colors-set = true;
+ highlight-foreground-color = pcfg.colors.highlight.foreground;
+ highlight-background-color = pcfg.colors.highlight.background;
+ } else {
+ highlight-colors-set = false;
+ })));
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ programs.gnome-terminal = {
+ enable = mkEnableOption "Gnome Terminal";
+
+ showMenubar = mkOption {
+ default = true;
+ type = types.bool;
+ description = "Whether to show the menubar by default";
+ };
+
+ themeVariant = mkOption {
+ default = "default";
+ type = types.enum [ "default" "light" "dark" "system" ];
+ description = "The theme variation to request";
+ };
+
+ profile = mkOption {
+ default = { };
+ type = types.attrsOf profileSubModule;
+ description = "A set of Gnome Terminal profiles.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.gnome3.gnome-terminal ];
+
+ dconf.settings = let dconfPath = "org/gnome/terminal/legacy";
+ in {
+ "${dconfPath}" = {
+ default-show-menubar = cfg.showMenubar;
+ theme-variant = cfg.themeVariant;
+ schema-version = 3;
+ };
+
+ "${dconfPath}/profiles:" = {
+ default = head (attrNames (filterAttrs (n: v: v.default) cfg.profile));
+ list = attrNames cfg.profile;
+ };
+ } // mapAttrs'
+ (n: v: nameValuePair ("${dconfPath}/profiles:/:${n}") (buildProfileSet v))
+ cfg.profile;
+
+ programs.bash.enableVteIntegration = true;
+ programs.zsh.enableVteIntegration = true;
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/go.nix b/infra/libkookie/home-manager/modules/programs/go.nix
new file mode 100644
index 000000000000..4b85ec854ad0
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/go.nix
@@ -0,0 +1,105 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.go;
+
+in {
+ meta.maintainers = [ maintainers.rvolosatovs ];
+
+ options = {
+ programs.go = {
+ enable = mkEnableOption "Go";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.go;
+ defaultText = literalExample "pkgs.go";
+ description = "The Go package to use.";
+ };
+
+ packages = mkOption {
+ type = with types; attrsOf path;
+ default = { };
+ example = literalExample ''
+ {
+ "golang.org/x/text" = builtins.fetchGit "https://go.googlesource.com/text";
+ "golang.org/x/time" = builtins.fetchGit "https://go.googlesource.com/time";
+ }
+ '';
+ description = "Packages to add to GOPATH.";
+ };
+
+ goPath = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ example = "go";
+ description = ''
+ Primary <envar>GOPATH</envar> relative to
+ <envar>HOME</envar>. It will be exported first and therefore
+ used by default by the Go tooling.
+ '';
+ };
+
+ extraGoPaths = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "extraGoPath1" "extraGoPath2" ];
+ description = let goPathOpt = "programs.go.goPath";
+ in ''
+ Extra <envar>GOPATH</envar>s relative to <envar>HOME</envar> appended
+ after
+ <varname><link linkend="opt-${goPathOpt}">${goPathOpt}</link></varname>,
+ if that option is set.
+ '';
+ };
+
+ goBin = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ example = ".local/bin.go";
+ description = "GOBIN relative to HOME";
+ };
+
+ goPrivate = mkOption {
+ type = with types; listOf str;
+ default = [ ];
+ example = [ "*.corp.example.com" "rsc.io/private" ];
+ description = ''
+ The <envar>GOPRIVATE</envar> environment variable controls
+ which modules the go command considers to be private (not
+ available publicly) and should therefore not use the proxy
+ or checksum database.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+ home.packages = [ cfg.package ];
+
+ home.file = let
+ goPath = if cfg.goPath != null then cfg.goPath else "go";
+ mkSrc = n: v: { "${goPath}/src/${n}".source = v; };
+ in foldl' (a: b: a // b) { } (mapAttrsToList mkSrc cfg.packages);
+ }
+
+ (mkIf (cfg.goPath != null) {
+ home.sessionVariables.GOPATH = concatStringsSep ":" (map builtins.toPath
+ (map (path: "${config.home.homeDirectory}/${path}")
+ ([ cfg.goPath ] ++ cfg.extraGoPaths)));
+ })
+
+ (mkIf (cfg.goBin != null) {
+ home.sessionVariables.GOBIN =
+ builtins.toPath "${config.home.homeDirectory}/${cfg.goBin}";
+ })
+
+ (mkIf (cfg.goPrivate != [ ]) {
+ home.sessionVariables.GOPRIVATE = concatStringsSep "," cfg.goPrivate;
+ })
+ ]);
+}
diff --git a/infra/libkookie/home-manager/modules/programs/gpg.nix b/infra/libkookie/home-manager/modules/programs/gpg.nix
new file mode 100644
index 000000000000..4588c59c8829
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/gpg.nix
@@ -0,0 +1,61 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.gpg;
+
+ cfgText =
+ concatStringsSep "\n"
+ (attrValues
+ (mapAttrs (key: value:
+ if isString value
+ then "${key} ${value}"
+ else optionalString value key)
+ cfg.settings));
+
+in {
+ options.programs.gpg = {
+ enable = mkEnableOption "GnuPG";
+
+ settings = mkOption {
+ type = types.attrsOf (types.either types.str types.bool);
+ example = {
+ no-comments = false;
+ s2k-cipher-algo = "AES128";
+ };
+ description = ''
+ GnuPG configuration options. Available options are described
+ in the gpg manpage:
+ <link xlink:href="https://gnupg.org/documentation/manpage.html"/>.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ programs.gpg.settings = {
+ personal-cipher-preferences = mkDefault "AES256 AES192 AES";
+ personal-digest-preferences = mkDefault "SHA512 SHA384 SHA256";
+ personal-compress-preferences = mkDefault "ZLIB BZIP2 ZIP Uncompressed";
+ default-preference-list = mkDefault "SHA512 SHA384 SHA256 AES256 AES192 AES ZLIB BZIP2 ZIP Uncompressed";
+ cert-digest-algo = mkDefault "SHA512";
+ s2k-digest-algo = mkDefault "SHA512";
+ s2k-cipher-algo = mkDefault "AES256";
+ charset = mkDefault "utf-8";
+ fixed-list-mode = mkDefault true;
+ no-comments = mkDefault true;
+ no-emit-version = mkDefault true;
+ keyid-format = mkDefault "0xlong";
+ list-options = mkDefault "show-uid-validity";
+ verify-options = mkDefault "show-uid-validity";
+ with-fingerprint = mkDefault true;
+ require-cross-certification = mkDefault true;
+ no-symkey-cache = mkDefault true;
+ use-agent = mkDefault true;
+ };
+
+ home.packages = [ pkgs.gnupg ];
+
+ home.file.".gnupg/gpg.conf".text = cfgText;
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/home-manager.nix b/infra/libkookie/home-manager/modules/programs/home-manager.nix
new file mode 100644
index 000000000000..9039a59d7c57
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/home-manager.nix
@@ -0,0 +1,37 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.home-manager;
+
+ dag = config.lib.dag;
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ programs.home-manager = {
+ enable = mkEnableOption "Home Manager";
+
+ path = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "$HOME/devel/home-manager";
+ description = ''
+ The default path to use for Home Manager. If this path does
+ not exist then
+ <filename>$HOME/.config/nixpkgs/home-manager</filename> and
+ <filename>$HOME/.nixpkgs/home-manager</filename> will be
+ attempted.
+ '';
+ };
+ };
+ };
+
+ config = mkIf (cfg.enable && !config.submoduleSupport.enable) {
+ home.packages =
+ [ (pkgs.callPackage ../../home-manager { inherit (cfg) path; }) ];
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/htop.nix b/infra/libkookie/home-manager/modules/programs/htop.nix
new file mode 100644
index 000000000000..1fb397cdc38d
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/htop.nix
@@ -0,0 +1,416 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.htop;
+
+ list = xs: concatMapStrings (x: "${toString x} ") xs;
+
+ bool = b: if b then "1" else "0";
+
+ fields = {
+ PID = 0;
+ COMM = 1;
+ STATE = 2;
+ PPID = 3;
+ PGRP = 4;
+ SESSION = 5;
+ TTY_NR = 6;
+ TPGID = 7;
+ MINFLT = 9;
+ MAJFLT = 11;
+ PRIORITY = 17;
+ NICE = 18;
+ STARTTIME = 20;
+ PROCESSOR = 37;
+ M_SIZE = 38;
+ M_RESIDENT = 39;
+ ST_UID = 45;
+ PERCENT_CPU = 46;
+ PERCENT_MEM = 47;
+ USER = 48;
+ TIME = 49;
+ NLWP = 50;
+ TGID = 51;
+ CMINFLT = 10;
+ CMAJFLT = 12;
+ UTIME = 13;
+ STIME = 14;
+ CUTIME = 15;
+ CSTIME = 16;
+ M_SHARE = 40;
+ M_TRS = 41;
+ M_DRS = 42;
+ M_LRS = 43;
+ M_DT = 44;
+ CTID = 99;
+ VPID = 100;
+ VXID = 102;
+ RCHAR = 102;
+ WCHAR = 103;
+ SYSCR = 104;
+ SYSCW = 105;
+ RBYTES = 106;
+ WBYTES = 107;
+ CNCLWB = 108;
+ IO_READ_RATE = 109;
+ IO_WRITE_RATE = 110;
+ IO_RATE = 111;
+ CGROUP = 112;
+ OOM = 113;
+ IO_PRIORITY = 114;
+ M_PSS = 118;
+ M_SWAP = 119;
+ M_PSSWP = 120;
+ };
+
+ # Mapping from names to defaults
+ meters = {
+ Clock = 2;
+ LoadAverage = 2;
+ Load = 2;
+ Memory = 1;
+ Swap = 1;
+ Tasks = 2;
+ Uptime = 2;
+ Battery = 2;
+ Hostname = 2;
+ AllCPUs = 1;
+ AllCPUs2 = 1;
+ AllCPUs4 = 1;
+ LeftCPUs = 1;
+ RightCPUs = 1;
+ Right = 1;
+ CPUs = 1;
+ LeftCPUs2 = 1;
+ RightCPUs2 = 1;
+ LeftCPUs4 = 1;
+ RightCPUs4 = 1;
+ Blank = 2;
+ PressureStallCPUSome = 2;
+ PressureStallIOSome = 2;
+ PressureStallIOFull = 2;
+ PressureStallMemorySome = 2;
+ PressureStallMemoryFull = 2;
+ ZFSARC = 2;
+ ZFSCARC = 2;
+ CPU = 1;
+ "CPU(1)" = 1;
+ "CPU(2)" = 1;
+ "CPU(3)" = 1;
+ "CPU(4)" = 1;
+ "CPU(5)" = 1;
+ "CPU(6)" = 1;
+ "CPU(7)" = 1;
+ "CPU(8)" = 1;
+ };
+
+ singleMeterType = let
+ meterEnum = types.enum (attrNames meters);
+ meterSubmodule = types.submodule {
+ options = {
+ kind = mkOption {
+ type = types.enum (attrNames meters);
+ example = "AllCPUs";
+ description = "What kind of meter.";
+ };
+
+ mode = mkOption {
+ type = types.enum [ 1 2 3 4 ];
+ example = 2;
+ description =
+ "Which mode the meter should use, one of 1(Bar) 2(Text) 3(Graph) 4(LED).";
+ };
+ };
+ };
+ in types.coercedTo meterEnum (m: {
+ kind = m;
+ mode = meters.${m};
+ }) meterSubmodule;
+
+ meterType = types.submodule {
+ options = {
+ left = mkOption {
+ description = "Meters shown in the left header.";
+ default = [ "AllCPUs" "Memory" "Swap" ];
+ example = [
+ "Memory"
+ "LeftCPUs2"
+ "RightCPUs2"
+ {
+ kind = "CPU";
+ mode = 3;
+ }
+ ];
+ type = types.listOf singleMeterType;
+ };
+ right = mkOption {
+ description = "Meters shown in the right header.";
+ default = [ "Tasks" "LoadAverage" "Uptime" ];
+ example = [
+ {
+ kind = "Clock";
+ mode = 4;
+ }
+ "Uptime"
+ "Tasks"
+ ];
+ type = types.listOf singleMeterType;
+ };
+ };
+ };
+
+in {
+ options.programs.htop = {
+ enable = mkEnableOption "htop";
+
+ fields = mkOption {
+ type = types.listOf (types.enum (attrNames fields));
+ default = [
+ "PID"
+ "USER"
+ "PRIORITY"
+ "NICE"
+ "M_SIZE"
+ "M_RESIDENT"
+ "M_SHARE"
+ "STATE"
+ "PERCENT_CPU"
+ "PERCENT_MEM"
+ "TIME"
+ "COMM"
+ ];
+ example = [
+ "PID"
+ "USER"
+ "PRIORITY"
+ "PERCENT_CPU"
+ "M_RESIDENT"
+ "PERCENT_MEM"
+ "TIME"
+ "COMM"
+ ];
+ description = "Active fields shown in the table.";
+ };
+
+ sortKey = mkOption {
+ type = types.enum (attrNames fields);
+ default = "PERCENT_CPU";
+ example = "TIME";
+ description = "Which field to use for sorting.";
+ };
+
+ sortDescending = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to sort descending or not.";
+ };
+
+ hideThreads = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Hide threads.";
+ };
+
+ hideKernelThreads = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Hide kernel threads.";
+ };
+
+ hideUserlandThreads = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Hide userland process threads.";
+ };
+
+ shadowOtherUsers = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Shadow other users' processes.";
+ };
+
+ showThreadNames = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Show custom thread names.";
+ };
+
+ showProgramPath = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Show program path.";
+ };
+
+ highlightBaseName = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Highlight program <quote>basename</quote>.";
+ };
+
+ highlightMegabytes = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Highlight large numbers in memory counters.";
+ };
+
+ highlightThreads = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Display threads in a different color.";
+ };
+
+ treeView = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Tree view.";
+ };
+
+ headerMargin = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Leave a margin around header.";
+ };
+
+ detailedCpuTime = mkOption {
+ type = types.bool;
+ default = false;
+ description =
+ "Detailed CPU time (System/IO-Wait/Hard-IRQ/Soft-IRQ/Steal/Guest).";
+ };
+
+ cpuCountFromZero = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Count CPUs from 0 instead of 1.";
+ };
+
+ showCpuUsage = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Show CPU usage frequency.";
+ };
+
+ showCpuFrequency = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Show CPU frequency.";
+ };
+
+ updateProcessNames = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Update process names on every refresh.";
+ };
+
+ accountGuestInCpuMeter = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Add guest time in CPU meter percentage.";
+ };
+
+ colorScheme = mkOption {
+ type = types.enum [ 0 1 2 3 4 5 6 ];
+ default = 0;
+ example = 6;
+ description = "Which color scheme to use.";
+ };
+
+ enableMouse = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Enable mouse support.";
+ };
+
+ delay = mkOption {
+ type = types.int;
+ default = 15;
+ example = 2;
+ description = "Set the delay between updates, in tenths of seconds.";
+ };
+
+ meters = mkOption {
+ description = "Meters shown in the header.";
+ default = {
+ left = [ "AllCPUs" "Memory" "Swap" ];
+ right = [ "Tasks" "LoadAverage" "Uptime" ];
+ };
+ example = {
+ left = [
+ "Memory"
+ "CPU"
+ "LeftCPUs2"
+ "RightCPUs2"
+ {
+ kind = "CPU";
+ mode = 3;
+ }
+ ];
+ right = [
+ {
+ kind = "Clock";
+ mode = 4;
+ }
+ "Uptime"
+ "Tasks"
+ "LoadAverage"
+ {
+ kind = "Battery";
+ mode = 1;
+ }
+ ];
+ };
+ type = meterType;
+ };
+
+ vimMode = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Vim key bindings.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.htop ];
+
+ xdg.configFile."htop/htoprc".text = let
+ leftMeters = map (m: m.kind) cfg.meters.left;
+ leftModes = map (m: m.mode) cfg.meters.left;
+ rightMeters = map (m: m.kind) cfg.meters.right;
+ rightModes = map (m: m.mode) cfg.meters.right;
+ in ''
+ # This file is regenerated by home-manager
+ # when options are changed in the config
+ fields=${list (map (n: fields.${n}) cfg.fields)}
+ sort_key=${toString (fields.${cfg.sortKey})}
+ sort_direction=${bool cfg.sortDescending}
+ hide_threads=${bool cfg.hideThreads}
+ hide_kernel_threads=${bool cfg.hideKernelThreads}
+ hide_userland_threads=${bool cfg.hideUserlandThreads}
+ shadow_other_users=${bool cfg.shadowOtherUsers}
+ show_thread_names=${bool cfg.showThreadNames}
+ show_program_path=${bool cfg.showProgramPath}
+ highlight_base_name=${bool cfg.highlightBaseName}
+ highlight_megabytes=${bool cfg.highlightMegabytes}
+ highlight_threads=${bool cfg.highlightThreads}
+ tree_view=${bool cfg.treeView}
+ header_margin=${bool cfg.headerMargin}
+ detailed_cpu_time=${bool cfg.detailedCpuTime}
+ cpu_count_from_zero=${bool cfg.cpuCountFromZero}
+ show_cpu_usage=${bool cfg.showCpuUsage}
+ show_cpu_frequency=${bool cfg.showCpuFrequency}
+ update_process_names=${bool cfg.updateProcessNames}
+ account_guest_in_cpu_meter=${bool cfg.accountGuestInCpuMeter}
+ color_scheme=${toString cfg.colorScheme}
+ enable_mouse=${bool cfg.enableMouse}
+ delay=${toString cfg.delay}
+ left_meters=${list leftMeters}
+ left_meter_modes=${list leftModes}
+ right_meters=${list rightMeters}
+ right_meter_modes=${list rightModes}
+ vim_mode=${bool cfg.vimMode}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/i3status-rust.nix b/infra/libkookie/home-manager/modules/programs/i3status-rust.nix
new file mode 100644
index 000000000000..5f44c818db05
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/i3status-rust.nix
@@ -0,0 +1,265 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.i3status-rust;
+
+ restartI3 = ''
+ i3Socket=''${XDG_RUNTIME_DIR:-/run/user/$UID}/i3/ipc-socket.*
+ if [ -S $i3Socket ]; then
+ echo "Reloading i3"
+ $DRY_RUN_CMD ${config.xsession.windowManager.i3.package}/bin/i3-msg -s $i3Socket restart 1>/dev/null
+ fi
+ '';
+
+ settingsFormat = pkgs.formats.toml { };
+
+in {
+ meta.maintainers = [ maintainers.farlion ];
+
+ options.programs.i3status-rust = {
+ enable = mkEnableOption "a replacement for i3-status written in Rust";
+
+ bars = mkOption {
+ type = types.attrsOf (types.submodule {
+ options = {
+
+ blocks = mkOption {
+ type = settingsFormat.type;
+ default = [
+ {
+ block = "disk_space";
+ path = "/";
+ alias = "/";
+ info_type = "available";
+ unit = "GB";
+ interval = 60;
+ warning = 20.0;
+ alert = 10.0;
+ }
+ {
+ block = "memory";
+ display_type = "memory";
+ format_mem = "{Mup}%";
+ format_swap = "{SUp}%";
+ }
+ {
+ block = "cpu";
+ interval = 1;
+ }
+ {
+ block = "load";
+ interval = 1;
+ format = "{1m}";
+ }
+ { block = "sound"; }
+ {
+ block = "time";
+ interval = 60;
+ format = "%a %d/%m %R";
+ }
+ ];
+ description = ''
+ Configuration blocks to add to i3status-rust
+ <filename>config</filename>. See
+ <link xlink:href="https://github.com/greshake/i3status-rust/blob/master/blocks.md"/>
+ for block options.
+ '';
+ example = literalExample ''
+ [
+ {
+ block = "disk_space";
+ path = "/";
+ alias = "/";
+ info_type = "available";
+ unit = "GB";
+ interval = 60;
+ warning = 20.0;
+ alert = 10.0;
+ }
+ {
+ block = "sound";
+ format = "{output_name} {volume}%";
+ on_click = "pavucontrol --tab=3";
+ mappings = {
+ "alsa_output.pci-0000_00_1f.3.analog-stereo" = "";
+ "bluez_sink.70_26_05_DA_27_A4.a2dp_sink" = ""
+ };
+ }
+ ];
+ '';
+ };
+
+ settings = mkOption {
+ type = settingsFormat.type;
+ default = { };
+ description = ''
+ Any extra options to add to i3status-rust
+ <filename>config</filename>.
+ '';
+ example = literalExample ''
+ {
+ theme = {
+ name = "solarized-dark";
+ overrides = {
+ idle_bg = "#123456";
+ idle_fg = "#abcdef";
+ };
+ };
+ }
+ '';
+ };
+
+ icons = mkOption {
+ type = types.str;
+ default = "none";
+ description = ''
+ The icons set to use. See
+ <link xlink:href="https://github.com/greshake/i3status-rust/blob/master/themes.md"/>
+ for a list of available icon sets.
+ '';
+ example = "awesome5";
+ };
+
+ theme = mkOption {
+ type = types.str;
+ default = "plain";
+ description = ''
+ The theme to use. See
+ <link xlink:href="https://github.com/greshake/i3status-rust/blob/master/themes.md"/>
+ for a list of available themes.
+ '';
+ example = "gruvbox-dark";
+ };
+ };
+ });
+
+ default = {
+ default = {
+ blocks = [
+ {
+ block = "disk_space";
+ path = "/";
+ alias = "/";
+ info_type = "available";
+ unit = "GB";
+ interval = 60;
+ warning = 20.0;
+ alert = 10.0;
+ }
+ {
+ block = "memory";
+ display_type = "memory";
+ format_mem = "{Mup}%";
+ format_swap = "{SUp}%";
+ }
+ {
+ block = "cpu";
+ interval = 1;
+ }
+ {
+ block = "load";
+ interval = 1;
+ format = "{1m}";
+ }
+ { block = "sound"; }
+ {
+ block = "time";
+ interval = 60;
+ format = "%a %d/%m %R";
+ }
+ ];
+ };
+ };
+ description = ''
+ Attribute set of i3status-rust bars, each with their own configuration.
+ Each bar <varname>name</varname> generates a config file suffixed with
+ the bar's <varname>name</varname> from the attribute set, like so:
+ <filename>config-<replaceable>name</replaceable>.toml</filename>.
+ </para><para>
+ This way, multiple config files can be generated, such as for having a
+ top and a bottom bar.
+ </para><para>
+ See
+ <citerefentry>
+ <refentrytitle>i3status-rust</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ for options.
+ '';
+ example = literalExample ''
+ bottom = {
+ blocks = [
+ {
+ block = "disk_space";
+ path = "/";
+ alias = "/";
+ info_type = "available";
+ unit = "GB";
+ interval = 60;
+ warning = 20.0;
+ alert = 10.0;
+ }
+ {
+ block = "memory";
+ display_type = "memory";
+ format_mem = "{Mup}%";
+ format_swap = "{SUp}%";
+ }
+ {
+ block = "cpu";
+ interval = 1;
+ }
+ {
+ block = "load";
+ interval = 1;
+ format = "{1m}";
+ }
+ { block = "sound"; }
+ {
+ block = "time";
+ interval = 60;
+ format = "%a %d/%m %R";
+ }
+ ];
+ settings = {
+ theme = {
+ name = "solarized-dark";
+ overrides = {
+ idle_bg = "#123456";
+ idle_fg = "#abcdef";
+ };
+ };
+ };
+ icons = "awesome5";
+ theme = "gruvbox-dark";
+ };
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.i3status-rust;
+ defaultText = literalExample "pkgs.i3status-rust";
+ description = "Package providing i3status-rust";
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+
+ xdg.configFile = mapAttrs' (cfgFileSuffix: cfg:
+ nameValuePair ("i3status-rust/config-${cfgFileSuffix}.toml") ({
+ onChange = mkIf config.xsession.windowManager.i3.enable restartI3;
+
+ source = settingsFormat.generate ("config-${cfgFileSuffix}.toml") ({
+ theme = cfg.theme;
+ icons = cfg.icons;
+ block = cfg.blocks;
+ } // cfg.settings);
+ })) cfg.bars;
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/i3status.nix b/infra/libkookie/home-manager/modules/programs/i3status.nix
new file mode 100644
index 000000000000..c1e12fe71d7b
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/i3status.nix
@@ -0,0 +1,208 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.i3status;
+
+ enabledModules = filterAttrs (n: v: v.enable) cfg.modules;
+
+ formatOrder = n: ''order += "${n}"'';
+
+ formatModule = n: v:
+ let
+ formatLine = n: v:
+ let
+ formatValue = v:
+ if isBool v then
+ (if v then "true" else "false")
+ else if isString v then
+ ''"${v}"''
+ else
+ toString v;
+ in "${n} = ${formatValue v}";
+ in ''
+ ${n} {
+ ${concatStringsSep "\n " (mapAttrsToList formatLine v)}
+ }
+ '';
+
+ settingsType = with types; attrsOf (oneOf [ bool int str ]);
+
+ sortAttrNamesByPosition = comparator: set:
+ let pos = n: set."${n}".position;
+ in sort (a: b: comparator (pos a) (pos b)) (attrNames set);
+in {
+ meta.maintainers = [ hm.maintainers.justinlovinger ];
+
+ options.programs.i3status = {
+ enable = mkEnableOption "i3status";
+
+ enableDefault = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether or not to enable
+ the default configuration.
+ '';
+ };
+
+ general = mkOption {
+ type = settingsType;
+ default = { };
+ description = ''
+ Configuration to add to i3status <filename>config</filename>
+ <code>general</code> section.
+ See
+ <citerefentry>
+ <refentrytitle>i3status</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ for options.
+ '';
+ example = literalExample ''
+ {
+ colors = true;
+ color_good = "#e0e0e0";
+ color_degraded = "#d7ae00";
+ color_bad = "#f69d6a";
+ interval = 1;
+ }
+ '';
+ };
+
+ modules = mkOption {
+ type = types.attrsOf (types.submodule {
+ options = {
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether or not to enable this module.
+ '';
+ };
+ position = mkOption {
+ type = with types; either int float;
+ description = ''
+ Position of this module in i3status <code>order</code>.
+ '';
+ };
+ settings = mkOption {
+ type = settingsType;
+ default = { };
+ description = ''
+ Configuration to add to this i3status module.
+ See
+ <citerefentry>
+ <refentrytitle>i3status</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ for options.
+ '';
+ example = literalExample ''
+ {
+ format = "♪ %volume";
+ format_muted = "♪ muted (%volume)";
+ device = "pulse:1";
+ }
+ '';
+ };
+ };
+ });
+ default = { };
+ description = ''
+ Modules to add to i3status <filename>config</filename> file.
+ See
+ <citerefentry>
+ <refentrytitle>i3status</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ for options.
+ '';
+ example = literalExample ''
+ {
+ "volume master" = {
+ position = 1;
+ settings = {
+ format = "♪ %volume";
+ format_muted = "♪ muted (%volume)";
+ device = "pulse:1";
+ };
+ };
+ "disk /" = {
+ position = 2;
+ settings = {
+ format = "/ %avail";
+ };
+ };
+ }
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ programs.i3status = mkIf cfg.enableDefault {
+ general = {
+ colors = mkDefault true;
+ interval = mkDefault 5;
+ };
+
+ modules = {
+ ipv6 = { position = mkDefault 1; };
+
+ "wireless _first_" = {
+ position = mkDefault 2;
+ settings = {
+ format_up = mkDefault "W: (%quality at %essid) %ip";
+ format_down = mkDefault "W: down";
+ };
+ };
+
+ "ethernet _first_" = {
+ position = mkDefault 3;
+ settings = {
+ format_up = mkDefault "E: %ip (%speed)";
+ format_down = mkDefault "E: down";
+ };
+ };
+
+ "battery all" = {
+ position = mkDefault 4;
+ settings = { format = mkDefault "%status %percentage %remaining"; };
+ };
+
+ "disk /" = {
+ position = mkDefault 5;
+ settings = { format = mkDefault "%avail"; };
+ };
+
+ load = {
+ position = mkDefault 6;
+ settings = { format = mkDefault "%1min"; };
+ };
+
+ memory = {
+ position = mkDefault 7;
+ settings = {
+ format = mkDefault "%used | %available";
+ threshold_degraded = mkDefault "1G";
+ format_degraded = mkDefault "MEMORY < %available";
+ };
+ };
+
+ "tztime local" = {
+ position = mkDefault 8;
+ settings = { format = mkDefault "%Y-%m-%d %H:%M:%S"; };
+ };
+ };
+ };
+
+ home.packages = [ pkgs.i3status ];
+
+ xdg.configFile."i3status/config".text = concatStringsSep "\n" ([ ]
+ ++ optional (cfg.general != { }) (formatModule "general" cfg.general)
+ ++ map formatOrder (sortAttrNamesByPosition lessThan enabledModules)
+ ++ mapAttrsToList formatModule
+ (mapAttrs (n: v: v.settings) enabledModules));
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/info.nix b/infra/libkookie/home-manager/modules/programs/info.nix
new file mode 100644
index 000000000000..a7d2692b5155
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/info.nix
@@ -0,0 +1,63 @@
+# info.nix -- install texinfo and create `dir` file
+
+# This is a helper for the GNU info documentation system. By default,
+# the `info` command (and the Info subsystem within Emacs) gives easy
+# access to the info files stored system-wide, but not info files in
+# your ~/.nix-profile.
+
+# Specifically, although info can then find files when you explicitly
+# ask for them, it doesn't show them to you in the table of contents
+# on startup. To do that requires a `dir` file. NixOS keeps the
+# system-wide `dir` file up to date, but ignores files installed in
+# user profiles.
+
+# This module contains extra profile commands that generate the `dir`
+# for your home profile. Then when you start info (and both `dir`
+# files are in your $INFOPATH), it will *merge* the contents of the
+# two files, showing you a unified table of contents for all packages.
+# This is really nice.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.info;
+
+ # Installs this package -- the interactive just means that it
+ # includes the curses `info` program. We also use `install-info`
+ # from this package in the activation script.
+ infoPkg = pkgs.texinfoInteractive;
+
+in {
+ imports = [
+ (mkRemovedOptionModule [ "programs" "info" "homeInfoDirLocation" ] ''
+ The `dir` file is now generated as part of the Home Manager profile and
+ will no longer be placed in your home directory.
+ '')
+ ];
+
+ options.programs.info.enable = mkEnableOption "GNU Info";
+
+ config = mkIf cfg.enable {
+ home.packages = [
+ infoPkg
+
+ # Make sure the target directory is a real directory.
+ (pkgs.runCommandLocal "dummy-info-dir1" { } "mkdir -p $out/share/info")
+ (pkgs.runCommandLocal "dummy-info-dir2" { } "mkdir -p $out/share/info")
+ ];
+
+ home.extraOutputsToInstall = [ "info" ];
+
+ home.extraProfileCommands = let infoPath = "$out/share/info";
+ in ''
+ if [[ -w "${infoPath}" && ! -e "${infoPath}/dir" ]]; then
+ PATH="${lib.makeBinPath [ pkgs.gzip infoPkg ]}''${PATH:+:}$PATH" \
+ find -L "${infoPath}" \( -name '*.info' -o -name '*.info.gz' \) \
+ -exec install-info '{}' "${infoPath}/dir" ';'
+ fi
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/irssi.nix b/infra/libkookie/home-manager/modules/programs/irssi.nix
new file mode 100644
index 000000000000..fc8fa8e6132c
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/irssi.nix
@@ -0,0 +1,211 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.irssi;
+
+ boolStr = b: if b then "yes" else "no";
+ quoteStr = s: escape ["\""] s;
+
+ assignFormat = set:
+ concatStringsSep "\n"
+ (mapAttrsToList (k: v: " ${k} = \"${quoteStr v}\";") set);
+
+ chatnetString =
+ concatStringsSep "\n"
+ (flip mapAttrsToList cfg.networks
+ (k: v: ''
+ ${k} = {
+ type = "${v.type}";
+ nick = "${quoteStr v.nick}";
+ autosendcmd = "${concatMapStringsSep ";" quoteStr v.autoCommands}";
+ };
+ ''));
+
+ serversString =
+ concatStringsSep ",\n"
+ (flip mapAttrsToList cfg.networks
+ (k: v: ''
+ {
+ chatnet = "${k}";
+ address = "${v.server.address}";
+ port = "${toString v.server.port}";
+ use_ssl = "${boolStr v.server.ssl.enable}";
+ ssl_verify = "${boolStr v.server.ssl.verify}";
+ autoconnect = "${boolStr v.server.autoConnect}";
+ }
+ ''));
+
+ channelString =
+ concatStringsSep ",\n"
+ (flip mapAttrsToList cfg.networks
+ (k: v:
+ concatStringsSep ",\n"
+ (flip mapAttrsToList v.channels
+ (c: cv: ''
+ {
+ chatnet = "${k}";
+ name = "${c}";
+ autojoin = "${boolStr cv.autoJoin}";
+ }
+ ''))));
+
+ channelType = types.submodule {
+ options = {
+ name = mkOption {
+ type = types.nullOr types.str;
+ visible = false;
+ default = null;
+ description = "Name of the channel.";
+ };
+
+ autoJoin = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to join this channel on connect.";
+ };
+ };
+ };
+
+ networkType = types.submodule ({ name, ...}: {
+ options = {
+ name = mkOption {
+ visible = false;
+ default = name;
+ type = types.str;
+ };
+
+ nick = mkOption {
+ type = types.str;
+ description = "Nickname in that network.";
+ };
+
+ type = mkOption {
+ type = types.str;
+ description = "Type of the network.";
+ default = "IRC";
+ };
+
+ autoCommands = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = "List of commands to execute on connect.";
+ };
+
+ server = {
+ address = mkOption {
+ type = types.str;
+ description = "Address of the chat server.";
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = 6667;
+ description = "Port of the chat server.";
+ };
+
+ ssl = {
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether SSL should be used.";
+ };
+
+ verify = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether the SSL certificate should be verified.";
+ };
+ };
+
+ autoConnect = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether Irssi connects to the server on launch.";
+ };
+ };
+
+ channels = mkOption {
+ description = "Channels for the given network.";
+ type = types.attrsOf channelType;
+ default = {};
+ };
+ };
+ });
+
+in
+
+{
+
+ options = {
+ programs.irssi = {
+ enable = mkEnableOption "the Irssi chat client";
+
+ extraConfig = mkOption {
+ default = "";
+ description = "These lines are appended to the Irssi configuration.";
+ type = types.str;
+ };
+
+ aliases = mkOption {
+ default = {};
+ example = { J = "join"; BYE = "quit";};
+ description = "An attribute set that maps aliases to commands.";
+ type = types.attrsOf types.str;
+ };
+
+ networks = mkOption {
+ default = {};
+ example = literalExample ''
+ {
+ freenode = {
+ nick = "hmuser";
+ server = {
+ address = "chat.freenode.net";
+ port = 6697;
+ autoConnect = true;
+ };
+ channels = {
+ nixos.autoJoin = true;
+ };
+ };
+ }
+ '';
+ description = "An attribute set of chat networks.";
+ type = types.attrsOf networkType;
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.irssi ];
+
+ home.file.".irssi/config".text = ''
+ settings = {
+ core = {
+ settings_autosave = "no";
+ };
+ };
+
+ aliases = {
+ ${assignFormat cfg.aliases}
+ };
+
+ chatnets = {
+ ${chatnetString}
+ };
+
+ servers = (
+ ${serversString}
+ );
+
+ channels = (
+ ${channelString}
+ );
+
+ ${cfg.extraConfig}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/jq.nix b/infra/libkookie/home-manager/modules/programs/jq.nix
new file mode 100644
index 000000000000..6c89df0df93c
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/jq.nix
@@ -0,0 +1,76 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.jq;
+
+ colorType = mkOption {
+ type = types.str;
+ description = "ANSI color definition";
+ example = "1;31";
+ visible = false;
+ };
+
+ colorsType = types.submodule {
+ options = {
+ null = colorType;
+ false = colorType;
+ true = colorType;
+ numbers = colorType;
+ strings = colorType;
+ arrays = colorType;
+ objects = colorType;
+ };
+ };
+
+in {
+ options = {
+ programs.jq = {
+ enable = mkEnableOption "the jq command-line JSON processor";
+
+ colors = mkOption {
+ description = ''
+ The colors used in colored JSON output.</para>
+
+ <para>See <link xlink:href="https://stedolan.github.io/jq/manual/#Colors"/>.
+ '';
+
+ example = literalExample ''
+ {
+ null = "1;30";
+ false = "0;31";
+ true = "0;32";
+ numbers = "0;36";
+ strings = "0;33";
+ arrays = "1;35";
+ objects = "1;37";
+ }
+ '';
+
+ default = {
+ null = "1;30";
+ false = "0;39";
+ true = "0;39";
+ numbers = "0;39";
+ strings = "0;32";
+ arrays = "1;39";
+ objects = "1;39";
+ };
+
+ type = colorsType;
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.jq ];
+
+ home.sessionVariables = let c = cfg.colors;
+ in {
+ JQ_COLORS =
+ "${c.null}:${c.false}:${c.true}:${c.numbers}:${c.strings}:${c.arrays}:${c.objects}";
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/kakoune.nix b/infra/libkookie/home-manager/modules/programs/kakoune.nix
new file mode 100644
index 000000000000..6db311a13767
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/kakoune.nix
@@ -0,0 +1,659 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.kakoune;
+
+ hook = types.submodule {
+ options = {
+ name = mkOption {
+ type = types.enum [
+ "NormalBegin"
+ "NormalIdle"
+ "NormalEnd"
+ "NormalKey"
+ "InsertBegin"
+ "InsertIdle"
+ "InsertEnd"
+ "InsertKey"
+ "InsertChar"
+ "InsertDelete"
+ "InsertMove"
+ "WinCreate"
+ "WinClose"
+ "WinResize"
+ "WinDisplay"
+ "WinSetOption"
+ "BufSetOption"
+ "BufNewFile"
+ "BufOpenFile"
+ "BufCreate"
+ "BufWritePre"
+ "BufWritePost"
+ "BufReload"
+ "BufClose"
+ "BufOpenFifo"
+ "BufReadFifo"
+ "BufCloseFifo"
+ "RuntimeError"
+ "ModeChange"
+ "PromptIdle"
+ "GlobalSetOption"
+ "KakBegin"
+ "KakEnd"
+ "FocusIn"
+ "FocusOut"
+ "RawKey"
+ "InsertCompletionShow"
+ "InsertCompletionHide"
+ "InsertCompletionSelect"
+ "ModuleLoaded"
+ ];
+ example = "SetOption";
+ description = ''
+ The name of the hook. For a description, see
+ <link xlink:href="https://github.com/mawww/kakoune/blob/master/doc/pages/hooks.asciidoc#default-hooks"/>.
+ '';
+ };
+
+ once = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Remove the hook after running it once.
+ '';
+ };
+
+ group = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Add the hook to the named group.
+ '';
+ };
+
+ option = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "filetype=latex";
+ description = ''
+ Additional option to pass to the hook.
+ '';
+ };
+
+ commands = mkOption {
+ type = types.lines;
+ default = "";
+ example = "set-option window indentwidth 2";
+ description = ''
+ Commands to run when the hook is activated.
+ '';
+ };
+ };
+ };
+
+ keyMapping = types.submodule {
+ options = {
+ mode = mkOption {
+ type = types.str;
+ example = "user";
+ description = ''
+ The mode in which the mapping takes effect.
+ '';
+ };
+
+ docstring = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Optional documentation text to display in info boxes.
+ '';
+ };
+
+ key = mkOption {
+ type = types.str;
+ example = "<a-x>";
+ description = ''
+ The key to be mapped. See
+ <link xlink:href="https://github.com/mawww/kakoune/blob/master/doc/pages/mapping.asciidoc#mappable-keys"/>
+ for possible values.
+ '';
+ };
+
+ effect = mkOption {
+ type = types.str;
+ example = ":wq<ret>";
+ description = ''
+ The sequence of keys to be mapped.
+ '';
+ };
+ };
+ };
+
+ configModule = types.submodule {
+ options = {
+ colorScheme = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Set the color scheme. To see available schemes, enter
+ <command>colorscheme</command> at the kakoune prompt.
+ '';
+ };
+
+ tabStop = mkOption {
+ type = types.nullOr types.ints.unsigned;
+ default = null;
+ description = ''
+ The width of a tab in spaces. The kakoune default is
+ <literal>6</literal>.
+ '';
+ };
+
+ indentWidth = mkOption {
+ type = types.nullOr types.ints.unsigned;
+ default = null;
+ description = ''
+ The width of an indentation in spaces.
+ The kakoune default is <literal>4</literal>.
+ If <literal>0</literal>, a tab will be used instead.
+ '';
+ };
+
+ incrementalSearch = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Execute a search as it is being typed.
+ '';
+ };
+
+ alignWithTabs = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Use tabs for the align command.
+ '';
+ };
+
+ autoInfo = mkOption {
+ type = types.nullOr
+ (types.listOf (types.enum [ "command" "onkey" "normal" ]));
+ default = null;
+ example = [ "command" "normal" ];
+ description = ''
+ Contexts in which to display automatic information box.
+ The kakoune default is <literal>[ "command" "onkey" ]</literal>.
+ '';
+ };
+
+ autoComplete = mkOption {
+ type = types.nullOr (types.listOf (types.enum [ "insert" "prompt" ]));
+ default = null;
+ description = ''
+ Modes in which to display possible completions.
+ The kakoune default is <literal>[ "insert" "prompt" ]</literal>.
+ '';
+ };
+
+ autoReload = mkOption {
+ type = types.nullOr (types.enum [ "yes" "no" "ask" ]);
+ default = null;
+ description = ''
+ Reload buffers when an external modification is detected.
+ The kakoune default is <literal>"ask"</literal>.
+ '';
+ };
+
+ scrollOff = mkOption {
+ type = types.nullOr (types.submodule {
+ options = {
+ lines = mkOption {
+ type = types.ints.unsigned;
+ default = 0;
+ description = ''
+ The number of lines to keep visible around the cursor.
+ '';
+ };
+
+ columns = mkOption {
+ type = types.ints.unsigned;
+ default = 0;
+ description = ''
+ The number of columns to keep visible around the cursor.
+ '';
+ };
+ };
+ });
+ default = null;
+ description = ''
+ How many lines and columns to keep visible around the cursor.
+ '';
+ };
+
+ ui = mkOption {
+ type = types.nullOr (types.submodule {
+ options = {
+ setTitle = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Change the title of the terminal emulator.
+ '';
+ };
+
+ statusLine = mkOption {
+ type = types.enum [ "top" "bottom" ];
+ default = "bottom";
+ description = ''
+ Where to display the status line.
+ '';
+ };
+
+ assistant = mkOption {
+ type = types.enum [ "clippy" "cat" "dilbert" "none" ];
+ default = "clippy";
+ description = ''
+ The assistant displayed in info boxes.
+ '';
+ };
+
+ enableMouse = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable mouse support.
+ '';
+ };
+
+ changeColors = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Change color palette.
+ '';
+ };
+
+ wheelDownButton = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Button to send for wheel down events.
+ '';
+ };
+
+ wheelUpButton = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Button to send for wheel up events.
+ '';
+ };
+
+ shiftFunctionKeys = mkOption {
+ type = types.nullOr types.ints.unsigned;
+ default = null;
+ description = ''
+ Amount by which shifted function keys are offset. That
+ is, if the terminal sends F13 for Shift-F1, this
+ should be <literal>12</literal>.
+ '';
+ };
+
+ useBuiltinKeyParser = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Bypass ncurses key parser and use an internal one.
+ '';
+ };
+ };
+ });
+ default = null;
+ description = ''
+ Settings for the ncurses interface.
+ '';
+ };
+
+ showMatching = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Highlight the matching char of the character under the
+ selections' cursor using the <literal>MatchingChar</literal>
+ face.
+ '';
+ };
+
+ wrapLines = mkOption {
+ type = types.nullOr (types.submodule {
+ options = {
+ enable = mkEnableOption "the wrap lines highlighter";
+
+ word = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Wrap at word boundaries instead of codepoint boundaries.
+ '';
+ };
+
+ indent = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Preserve line indentation when wrapping.
+ '';
+ };
+
+ maxWidth = mkOption {
+ type = types.nullOr types.ints.unsigned;
+ default = null;
+ description = ''
+ Wrap text at maxWidth, even if the window is wider.
+ '';
+ };
+
+ marker = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "⏎";
+ description = ''
+ Prefix wrapped lines with marker text.
+ If not <literal>null</literal>,
+ the marker text will be displayed in the indentation if possible.
+ '';
+ };
+ };
+ });
+ default = null;
+ description = ''
+ Settings for the wrap lines highlighter.
+ '';
+ };
+
+ numberLines = mkOption {
+ type = types.nullOr (types.submodule {
+ options = {
+ enable = mkEnableOption "the number lines highlighter";
+
+ relative = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Show line numbers relative to the main cursor line.
+ '';
+ };
+
+ highlightCursor = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Highlight the cursor line with a separate face.
+ '';
+ };
+
+ separator = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ String that separates the line number column from the
+ buffer contents. The kakoune default is
+ <literal>"|"</literal>.
+ '';
+ };
+ };
+ });
+ default = null;
+ description = ''
+ Settings for the number lines highlighter.
+ '';
+ };
+
+ showWhitespace = mkOption {
+ type = types.nullOr (types.submodule {
+ options = {
+ enable = mkEnableOption "the show whitespace highlighter";
+
+ lineFeed = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The character to display for line feeds.
+ The kakoune default is <literal>"¬"</literal>.
+ '';
+ };
+
+ space = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The character to display for spaces.
+ The kakoune default is <literal>"·"</literal>.
+ '';
+ };
+
+ nonBreakingSpace = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The character to display for non-breaking spaces.
+ The kakoune default is <literal>"⍽"</literal>.
+ '';
+ };
+
+ tab = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The character to display for tabs.
+ The kakoune default is <literal>"→"</literal>.
+ '';
+ };
+
+ tabStop = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ The character to append to tabs to reach the width of a tabstop.
+ The kakoune default is <literal>" "</literal>.
+ '';
+ };
+ };
+ });
+ default = null;
+ description = ''
+ Settings for the show whitespaces highlighter.
+ '';
+ };
+
+ keyMappings = mkOption {
+ type = types.listOf keyMapping;
+ default = [ ];
+ description = ''
+ User-defined key mappings. For documentation, see
+ <link xlink:href="https://github.com/mawww/kakoune/blob/master/doc/pages/mapping.asciidoc"/>.
+ '';
+ };
+
+ hooks = mkOption {
+ type = types.listOf hook;
+ default = [ ];
+ description = ''
+ Global hooks. For documentation, see
+ <link xlink:href="https://github.com/mawww/kakoune/blob/master/doc/pages/hooks.asciidoc"/>.
+ '';
+ };
+ };
+ };
+
+ kakouneWithPlugins = pkgs.wrapKakoune pkgs.kakoune-unwrapped {
+ configure = { plugins = cfg.plugins; };
+ };
+
+ configFile = let
+ wrapOptions = with cfg.config.wrapLines;
+ concatStrings [
+ "${optionalString word " -word"}"
+ "${optionalString indent " -indent"}"
+ "${optionalString (marker != null) " -marker ${marker}"}"
+ "${optionalString (maxWidth != null) " -width ${toString maxWidth}"}"
+ ];
+
+ numberLinesOptions = with cfg.config.numberLines;
+ concatStrings [
+ "${optionalString relative " -relative "}"
+ "${optionalString highlightCursor " -hlcursor"}"
+ "${optionalString (separator != null) " -separator ${separator}"}"
+ ];
+
+ showWhitespaceOptions = with cfg.config.showWhitespace;
+ let
+ quoteSep = sep:
+ if sep == "'" then
+ ''"'"''
+ else if lib.strings.stringLength sep == 1 then
+ "'${sep}'"
+ else
+ sep; # backwards compat, in case sep == "' '", etc.
+
+ in concatStrings [
+ (optionalString (tab != null) " -tab ${quoteSep tab}")
+ (optionalString (tabStop != null) " -tabpad ${quoteSep tabStop}")
+ (optionalString (space != null) " -spc ${quoteSep space}")
+ (optionalString (nonBreakingSpace != null)
+ " -nbsp ${quoteSep nonBreakingSpace}")
+ (optionalString (lineFeed != null) " -lf ${quoteSep lineFeed}")
+ ];
+
+ uiOptions = with cfg.config.ui;
+ concatStringsSep " " [
+ "ncurses_set_title=${if setTitle then "true" else "false"}"
+ "ncurses_status_on_top=${
+ if (statusLine == "top") then "true" else "false"
+ }"
+ "ncurses_assistant=${assistant}"
+ "ncurses_enable_mouse=${if enableMouse then "true" else "false"}"
+ "ncurses_change_colors=${if changeColors then "true" else "false"}"
+ "${optionalString (wheelDownButton != null)
+ "ncurses_wheel_down_button=${wheelDownButton}"}"
+ "${optionalString (wheelUpButton != null)
+ "ncurses_wheel_up_button=${wheelUpButton}"}"
+ "${optionalString (shiftFunctionKeys != null)
+ "ncurses_shift_function_key=${toString shiftFunctionKeys}"}"
+ "ncurses_builtin_key_parser=${
+ if useBuiltinKeyParser then "true" else "false"
+ }"
+ ];
+
+ userModeString = mode:
+ optionalString (!builtins.elem mode [
+ "insert"
+ "normal"
+ "prompt"
+ "menu"
+ "user"
+ "goto"
+ "view"
+ "object"
+ ]) "try %{declare-user-mode ${mode}}";
+
+ userModeStrings = map userModeString
+ (lists.unique (map (km: km.mode) cfg.config.keyMappings));
+
+ keyMappingString = km:
+ concatStringsSep " " [
+ "map global"
+ "${km.mode} ${km.key} '${km.effect}'"
+ "${optionalString (km.docstring != null)
+ "-docstring '${km.docstring}'"}"
+ ];
+
+ hookString = h:
+ concatStringsSep " " [
+ "hook"
+ "${optionalString (h.group != null) "-group ${group}"}"
+ "${optionalString (h.once) "-once"}"
+ "global"
+ "${h.name}"
+ "${optionalString (h.option != null) h.option}"
+ "%{ ${h.commands} }"
+ ];
+
+ cfgStr = with cfg.config;
+ concatStringsSep "\n" ([ "# Generated by home-manager" ]
+ ++ optional (colorScheme != null) "colorscheme ${colorScheme}"
+ ++ optional (tabStop != null)
+ "set-option global tabstop ${toString tabStop}"
+ ++ optional (indentWidth != null)
+ "set-option global indentwidth ${toString indentWidth}"
+ ++ optional (!incrementalSearch) "set-option global incsearch false"
+ ++ optional (alignWithTabs) "set-option global aligntab true"
+ ++ optional (autoInfo != null)
+ "set-option global autoinfo ${concatStringsSep "|" autoInfo}"
+ ++ optional (autoComplete != null)
+ "set-option global autocomplete ${concatStringsSep "|" autoComplete}"
+ ++ optional (autoReload != null)
+ "set-option global autoreload ${autoReload}"
+ ++ optional (wrapLines != null && wrapLines.enable)
+ "add-highlighter global/ wrap${wrapOptions}"
+ ++ optional (numberLines != null && numberLines.enable)
+ "add-highlighter global/ number-lines${numberLinesOptions}"
+ ++ optional showMatching "add-highlighter global/ show-matching"
+ ++ optional (showWhitespace != null && showWhitespace.enable)
+ "add-highlighter global/ show-whitespaces${showWhitespaceOptions}"
+ ++ optional (scrollOff != null)
+ "set-option global scrolloff ${toString scrollOff.lines},${
+ toString scrollOff.columns
+ }"
+
+ ++ [ "# UI options" ]
+ ++ optional (ui != null) "set-option global ui_options ${uiOptions}"
+
+ ++ [ "# User modes" ] ++ userModeStrings ++ [ "# Key mappings" ]
+ ++ map keyMappingString keyMappings
+
+ ++ [ "# Hooks" ] ++ map hookString hooks);
+ in pkgs.writeText "kakrc"
+ (optionalString (cfg.config != null) cfgStr + "\n" + cfg.extraConfig);
+
+in {
+ options = {
+ programs.kakoune = {
+ enable = mkEnableOption "the kakoune text editor";
+
+ config = mkOption {
+ type = types.nullOr configModule;
+ default = { };
+ description = "kakoune configuration options.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration lines to add to
+ <filename>~/.config/kak/kakrc</filename>.
+ '';
+ };
+
+ plugins = mkOption {
+ type = with types; listOf package;
+ default = [ ];
+ example = literalExample "[ pkgs.kakounePlugins.kak-fzf ]";
+ description = ''
+ List of kakoune plugins to install. To get a list of
+ supported plugins run:
+ <command>nix-env -f '&lt;nixpkgs&gt;' -qaP -A kakounePlugins</command>.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ kakouneWithPlugins ];
+ xdg.configFile."kak/kakrc".source = configFile;
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/keychain.nix b/infra/libkookie/home-manager/modules/programs/keychain.nix
new file mode 100644
index 000000000000..6e26bd232cee
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/keychain.nix
@@ -0,0 +1,115 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.keychain;
+
+ flags = cfg.extraFlags ++ optional (cfg.agents != [ ])
+ "--agents ${concatStringsSep "," cfg.agents}"
+ ++ optional (cfg.inheritType != null) "--inherit ${cfg.inheritType}";
+
+ shellCommand =
+ "${cfg.package}/bin/keychain --eval ${concatStringsSep " " flags} ${
+ concatStringsSep " " cfg.keys
+ }";
+
+in {
+ meta.maintainers = [ maintainers.marsam ];
+
+ options.programs.keychain = {
+ enable = mkEnableOption "keychain";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.keychain;
+ defaultText = literalExample "pkgs.keychain";
+ description = ''
+ Keychain package to install.
+ '';
+ };
+
+ keys = mkOption {
+ type = types.listOf types.str;
+ default = [ "id_rsa" ];
+ description = ''
+ Keys to add to keychain.
+ '';
+ };
+
+ agents = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = ''
+ Agents to add.
+ '';
+ };
+
+ inheritType = mkOption {
+ type =
+ types.nullOr (types.enum [ "local" "any" "local-once" "any-once" ]);
+ default = null;
+ description = ''
+ Inherit type to attempt from agent variables from the environment.
+ '';
+ };
+
+ extraFlags = mkOption {
+ type = types.listOf types.str;
+ default = [ "--quiet" ];
+ description = ''
+ Extra flags to pass to keychain.
+ '';
+ };
+
+ enableBashIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Bash integration.
+ '';
+ };
+
+ enableFishIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Fish integration.
+ '';
+ };
+
+ enableZshIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Zsh integration.
+ '';
+ };
+
+ enableXsessionIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ visible = pkgs.stdenv.hostPlatform.isLinux;
+ description = ''
+ Whether to run keychain from your <filename>~/.xsession</filename>.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+ programs.bash.initExtra = mkIf cfg.enableBashIntegration ''
+ eval "$(${shellCommand})"
+ '';
+ programs.fish.interactiveShellInit = mkIf cfg.enableFishIntegration ''
+ eval (${shellCommand})
+ '';
+ programs.zsh.initExtra = mkIf cfg.enableZshIntegration ''
+ eval "$(${shellCommand})"
+ '';
+ xsession.initExtra = mkIf cfg.enableXsessionIntegration ''
+ eval "$(${shellCommand})"
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/kitty.nix b/infra/libkookie/home-manager/modules/programs/kitty.nix
new file mode 100644
index 000000000000..313a0bfadd77
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/kitty.nix
@@ -0,0 +1,91 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.kitty;
+
+ eitherStrBoolInt = with types; either str (either bool int);
+
+ optionalPackage = opt:
+ optional (opt != null && opt.package != null) opt.package;
+
+ toKittyConfig = generators.toKeyValue {
+ mkKeyValue = key: value:
+ let
+ value' = if isBool value then
+ (if value then "yes" else "no")
+ else
+ toString value;
+ in "${key} ${value'}";
+ };
+
+ toKittyKeybindings = generators.toKeyValue {
+ mkKeyValue = key: command: "map ${key} ${command}";
+ };
+
+in {
+ options.programs.kitty = {
+ enable = mkEnableOption "Kitty terminal emulator";
+
+ settings = mkOption {
+ type = types.attrsOf eitherStrBoolInt;
+ default = { };
+ example = literalExample ''
+ {
+ scrollback_lines = 10000;
+ enable_audio_bell = false;
+ update_check_interval = 0;
+ }
+ '';
+ description = ''
+ Configuration written to
+ <filename>~/.config/kitty/kitty.conf</filename>. See
+ <link xlink:href="https://sw.kovidgoyal.net/kitty/conf.html" />
+ for the documentation.
+ '';
+ };
+
+ font = mkOption {
+ type = types.nullOr hm.types.fontType;
+ default = null;
+ description = "The font to use.";
+ };
+
+ keybindings = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ description = "Mapping of keybindings to actions.";
+ example = literalExample ''
+ {
+ "ctrl+c" = "copy_or_interrupt";
+ "ctrl+f>2" = "set_font_size 20";
+ }
+ '';
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = "Additional configuration to add.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.kitty ] ++ optionalPackage cfg.font;
+
+ xdg.configFile."kitty/kitty.conf".text = ''
+ # Generated by Home Manager.
+ # See https://sw.kovidgoyal.net/kitty/conf.html
+
+ ${optionalString (cfg.font != null) "font_family ${cfg.font.name}"}
+
+ ${toKittyConfig cfg.settings}
+
+ ${toKittyKeybindings cfg.keybindings}
+
+ ${cfg.extraConfig}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/lesspipe.nix b/infra/libkookie/home-manager/modules/programs/lesspipe.nix
new file mode 100644
index 000000000000..a7a51ffe2a2c
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/lesspipe.nix
@@ -0,0 +1,19 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ programs.lesspipe = {
+ enable = mkEnableOption "lesspipe preprocessor for less";
+ };
+ };
+
+ config = mkIf config.programs.lesspipe.enable {
+ home.sessionVariables = {
+ LESSOPEN = "|${pkgs.lesspipe}/bin/lesspipe.sh %s";
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/lf.nix b/infra/libkookie/home-manager/modules/programs/lf.nix
new file mode 100644
index 000000000000..ee4e9b5bfce2
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/lf.nix
@@ -0,0 +1,219 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.lf;
+
+ knownSettings = {
+ anchorfind = types.bool;
+ color256 = types.bool;
+ dircounts = types.bool;
+ dirfirst = types.bool;
+ drawbox = types.bool;
+ globsearch = types.bool;
+ icons = types.bool;
+ hidden = types.bool;
+ ignorecase = types.bool;
+ ignoredia = types.bool;
+ incsearch = types.bool;
+ preview = types.bool;
+ reverse = types.bool;
+ smartcase = types.bool;
+ smartdia = types.bool;
+ wrapscan = types.bool;
+ wrapscroll = types.bool;
+ number = types.bool;
+ relativenumber = types.bool;
+ findlen = types.int;
+ period = types.int;
+ scrolloff = types.int;
+ tabstop = types.int;
+ errorfmt = types.str;
+ filesep = types.str;
+ ifs = types.str;
+ promptfmt = types.str;
+ shell = types.str;
+ sortby = types.str;
+ timefmt = types.str;
+ ratios = types.str;
+ info = types.str;
+ shellopts = types.str;
+ };
+
+ lfSettingsType = types.submodule {
+ options = let
+ opt = name: type:
+ mkOption {
+ type = types.nullOr type;
+ default = null;
+ visible = false;
+ };
+ in mapAttrs opt knownSettings;
+ };
+in {
+ meta.maintainers = [ hm.maintainers.owm111 ];
+
+ options = {
+ programs.lf = {
+ enable = mkEnableOption "lf";
+
+ settings = mkOption {
+ type = lfSettingsType;
+ default = { };
+ example = {
+ tabstop = 4;
+ number = true;
+ ratios = "1:1:2";
+ };
+ description = ''
+ An attribute set of lf settings. The attribute names and corresponding
+ values must be among the following supported options.
+
+ <informaltable frame="none"><tgroup cols="1"><tbody>
+ ${concatStringsSep "\n" (mapAttrsToList (n: v: ''
+ <row>
+ <entry><varname>${n}</varname></entry>
+ <entry>${v.description}</entry>
+ </row>
+ '') knownSettings)}
+ </tbody></tgroup></informaltable>
+
+ See the lf documentation for detailed descriptions of these options.
+ Note, use <varname>previewer</varname> to set lf's
+ <varname>previewer</varname> option, and
+ <varname>extraConfig</varname> for any other option not listed above.
+ All string options are quoted with double quotes.
+ '';
+ };
+
+ commands = mkOption {
+ type = with types; attrsOf (nullOr str);
+ default = { };
+ example = {
+ get-mime-type = ''%xdg-mime query filetype "$f"'';
+ open = "$$OPENER $f";
+ };
+ description = ''
+ Commands to declare. Commands set to null or an empty string are
+ deleted.
+ '';
+ };
+
+ keybindings = mkOption {
+ type = with types; attrsOf (nullOr str);
+ default = { };
+ example = {
+ gh = "cd ~";
+ D = "trash";
+ i = "$less $f";
+ U = "!du -sh";
+ gg = null;
+ };
+ description =
+ "Keys to bind. Keys set to null or an empty string are deleted.";
+ };
+
+ cmdKeybindings = mkOption {
+ type = with types; attrsOf (nullOr str);
+ default = { };
+ example = literalExample ''{ "<c-g>" = "cmd-escape"; }'';
+ description = ''
+ Keys to bind to command line commands which can only be one of the
+ builtin commands. Keys set to null or an empty string are deleted.
+ '';
+ };
+
+ previewer.source = mkOption {
+ type = with types; nullOr path;
+ default = null;
+ example = literalExample ''
+ pkgs.writeShellScript "pv.sh" '''
+ #!/bin/sh
+
+ case "$1" in
+ *.tar*) tar tf "$1";;
+ *.zip) unzip -l "$1";;
+ *.rar) unrar l "$1";;
+ *.7z) 7z l "$1";;
+ *.pdf) pdftotext "$1" -;;
+ *) highlight -O ansi "$1" || cat "$1";;
+ esac
+ '''
+ '';
+ description = ''
+ Script or executable to use to preview files. Sets lf's
+ <varname>previewer</varname> option.
+ '';
+ };
+
+ previewer.keybinding = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ example = "i";
+ description = ''
+ Key to bind to the script at <varname>previewer.source</varname> and
+ pipe through less. Setting to null will not bind any key.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ $mkdir -p ~/.trash
+ '';
+ description = "Custom lfrc lines.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.lf ];
+
+ xdg.configFile."lf/lfrc".text = let
+ fmtSetting = k: v:
+ optionalString (v != null) "set ${
+ if isBool v then
+ "${optionalString (!v) "no"}${k}"
+ else
+ "${k} ${if isInt v then toString v else ''"${v}"''}"
+ }";
+
+ settingsStr = concatStringsSep "\n" (filter (x: x != "")
+ (mapAttrsToList fmtSetting
+ (builtins.intersectAttrs knownSettings cfg.settings)));
+
+ fmtCmdMap = before: k: v:
+ "${before} ${k}${optionalString (v != null && v != "") " ${v}"}";
+ fmtCmd = fmtCmdMap "cmd";
+ fmtMap = fmtCmdMap "map";
+ fmtCmap = fmtCmdMap "cmap";
+
+ commandsStr = concatStringsSep "\n" (mapAttrsToList fmtCmd cfg.commands);
+ keybindingsStr =
+ concatStringsSep "\n" (mapAttrsToList fmtMap cfg.keybindings);
+ cmdKeybindingsStr =
+ concatStringsSep "\n" (mapAttrsToList fmtCmap cfg.cmdKeybindings);
+
+ previewerStr = optionalString (cfg.previewer.source != null) ''
+ set previewer ${cfg.previewer.source}
+ ${optionalString (cfg.previewer.keybinding != null) ''
+ map ${cfg.previewer.keybinding} ''$${cfg.previewer.source} "$f" | less -R
+ ''}
+ '';
+ in ''
+ ${settingsStr}
+
+ ${commandsStr}
+
+ ${keybindingsStr}
+
+ ${cmdKeybindingsStr}
+
+ ${previewerStr}
+
+ ${cfg.extraConfig}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/lieer-accounts.nix b/infra/libkookie/home-manager/modules/programs/lieer-accounts.nix
new file mode 100644
index 000000000000..238049065b39
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/lieer-accounts.nix
@@ -0,0 +1,69 @@
+{ lib, ... }:
+
+with lib;
+
+{
+ options.lieer = {
+ enable = mkEnableOption "lieer Gmail synchronization for notmuch";
+
+ timeout = mkOption {
+ type = types.ints.unsigned;
+ default = 0;
+ description = ''
+ HTTP timeout in seconds. 0 means forever or system timeout.
+ '';
+ };
+
+ replaceSlashWithDot = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Replace '/' with '.' in Gmail labels.
+ '';
+ };
+
+ dropNonExistingLabels = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Allow missing labels on the Gmail side to be dropped.
+ '';
+ };
+
+ ignoreTagsLocal = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = ''
+ Set custom tags to ignore when syncing from local to
+ remote (after translations).
+ '';
+ };
+
+ ignoreTagsRemote = mkOption {
+ type = types.listOf types.str;
+ default = [
+ "CATEGORY_FORUMS"
+ "CATEGORY_PROMOTIONS"
+ "CATEGORY_UPDATES"
+ "CATEGORY_SOCIAL"
+ "CATEGORY_PERSONAL"
+ ];
+ description = ''
+ Set custom tags to ignore when syncing from remote to
+ local (before translations).
+ '';
+ };
+
+ notmuchSetupWarning = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Warn if Notmuch is not also enabled for this account.
+ </para><para>
+ This can safely be disabled if <command>notmuch init</command>
+ has been used to configure this account outside of Home
+ Manager.
+ '';
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/lieer.nix b/infra/libkookie/home-manager/modules/programs/lieer.nix
new file mode 100644
index 000000000000..e34a247af464
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/lieer.nix
@@ -0,0 +1,93 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.lieer;
+
+ lieerAccounts =
+ filter (a: a.lieer.enable) (attrValues config.accounts.email.accounts);
+
+ nonGmailAccounts =
+ map (a: a.name) (filter (a: a.flavor != "gmail.com") lieerAccounts);
+
+ nonGmailConfigHelp =
+ map (name: ''accounts.email.accounts.${name}.flavor = "gmail.com";'')
+ nonGmailAccounts;
+
+ missingNotmuchAccounts = map (a: a.name)
+ (filter (a: !a.notmuch.enable && a.lieer.notmuchSetupWarning)
+ lieerAccounts);
+
+ notmuchConfigHelp =
+ map (name: "accounts.email.accounts.${name}.notmuch.enable = true;")
+ missingNotmuchAccounts;
+
+ configFile = account: {
+ name = "${account.maildir.absPath}/.gmailieer.json";
+ value = {
+ text = builtins.toJSON {
+ inherit (account.lieer) timeout;
+ account = account.address;
+ replace_slash_with_dot = account.lieer.replaceSlashWithDot;
+ drop_non_existing_label = account.lieer.dropNonExistingLabels;
+ ignore_tags = account.lieer.ignoreTagsLocal;
+ ignore_remote_labels = account.lieer.ignoreTagsRemote;
+ } + "\n";
+ };
+ };
+
+in {
+ meta.maintainers = [ maintainers.tadfisher ];
+
+ options = {
+ programs.lieer.enable =
+ mkEnableOption "lieer Gmail synchronization for notmuch";
+
+ accounts.email.accounts = mkOption {
+ type = with types; attrsOf (submodule (import ./lieer-accounts.nix));
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ (mkIf (missingNotmuchAccounts != [ ]) {
+ warnings = [''
+ lieer is enabled for the following email accounts, but notmuch is not:
+
+ ${concatStringsSep "\n " missingNotmuchAccounts}
+
+ Notmuch can be enabled with:
+
+ ${concatStringsSep "\n " notmuchConfigHelp}
+
+ If you have configured notmuch outside of Home Manager, you can suppress this
+ warning with:
+
+ programs.lieer.notmuchSetupWarning = false;
+ ''];
+ })
+
+ {
+ assertions = [{
+ assertion = nonGmailAccounts == [ ];
+ message = ''
+ lieer is enabled for non-Gmail accounts:
+
+ ${concatStringsSep "\n " nonGmailAccounts}
+
+ If these accounts are actually Gmail accounts, you can
+ fix this error with:
+
+ ${concatStringsSep "\n " nonGmailConfigHelp}
+ '';
+ }];
+
+ home.packages = [ pkgs.gmailieer ];
+
+ # Notmuch should ignore non-mail files created by lieer.
+ programs.notmuch.new.ignore = [ "/.*[.](json|lock|bak)$/" ];
+
+ home.file = listToAttrs (map configFile lieerAccounts);
+ }
+ ]);
+}
diff --git a/infra/libkookie/home-manager/modules/programs/lsd.nix b/infra/libkookie/home-manager/modules/programs/lsd.nix
new file mode 100644
index 000000000000..ab1880ff8289
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/lsd.nix
@@ -0,0 +1,41 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.lsd;
+
+ aliases = {
+ ls = "${pkgs.lsd}/bin/lsd";
+ ll = "ls -l";
+ la = "ls -a";
+ lt = "ls --tree";
+ lla = "ls -la";
+ };
+
+in {
+ meta.maintainers = [ maintainers.marsam ];
+
+ options.programs.lsd = {
+ enable = mkEnableOption "lsd";
+
+ enableAliases = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to enable recommended lsd aliases.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.lsd ];
+
+ programs.bash.shellAliases = mkIf cfg.enableAliases aliases;
+
+ programs.zsh.shellAliases = mkIf cfg.enableAliases aliases;
+
+ programs.fish.shellAliases = mkIf cfg.enableAliases aliases;
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/man.nix b/infra/libkookie/home-manager/modules/programs/man.nix
new file mode 100644
index 000000000000..b235b02fe2d2
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/man.nix
@@ -0,0 +1,72 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ options = {
+ programs.man = {
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable manual pages and the <command>man</command>
+ command. This also includes "man" outputs of all
+ <literal>home.packages</literal>.
+ '';
+ };
+
+ generateCaches = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to generate the manual page index caches using
+ <citerefentry>
+ <refentrytitle>mandb</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </citerefentry>. This allows searching for a page or
+ keyword using utilities like <citerefentry>
+ <refentrytitle>apropos</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>.
+ </para><para>
+ This feature is disabled by default because it slows down
+ building. If you don't mind waiting a few more seconds when
+ Home Manager builds a new generation, you may safely enable
+ this option.
+ '';
+ };
+ };
+ };
+
+ config = mkIf config.programs.man.enable {
+ home.packages = [ pkgs.man ];
+ home.extraOutputsToInstall = [ "man" ];
+
+ # This is mostly copy/pasted/adapted from NixOS' documentation.nix.
+ home.file = mkIf config.programs.man.generateCaches {
+ ".manpath".text = let
+ # Generate a directory containing installed packages' manpages.
+ manualPages = pkgs.buildEnv {
+ name = "man-paths";
+ paths = config.home.packages;
+ pathsToLink = [ "/share/man" ];
+ extraOutputsToInstall = [ "man" ];
+ ignoreCollisions = true;
+ };
+
+ # Generate a database of all manpages in ${manualPages}.
+ manualCache = pkgs.runCommandLocal "man-cache" { } ''
+ # Generate a temporary man.conf so mandb knows where to
+ # write cache files.
+ echo "MANDB_MAP ${manualPages}/share/man $out" > man.conf
+
+ # Run mandb to generate cache files:
+ ${pkgs.man-db}/bin/mandb -C man.conf --no-straycats --create \
+ ${manualPages}/share/man
+ '';
+ in ''
+ MANDB_MAP ${config.home.profileDirectory}/share/man ${manualCache}
+ '';
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/matplotlib.nix b/infra/libkookie/home-manager/modules/programs/matplotlib.nix
new file mode 100644
index 000000000000..0d4e48c953f4
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/matplotlib.nix
@@ -0,0 +1,59 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.matplotlib;
+
+ formatLine = o: n: v:
+ let
+ formatValue = v:
+ if isBool v then (if v then "True" else "False") else toString v;
+ in if isAttrs v then
+ concatStringsSep "\n" (mapAttrsToList (formatLine "${o}${n}.") v)
+ else
+ (if v == "" then "" else "${o}${n}: ${formatValue v}");
+
+in {
+ meta.maintainers = [ maintainers.rprospero ];
+
+ options.programs.matplotlib = {
+ enable = mkEnableOption "matplotlib, a plotting library for python";
+
+ config = mkOption {
+ default = { };
+ type = types.attrsOf types.anything;
+ description = ''
+ Add terms to the <filename>matplotlibrc</filename> file to
+ control the default matplotlib behavior.
+ '';
+ example = literalExample ''
+ {
+ backend = "Qt5Agg";
+ axes = {
+ grid = true;
+ facecolor = "black";
+ edgecolor = "FF9900";
+ };
+ grid.color = "FF9900";
+ }
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Additional commands for matplotlib that will be added to the
+ <filename>matplotlibrc</filename> file.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ xdg.configFile."matplotlib/matplotlibrc".text = concatStringsSep "\n" ([ ]
+ ++ mapAttrsToList (formatLine "") cfg.config
+ ++ optional (cfg.extraConfig != "") cfg.extraConfig) + "\n";
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/mbsync-accounts.nix b/infra/libkookie/home-manager/modules/programs/mbsync-accounts.nix
new file mode 100644
index 000000000000..c1bd551fa09e
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/mbsync-accounts.nix
@@ -0,0 +1,226 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+
+ extraConfigType = with lib.types; attrsOf (either (either str int) bool);
+
+ perAccountGroups = { name, config, ... }: {
+ options = {
+ name = mkOption {
+ type = types.str;
+ # Make value of name the same as the name used with the dot prefix
+ default = name;
+ readOnly = true;
+ description = ''
+ The name of this group for this account. These names are different than
+ some others, because they will hide channel names that are the same.
+ '';
+ };
+
+ channels = mkOption {
+ type = types.attrsOf (types.submodule channel);
+ default = { };
+ description = ''
+ List of channels that should be grouped together into this group. When
+ performing a synchronization, the groups are synchronized, rather than
+ the individual channels.
+ </para><para>
+ Using these channels and then grouping them together allows for you to
+ define the maildir hierarchy as you see fit.
+ '';
+ };
+ };
+ };
+
+ # Options for configuring channel(s) that will be composed together into a group.
+ channel = { name, config, ... }: {
+ options = {
+ name = mkOption {
+ type = types.str;
+ default = name;
+ readOnly = true;
+ description = ''
+ The unique name for THIS channel in THIS group. The group will refer to
+ this channel by this name.
+ </para><para>
+ In addition, you can manually sync just this channel by specifying this
+ name to mbsync on the command line.
+ '';
+ };
+
+ masterPattern = mkOption {
+ type = types.str;
+ default = "";
+ example = "[Gmail]/Sent Mail";
+ description = ''
+ IMAP4 patterns for which mailboxes on the remote mail server to sync.
+ If <literal>Patterns</literal> are specified, <literal>masterPattern</literal>
+ is interpreted as a prefix which is not matched against the patterns,
+ and is not affected by mailbox list overrides.
+ </para><para>
+ If this is left as the default, then mbsync will default to the pattern
+ <literal>INBOX</literal>.
+ '';
+ };
+
+ slavePattern = mkOption {
+ type = types.str;
+ default = "";
+ example = "Sent";
+ description = ''
+ Name for where mail coming from the master mail server will end up
+ locally. The mailbox specified by the master's pattern will be placed
+ in this directory.
+ </para><para>
+ If this is left as the default, then mbsync will default to the pattern
+ <literal>INBOX</literal>.
+ '';
+ };
+
+ patterns = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "INBOX" ];
+ description = ''
+ Instead of synchronizing <emphasis>just</emphasis> the mailboxes that
+ match the <literal>masterPattern</literal>, use it as a prefix which is
+ not matched against the patterns, and is not affected by mailbox list
+ overrides.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = extraConfigType;
+ default = { };
+ example = ''
+ {
+ Create = "both";
+ CopyArrivalDate = "yes";
+ MaxMessages = 10000;
+ MaxSize = "1m";
+ }
+ '';
+ description = ''
+ Extra configuration lines to add to <emphasis>THIS</emphasis> channel's
+ configuration.
+ '';
+ };
+ };
+ };
+
+in {
+ options.mbsync = {
+ enable = mkEnableOption "synchronization using mbsync";
+
+ flatten = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = ".";
+ description = ''
+ If set, flattens the hierarchy within the maildir by
+ substituting the canonical hierarchy delimiter
+ <literal>/</literal> with this value.
+ '';
+ };
+
+ create = mkOption {
+ type = types.enum [ "none" "maildir" "imap" "both" ];
+ default = "none";
+ example = "maildir";
+ description = ''
+ Automatically create missing mailboxes within the
+ given mail store.
+ '';
+ };
+
+ remove = mkOption {
+ type = types.enum [ "none" "maildir" "imap" "both" ];
+ default = "none";
+ example = "imap";
+ description = ''
+ Propagate mailbox deletions to the given mail store.
+ '';
+ };
+
+ expunge = mkOption {
+ type = types.enum [ "none" "maildir" "imap" "both" ];
+ default = "none";
+ example = "both";
+ description = ''
+ Permanently remove messages marked for deletion from
+ the given mail store.
+ '';
+ };
+
+ patterns = mkOption {
+ type = types.listOf types.str;
+ default = [ "*" ];
+ description = ''
+ Pattern of mailboxes to synchronize.
+ '';
+ };
+
+ groups = mkOption {
+ type = types.attrsOf (types.submodule perAccountGroups);
+ default = { };
+ # The default cannot actually be empty, but contains an attribute set where
+ # the channels set is empty. If a group is specified, then a name is given,
+ # creating the attribute set.
+ description = ''
+ Some email providers (Gmail) have a different directory hierarchy for
+ synchronized email messages. Namely, when using mbsync without specifying
+ a set of channels into a group, all synchronized directories end up beneath
+ the <literal>[Gmail]/</literal> directory.
+ </para><para>
+ This option allows you to specify a group, and subsequently channels that
+ will allow you to sync your mail into an arbitrary hierarchy.
+ '';
+ };
+
+ extraConfig.channel = mkOption {
+ type = extraConfigType;
+ default = { };
+ example = literalExample ''
+ {
+ MaxMessages = 10000;
+ MaxSize = "1m";
+ };
+ '';
+ description = ''
+ Per channel extra configuration.
+ '';
+ };
+
+ extraConfig.local = mkOption {
+ type = extraConfigType;
+ default = { };
+ description = ''
+ Local store extra configuration.
+ '';
+ };
+
+ extraConfig.remote = mkOption {
+ type = extraConfigType;
+ default = { };
+ description = ''
+ Remote store extra configuration.
+ '';
+ };
+
+ extraConfig.account = mkOption {
+ type = extraConfigType;
+ default = { };
+ example = literalExample ''
+ {
+ PipelineDepth = 10;
+ Timeout = 60;
+ };
+ '';
+ description = ''
+ Account section extra configuration.
+ '';
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/mbsync.nix b/infra/libkookie/home-manager/modules/programs/mbsync.nix
new file mode 100644
index 000000000000..f9713da3aef3
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/mbsync.nix
@@ -0,0 +1,252 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.mbsync;
+
+ # Accounts for which mbsync is enabled.
+ mbsyncAccounts =
+ filter (a: a.mbsync.enable) (attrValues config.accounts.email.accounts);
+
+ genTlsConfig = tls:
+ {
+ SSLType = if !tls.enable then
+ "None"
+ else if tls.useStartTls then
+ "STARTTLS"
+ else
+ "IMAPS";
+ } // optionalAttrs (tls.enable && tls.certificatesFile != null) {
+ CertificateFile = toString tls.certificatesFile;
+ };
+
+ masterSlaveMapping = {
+ none = "None";
+ imap = "Master";
+ maildir = "Slave";
+ both = "Both";
+ };
+
+ genSection = header: entries:
+ let
+ escapeValue = escape [ ''"'' ];
+ hasSpace = v: builtins.match ".* .*" v != null;
+ genValue = n: v:
+ if isList v then
+ concatMapStringsSep " " (genValue n) v
+ else if isBool v then
+ (if v then "yes" else "no")
+ else if isInt v then
+ toString v
+ else if isString v && hasSpace v then
+ ''"${escapeValue v}"''
+ else if isString v then
+ v
+ else
+ let prettyV = lib.generators.toPretty { } v;
+ in throw "mbsync: unexpected value for option ${n}: '${prettyV}'";
+ in ''
+ ${header}
+ ${concatStringsSep "\n"
+ (mapAttrsToList (n: v: "${n} ${genValue n v}") entries)}
+ '';
+
+ genAccountConfig = account:
+ with account;
+ genSection "IMAPAccount ${name}" ({
+ Host = imap.host;
+ User = userName;
+ PassCmd = toString passwordCommand;
+ } // genTlsConfig imap.tls
+ // optionalAttrs (imap.port != null) { Port = toString imap.port; }
+ // mbsync.extraConfig.account) + "\n"
+ + genSection "IMAPStore ${name}-remote"
+ ({ Account = name; } // mbsync.extraConfig.remote) + "\n"
+ + genSection "MaildirStore ${name}-local" ({
+ Path = "${maildir.absPath}/";
+ Inbox = "${maildir.absPath}/${folders.inbox}";
+ SubFolders = "Verbatim";
+ } // optionalAttrs (mbsync.flatten != null) { Flatten = mbsync.flatten; }
+ // mbsync.extraConfig.local) + "\n" + genChannels account;
+
+ genChannels = account:
+ with account;
+ if mbsync.groups == { } then
+ genAccountWideChannel account
+ else
+ genGroupChannelConfig name mbsync.groups + "\n"
+ + genAccountGroups mbsync.groups;
+
+ # Used when no channels are specified for this account. This will create a
+ # single channel for the entire account that is then further refined within
+ # the Group for synchronization.
+ genAccountWideChannel = account:
+ with account;
+ genSection "Channel ${name}" ({
+ Master = ":${name}-remote:";
+ Slave = ":${name}-local:";
+ Patterns = mbsync.patterns;
+ Create = masterSlaveMapping.${mbsync.create};
+ Remove = masterSlaveMapping.${mbsync.remove};
+ Expunge = masterSlaveMapping.${mbsync.expunge};
+ SyncState = "*";
+ } // mbsync.extraConfig.channel) + "\n";
+
+ # Given the attr set of groups, return a string of channels that will direct
+ # mail to the proper directories, according to the pattern used in channel's
+ # master pattern definition.
+ genGroupChannelConfig = storeName: groups:
+ let
+ # Given the name of the group this channel is part of and the channel
+ # itself, generate the string for the desired configuration.
+ genChannelString = groupName: channel:
+ let
+ escapeValue = escape [ ''\"'' ];
+ hasSpace = v: builtins.match ".* .*" v != null;
+ # Given a list of patterns, will return the string requested.
+ # Only prints if the pattern is NOT the empty list, the default.
+ genChannelPatterns = patterns:
+ if (length patterns) != 0 then
+ "Pattern " + concatStringsSep " "
+ (map (pat: if hasSpace pat then escapeValue pat else pat)
+ patterns) + "\n"
+ else
+ "";
+ in genSection "Channel ${groupName}-${channel.name}" ({
+ Master = ":${storeName}-remote:${channel.masterPattern}";
+ Slave = ":${storeName}-local:${channel.slavePattern}";
+ } // channel.extraConfig) + genChannelPatterns channel.patterns;
+ # Given the group name, and a attr set of channels within that group,
+ # Generate a list of strings for each channels' configuration.
+ genChannelStrings = groupName: channels:
+ optionals (channels != { })
+ (mapAttrsToList (channelName: info: genChannelString groupName info)
+ channels);
+ # Given a group, return a string that configures all the channels within
+ # the group.
+ genGroupsChannels = group:
+ concatStringsSep "\n" (genChannelStrings group.name group.channels);
+ # Generate all channel configurations for all groups for this account.
+ in concatStringsSep "\n" (filter (s: s != "")
+ (mapAttrsToList (name: group: genGroupsChannels group) groups));
+
+ # Given the attr set of groups, return a string which maps channels to groups
+ genAccountGroups = groups:
+ let
+ # Given the name of the group and the attribute set of channels, make
+ # make "Channel <grpName>-<chnName>" for each channel to list os strings
+ genChannelStrings = groupName: channels:
+ mapAttrsToList (name: info: "Channel ${groupName}-${name}") channels;
+ # Take in 1 group, if the group has channels specified, construct the
+ # "Group <grpName>" header and each of the channels.
+ genGroupChannelString = group:
+ flatten (optionals (group.channels != { }) ([ "Group ${group.name}" ]
+ ++ (genChannelStrings group.name group.channels)));
+ # Given set of groups, generates list of strings, where each string is one
+ # of the groups and its consituent channels.
+ genGroupsStrings = mapAttrsToList (name: info:
+ concatStringsSep "\n" (genGroupChannelString groups.${name})) groups;
+ in concatStringsSep "\n\n" (filter (s: s != "")
+ genGroupsStrings) # filter for the cases of empty groups
+ + "\n"; # Put all strings together.
+
+ genGroupConfig = name: channels:
+ let
+ genGroupChannel = n: boxes: "Channel ${n}:${concatStringsSep "," boxes}";
+ in "\n" + concatStringsSep "\n"
+ ([ "Group ${name}" ] ++ mapAttrsToList genGroupChannel channels);
+
+in {
+ meta.maintainers = [ maintainers.KarlJoad ];
+
+ options = {
+ programs.mbsync = {
+ enable = mkEnableOption "mbsync IMAP4 and Maildir mailbox synchronizer";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.isync;
+ defaultText = literalExample "pkgs.isync";
+ example = literalExample "pkgs.isync";
+ description = "The package to use for the mbsync binary.";
+ };
+
+ groups = mkOption {
+ type = types.attrsOf (types.attrsOf (types.listOf types.str));
+ default = { };
+ example = literalExample ''
+ {
+ inboxes = {
+ account1 = [ "Inbox" ];
+ account2 = [ "Inbox" ];
+ };
+ }
+ '';
+ description = ''
+ Definition of groups.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration lines to add to the mbsync configuration.
+ '';
+ };
+ };
+
+ accounts.email.accounts = mkOption {
+ type = with types; attrsOf (submodule (import ./mbsync-accounts.nix));
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = let
+ checkAccounts = pred: msg:
+ let badAccounts = filter pred mbsyncAccounts;
+ in {
+ assertion = badAccounts == [ ];
+ message = "mbsync: ${msg} for accounts: "
+ + concatMapStringsSep ", " (a: a.name) badAccounts;
+ };
+ in [
+ (checkAccounts (a: a.maildir == null) "Missing maildir configuration")
+ (checkAccounts (a: a.imap == null) "Missing IMAP configuration")
+ (checkAccounts (a: a.passwordCommand == null) "Missing passwordCommand")
+ (checkAccounts (a: a.userName == null) "Missing username")
+ ];
+
+ home.packages = [ cfg.package ];
+
+ programs.notmuch.new.ignore = [ ".uidvalidity" ".mbsyncstate" ];
+
+ home.file.".mbsyncrc".text = let
+ accountsConfig = map genAccountConfig mbsyncAccounts;
+ # Only generate this kind of Group configuration if there are ANY accounts
+ # that do NOT have a per-account groups/channels option(s) specified.
+ groupsConfig =
+ if any (account: account.mbsync.groups == { }) mbsyncAccounts then
+ mapAttrsToList genGroupConfig cfg.groups
+ else
+ [ ];
+ in ''
+ # Generated by Home Manager.
+
+ ''
+ + concatStringsSep "\n" (optional (cfg.extraConfig != "") cfg.extraConfig)
+ + concatStringsSep "\n\n" accountsConfig
+ + concatStringsSep "\n" groupsConfig;
+
+ home.activation = mkIf (mbsyncAccounts != [ ]) {
+ createMaildir =
+ hm.dag.entryBetween [ "linkGeneration" ] [ "writeBoundary" ] ''
+ $DRY_RUN_CMD mkdir -m700 -p $VERBOSE_ARG ${
+ concatMapStringsSep " " (a: a.maildir.absPath) mbsyncAccounts
+ }
+ '';
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/mcfly.nix b/infra/libkookie/home-manager/modules/programs/mcfly.nix
new file mode 100644
index 000000000000..1206f9da5660
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/mcfly.nix
@@ -0,0 +1,79 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+
+ cfg = config.programs.mcfly;
+
+in {
+ meta.maintainers = [ maintainers.marsam ];
+
+ options.programs.mcfly = {
+ enable = mkEnableOption "mcfly";
+
+ keyScheme = mkOption {
+ type = types.enum [ "emacs" "vim" ];
+ default = "emacs";
+ description = ''
+ Key scheme to use.
+ '';
+ };
+
+ enableLightTheme = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to enable light mode theme.
+ '';
+ };
+
+ enableBashIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Bash integration.
+ '';
+ };
+
+ enableZshIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Zsh integration.
+ '';
+ };
+
+ enableFishIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Fish integration.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+ home.packages = [ pkgs.mcfly ];
+
+ programs.bash.initExtra = mkIf cfg.enableBashIntegration ''
+ source "${pkgs.mcfly}/share/mcfly/mcfly.bash"
+ '';
+
+ programs.zsh.initExtra = mkIf cfg.enableZshIntegration ''
+ source "${pkgs.mcfly}/share/mcfly/mcfly.zsh"
+ '';
+
+ programs.fish.shellInit = mkIf cfg.enableFishIntegration ''
+ source "${pkgs.mcfly}/share/mcfly/mcfly.fish"
+ if status is-interactive
+ mcfly_key_bindings
+ end
+ '';
+
+ home.sessionVariables.MCFLY_KEY_SCHEME = cfg.keyScheme;
+ }
+
+ (mkIf cfg.enableLightTheme { home.sessionVariables.MCFLY_LIGHT = "TRUE"; })
+ ]);
+}
diff --git a/infra/libkookie/home-manager/modules/programs/mercurial.nix b/infra/libkookie/home-manager/modules/programs/mercurial.nix
new file mode 100644
index 000000000000..2fc6e0076aea
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/mercurial.nix
@@ -0,0 +1,103 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.mercurial;
+
+ iniFormat = pkgs.formats.ini { };
+
+in {
+
+ options = {
+ programs.mercurial = {
+ enable = mkEnableOption "Mercurial";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.mercurial;
+ defaultText = literalExample "pkgs.mercurial";
+ description = "Mercurial package to install.";
+ };
+
+ userName = mkOption {
+ type = types.str;
+ description = "Default user name to use.";
+ };
+
+ userEmail = mkOption {
+ type = types.str;
+ description = "Default user email to use.";
+ };
+
+ aliases = mkOption {
+ type = types.attrsOf types.anything;
+ default = { };
+ description = "Mercurial aliases to define.";
+ };
+
+ extraConfig = mkOption {
+ type = types.either (types.attrsOf types.anything) types.lines;
+ default = { };
+ description = "Additional configuration to add.";
+ };
+
+ iniContent = mkOption {
+ type = iniFormat.type;
+ internal = true;
+ };
+
+ ignores = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "*~" "*.swp" ];
+ description = "List of globs for files to be globally ignored.";
+ };
+
+ ignoresRegexp = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "^.*~$" "^.*\\.swp$" ];
+ description =
+ "List of regular expressions for files to be globally ignored.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+ home.packages = [ cfg.package ];
+
+ programs.mercurial.iniContent.ui = {
+ username = cfg.userName + " <" + cfg.userEmail + ">";
+ };
+
+ xdg.configFile."hg/hgrc".source =
+ iniFormat.generate "hgrc" cfg.iniContent;
+ }
+
+ (mkIf (cfg.ignores != [ ] || cfg.ignoresRegexp != [ ]) {
+ programs.mercurial.iniContent.ui.ignore =
+ "${config.xdg.configHome}/hg/hgignore_global";
+
+ xdg.configFile."hg/hgignore_global".text = ''
+ syntax: glob
+ '' + concatStringsSep "\n" cfg.ignores + "\n" + ''
+ syntax: regexp
+ '' + concatStringsSep "\n" cfg.ignoresRegexp + "\n";
+ })
+
+ (mkIf (cfg.aliases != { }) {
+ programs.mercurial.iniContent.alias = cfg.aliases;
+ })
+
+ (mkIf (lib.isAttrs cfg.extraConfig) {
+ programs.mercurial.iniContent = cfg.extraConfig;
+ })
+
+ (mkIf (lib.isString cfg.extraConfig) {
+ xdg.configFile."hg/hgrc".text = cfg.extraConfig;
+ })
+ ]);
+}
diff --git a/infra/libkookie/home-manager/modules/programs/mpv.nix b/infra/libkookie/home-manager/modules/programs/mpv.nix
new file mode 100644
index 000000000000..5901f9d08b8b
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/mpv.nix
@@ -0,0 +1,158 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ inherit (builtins) typeOf stringLength;
+
+ cfg = config.programs.mpv;
+
+ mpvOption = with types; either str (either int (either bool float));
+ mpvOptionDup = with types; either mpvOption (listOf mpvOption);
+ mpvOptions = with types; attrsOf mpvOptionDup;
+ mpvProfiles = with types; attrsOf mpvOptions;
+ mpvBindings = with types; attrsOf str;
+
+ renderOption = option:
+ rec {
+ int = toString option;
+ float = int;
+
+ bool = if option then "yes" else "no";
+
+ string = option;
+ }.${typeOf option};
+
+ renderOptionValue = value:
+ let
+ rendered = renderOption value;
+ length = toString (stringLength rendered);
+ in "%${length}%${rendered}";
+
+ renderOptions = generators.toKeyValue {
+ mkKeyValue =
+ generators.mkKeyValueDefault { mkValueString = renderOptionValue; } "=";
+ listsAsDuplicateKeys = true;
+ };
+
+ renderProfiles = generators.toINI {
+ mkKeyValue =
+ generators.mkKeyValueDefault { mkValueString = renderOptionValue; } "=";
+ listsAsDuplicateKeys = true;
+ };
+
+ renderBindings = bindings:
+ concatStringsSep "\n"
+ (mapAttrsToList (name: value: "${name} ${value}") bindings);
+
+ mpvPackage = if cfg.scripts == [ ] then
+ pkgs.mpv
+ else
+ pkgs.wrapMpv pkgs.mpv-unwrapped { scripts = cfg.scripts; };
+
+in {
+ options = {
+ programs.mpv = {
+ enable = mkEnableOption "mpv";
+
+ package = mkOption {
+ type = types.package;
+ readOnly = true;
+ description = ''
+ Resulting mpv package.
+ '';
+ };
+
+ scripts = mkOption {
+ type = with types; listOf (either package str);
+ default = [ ];
+ example = literalExample "[ pkgs.mpvScripts.mpris ]";
+ description = ''
+ List of scripts to use with mpv.
+ '';
+ };
+
+ config = mkOption {
+ description = ''
+ Configuration written to
+ <filename>~/.config/mpv/mpv.conf</filename>. See
+ <citerefentry>
+ <refentrytitle>mpv</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ for the full list of options.
+ '';
+ type = mpvOptions;
+ default = { };
+ example = literalExample ''
+ {
+ profile = "gpu-hq";
+ force-window = "yes";
+ ytdl-format = "bestvideo+bestaudio";
+ cache-default = 4000000;
+ }
+ '';
+ };
+
+ profiles = mkOption {
+ description = ''
+ Sub-configuration options for specific profiles written to
+ <filename>~/.config/mpv/mpv.conf</filename>. See
+ <option>programs.mpv.config</option> for more information.
+ '';
+ type = mpvProfiles;
+ default = { };
+ example = literalExample ''
+ {
+ fast = {
+ vo = "vdpau";
+ };
+ "protocol.dvd" = {
+ profile-desc = "profile for dvd:// streams";
+ alang = "en";
+ };
+ }
+ '';
+ };
+
+ bindings = mkOption {
+ description = ''
+ Input configuration written to
+ <filename>~/.config/mpv/input.conf</filename>. See
+ <citerefentry>
+ <refentrytitle>mpv</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ for the full list of options.
+ '';
+ type = mpvBindings;
+ default = { };
+ example = literalExample ''
+ {
+ WHEEL_UP = "seek 10";
+ WHEEL_DOWN = "seek -10";
+ "Alt+0" = "set window-scale 0.5";
+ }
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+ home.packages = [ mpvPackage ];
+ programs.mpv.package = mpvPackage;
+ }
+ (mkIf (cfg.config != { } || cfg.profiles != { }) {
+ xdg.configFile."mpv/mpv.conf".text = ''
+ ${optionalString (cfg.config != { }) (renderOptions cfg.config)}
+ ${optionalString (cfg.profiles != { }) (renderProfiles cfg.profiles)}
+ '';
+ })
+ (mkIf (cfg.bindings != { }) {
+ xdg.configFile."mpv/input.conf".text = renderBindings cfg.bindings;
+ })
+ ]);
+
+ meta.maintainers = with maintainers; [ tadeokondrak ];
+}
diff --git a/infra/libkookie/home-manager/modules/programs/msmtp-accounts.nix b/infra/libkookie/home-manager/modules/programs/msmtp-accounts.nix
new file mode 100644
index 000000000000..894cef517426
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/msmtp-accounts.nix
@@ -0,0 +1,48 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+ options.msmtp = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable msmtp.
+ </para><para>
+ If enabled then it is possible to use the
+ <parameter class="command">--account</parameter> command line
+ option to send a message for a given account using the
+ <command>msmtp</command> or <command>msmtpq</command> tool.
+ For example, <command>msmtp --account=private</command> would
+ send using the account defined in
+ <option>accounts.email.accounts.private</option>. If the
+ <parameter class="command">--account</parameter> option is not
+ given then the primary account will be used.
+ '';
+ };
+
+ tls.fingerprint = mkOption {
+ type =
+ types.nullOr (types.strMatching "([[:alnum:]]{2}:)+[[:alnum:]]{2}");
+ default = null;
+ example = "my:SH:a2:56:ha:sh";
+ description = ''
+ Fingerprint of a trusted TLS certificate.
+ The fingerprint can be obtained by executing
+ <command>msmtp --serverinfo --tls --tls-certcheck=off</command>.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ example = { auth = "login"; };
+ description = ''
+ Extra configuration options to add to <filename>~/.msmtprc</filename>.
+ See <link xlink:href="https://marlam.de/msmtp/msmtprc.txt"/> for
+ examples.
+ '';
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/msmtp.nix b/infra/libkookie/home-manager/modules/programs/msmtp.nix
new file mode 100644
index 000000000000..f7745d33bb5a
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/msmtp.nix
@@ -0,0 +1,73 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.msmtp;
+
+ msmtpAccounts =
+ filter (a: a.msmtp.enable) (attrValues config.accounts.email.accounts);
+
+ onOff = p: if p then "on" else "off";
+
+ accountStr = account:
+ with account;
+ concatStringsSep "\n" ([ "account ${name}" ]
+ ++ mapAttrsToList (n: v: n + " " + v) ({
+ host = smtp.host;
+ from = address;
+ auth = "on";
+ user = userName;
+ tls = onOff smtp.tls.enable;
+ tls_starttls = onOff smtp.tls.useStartTls;
+ tls_trust_file = smtp.tls.certificatesFile;
+ } // optionalAttrs (msmtp.tls.fingerprint != null) {
+ tls_fingerprint = msmtp.tls.fingerprint;
+ } // optionalAttrs (smtp.port != null) { port = toString smtp.port; }
+ // optionalAttrs (passwordCommand != null) {
+ passwordeval = toString passwordCommand;
+ } // msmtp.extraConfig) ++ optional primary ''
+
+ account default : ${name}'');
+
+ configFile = mailAccounts: ''
+ # Generated by Home Manager.
+
+ ${cfg.extraConfig}
+
+ ${concatStringsSep "\n\n" (map accountStr mailAccounts)}
+ '';
+
+in {
+
+ options = {
+ programs.msmtp = {
+ enable = mkEnableOption "msmtp";
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration lines to add to <filename>~/.msmtprc</filename>.
+ See <link xlink:href="https://marlam.de/msmtp/msmtprc.txt"/> for examples.
+ '';
+ };
+ };
+
+ accounts.email.accounts = mkOption {
+ type = with types; attrsOf (submodule (import ./msmtp-accounts.nix));
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.msmtp ];
+
+ xdg.configFile."msmtp/config".text = configFile msmtpAccounts;
+
+ home.sessionVariables = {
+ MSMTP_QUEUE = "${config.xdg.dataHome}/msmtp/queue";
+ MSMTP_LOG = "${config.xdg.dataHome}/msmtp/queue.log";
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/mu.nix b/infra/libkookie/home-manager/modules/programs/mu.nix
new file mode 100644
index 000000000000..18c3993a158d
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/mu.nix
@@ -0,0 +1,57 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.mu;
+
+ # Used to generate command line arguments that mu can operate with.
+ genCmdMaildir = path: "--maildir=" + path;
+
+ # Takes the list of accounts with mu.enable = true, and generates a
+ # command-line flag for initializing the mu database.
+ myAddresses = let
+ # List of account sets where mu.enable = true.
+ muAccounts =
+ filter (a: a.mu.enable) (attrValues config.accounts.email.accounts);
+ addrs = map (a: a.address) muAccounts;
+ # Prefix --my-address= to each account's address with mu.enable.
+ addMyAddress = map (addr: "--my-address=" + addr) addrs;
+ in concatStringsSep " " addMyAddress;
+
+in {
+ meta.maintainers = [ maintainers.KarlJoad ];
+
+ options = {
+ programs.mu = {
+ enable = mkEnableOption "mu, a maildir indexer and searcher";
+
+ # No options/config file present for mu, and program author will not be
+ # adding one soon. See https://github.com/djcb/mu/issues/882 for more
+ # information about this.
+ };
+
+ accounts.email.accounts = mkOption {
+ type = with types;
+ attrsOf
+ (submodule { options.mu.enable = mkEnableOption "mu indexing"; });
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.mu ];
+
+ home.activation.runMuInit = let
+ maildirOption = genCmdMaildir config.accounts.email.maildirBasePath;
+ dbLocation = config.xdg.cacheHome + "/mu";
+ in hm.dag.entryAfter [ "writeBoundary" ] ''
+ # If the database directory exists, then `mu init` should NOT be run.
+ # In theory, mu is the only thing that creates that directory, and it is
+ # only created during the initial index.
+ if [[ ! -d "${dbLocation}" ]]; then
+ $DRY_RUN_CMD mu init ${maildirOption} $VERBOSE_ARG;
+ fi
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/ncmpcpp.nix b/infra/libkookie/home-manager/modules/programs/ncmpcpp.nix
new file mode 100644
index 000000000000..a39baab6ca5a
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/ncmpcpp.nix
@@ -0,0 +1,135 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.ncmpcpp;
+
+ renderSettings = settings:
+ concatStringsSep "\n" (mapAttrsToList renderSetting settings);
+
+ renderSetting = name: value: "${name}=${renderValue value}";
+
+ renderValue = option:
+ {
+ int = toString option;
+ bool = if option then "yes" else "no";
+ string = option;
+ }.${builtins.typeOf option};
+
+ renderBindings = bindings: concatStringsSep "\n" (map renderBinding bindings);
+
+ renderBinding = { key, command }:
+ concatStringsSep "\n " ([ ''def_key "${key}"'' ] ++ maybeWrapList command);
+
+ maybeWrapList = xs: if isList xs then xs else [ xs ];
+
+ valueType = with types; oneOf [ bool int str ];
+
+ bindingType = types.submodule ({ name, config, ... }: {
+ options = {
+ key = mkOption {
+ type = types.str;
+ description = "Key to bind.";
+ example = "j";
+ };
+
+ command = mkOption {
+ type = with types; either str (listOf str);
+ description = "Command or sequence of commands to be executed.";
+ example = "scroll_down";
+ };
+ };
+ });
+
+in {
+ meta.maintainers = with maintainers; [ olmokramer ];
+
+ options.programs.ncmpcpp = {
+ enable =
+ mkEnableOption "ncmpcpp - an ncurses Music Player Daemon (MPD) client";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.ncmpcpp;
+ defaultText = literalExample "pkgs.ncmpcpp";
+ description = ''
+ Package providing the <code>ncmpcpp</code> command.
+ '';
+ example =
+ literalExample "pkgs.ncmpcpp.override { visualizerSupport = true; }";
+ };
+
+ mpdMusicDir = mkOption {
+ type = types.nullOr types.path;
+ default = let mpdCfg = config.services.mpd;
+ in if pkgs.stdenv.hostPlatform.isLinux && mpdCfg.enable then
+ mpdCfg.musicDirectory
+ else
+ null;
+ defaultText = literalExample ''
+ if pkgs.stdenv.hostPlatform.isLinux && config.services.mpd.enable then
+ config.services.mpd.musicDirectory
+ else
+ null
+ '';
+ description = ''
+ Value of the <code>mpd_music_dir</code> setting. On Linux platforms the
+ value of <varname>services.mpd.musicDirectory</varname> is used as the
+ default if <varname>services.mpd.enable</varname> is
+ <literal>true</literal>.
+ '';
+ example = "~/music";
+ };
+
+ settings = mkOption {
+ type = types.attrsOf valueType;
+ default = { };
+ description = ''
+ Attribute set from name of a setting to its value. For available options
+ see
+ <citerefentry>
+ <refentrytitle>ncmpcpp</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>.
+ '';
+ example = { ncmpcpp_directory = "~/.local/share/ncmpcpp"; };
+ };
+
+ bindings = mkOption {
+ type = types.listOf bindingType;
+ default = [ ];
+ description = "List of keybindings.";
+ example = literalExample ''
+ [
+ { key = "j"; command = "scroll_down"; }
+ { key = "k"; command = "scroll_up"; }
+ { key = "J"; command = [ "select_item" "scroll_down" ]; }
+ { key = "K"; command = [ "select_item" "scroll_up" ]; }
+ ]
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ warnings = mkIf (cfg.settings ? mpd_music_dir && cfg.mpdMusicDir != null) [
+ ("programs.ncmpcpp.settings.mpd_music_dir will be overridden by"
+ + " programs.ncmpcpp.mpdMusicDir.")
+ ];
+
+ home.packages = [ cfg.package ];
+
+ xdg.configFile = {
+ "ncmpcpp/config" = let
+ settings = cfg.settings // optionalAttrs (cfg.mpdMusicDir != null) {
+ mpd_music_dir = toString cfg.mpdMusicDir;
+ };
+ in mkIf (settings != { }) { text = renderSettings settings + "\n"; };
+
+ "ncmpcpp/bindings" = mkIf (cfg.bindings != [ ]) {
+ text = renderBindings cfg.bindings + "\n";
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/ne.nix b/infra/libkookie/home-manager/modules/programs/ne.nix
new file mode 100644
index 000000000000..a88d23d91338
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/ne.nix
@@ -0,0 +1,95 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.ne;
+
+ autoPrefFiles = let
+ autoprefs = cfg.automaticPreferences
+ // optionalAttrs (cfg.defaultPreferences != "") {
+ ".default" = cfg.defaultPreferences;
+ };
+
+ gen = fileExtension: configText:
+ nameValuePair ".ne/${fileExtension}#ap" {
+ text = configText;
+ }; # Generates [path].text format expected by home.file.
+ in mapAttrs' gen autoprefs;
+
+in {
+ meta.maintainers = [ hm.maintainers.cwyc ];
+
+ options.programs.ne = {
+ enable = mkEnableOption "ne";
+
+ keybindings = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ KEY 7f BS
+ SEQ "\x1b[1;5D" 7f
+ '';
+ description = ''
+ Keybinding file for ne.
+ '';
+ };
+
+ defaultPreferences = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Default preferences for ne.
+ </para><para>
+ Equivalent to <literal>programs.ne.automaticPreferences.".default"</literal>.
+ '';
+ };
+
+ automaticPreferences = mkOption {
+ type = types.attrsOf types.lines;
+ default = { };
+ example = literalExample ''
+ {
+ nix = '''
+ TAB 0
+ TS 2
+ ''';
+ js = '''
+ TS 4
+ ''';
+ }
+ '';
+ description = ''
+ Automatic preferences files for ne.
+ '';
+ };
+
+ menus = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Menu configuration file for ne.";
+ };
+
+ virtualExtensions = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ sh 1 ^#!\s*/.*\b(bash|sh|ksh|zsh)\s*
+ csh 1 ^#!\s*/.*\b(csh|tcsh)\s*
+ '';
+ description = "Virtual extensions configuration file for ne.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.ne ];
+
+ home.file = {
+ ".ne/.keys" = mkIf (cfg.keybindings != "") { text = cfg.keybindings; };
+ ".ne/.extensions" =
+ mkIf (cfg.virtualExtensions != "") { text = cfg.virtualExtensions; };
+ ".ne/.menus" = mkIf (cfg.menus != "") { text = cfg.menus; };
+ } // autoPrefFiles;
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/neomutt-accounts.nix b/infra/libkookie/home-manager/modules/programs/neomutt-accounts.nix
new file mode 100644
index 000000000000..009cf1fa7e82
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/neomutt-accounts.nix
@@ -0,0 +1,36 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+ options.neomutt = {
+ enable = mkEnableOption "NeoMutt";
+
+ sendMailCommand = mkOption {
+ type = types.nullOr types.str;
+ default = if config.msmtp.enable then
+ "msmtpq --read-envelope-from --read-recipients"
+ else
+ null;
+ defaultText = literalExample ''
+ if config.msmtp.enable then
+ "msmtpq --read-envelope-from --read-recipients"
+ else
+ null
+ '';
+ example = "msmtpq --read-envelope-from --read-recipients";
+ description = ''
+ Command to send a mail. If not set, neomutt will be in charge of sending mails.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = "color status cyan default";
+ description = ''
+ Extra lines to add to the folder hook for this account.
+ '';
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/neomutt.nix b/infra/libkookie/home-manager/modules/programs/neomutt.nix
new file mode 100644
index 000000000000..d990f02eaca5
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/neomutt.nix
@@ -0,0 +1,312 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.neomutt;
+
+ neomuttAccounts =
+ filter (a: a.neomutt.enable) (attrValues config.accounts.email.accounts);
+
+ sidebarModule = types.submodule {
+ options = {
+ enable = mkEnableOption "sidebar support";
+
+ width = mkOption {
+ type = types.int;
+ default = 22;
+ description = "Width of the sidebar";
+ };
+
+ shortPath = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ By default sidebar shows the full path of the mailbox, but
+ with this enabled only the relative name is shown.
+ '';
+ };
+
+ format = mkOption {
+ type = types.str;
+ default = "%B%?F? [%F]?%* %?N?%N/?%S";
+ description = ''
+ Sidebar format. Check neomutt documentation for details.
+ '';
+ };
+ };
+ };
+
+ sortOptions = [
+ "date"
+ "date-received"
+ "from"
+ "mailbox-order"
+ "score"
+ "size"
+ "spam"
+ "subject"
+ "threads"
+ "to"
+ ];
+
+ bindModule = types.submodule {
+ options = {
+ map = mkOption {
+ type = types.enum [
+ "alias"
+ "attach"
+ "browser"
+ "compose"
+ "editor"
+ "generic"
+ "index"
+ "mix"
+ "pager"
+ "pgp"
+ "postpone"
+ "query"
+ "smime"
+ ];
+ default = "index";
+ description = "Select the menu to bind the command to.";
+ };
+
+ key = mkOption {
+ type = types.str;
+ example = "<left>";
+ description = "The key to bind.";
+ };
+
+ action = mkOption {
+ type = types.str;
+ example = "<enter-command>toggle sidebar_visible<enter><refresh>";
+ description = "Specify the action to take.";
+ };
+ };
+ };
+
+ yesno = x: if x then "yes" else "no";
+ setOption = n: v: if v == null then "unset ${n}" else "set ${n}=${v}";
+ escape = replaceStrings [ "%" ] [ "%25" ];
+
+ accountFilename = account: config.xdg.configHome + "/neomutt/" + account.name;
+
+ genCommonFolderHooks = account:
+ with account; {
+ from = "'${address}'";
+ realname = "'${realName}'";
+ spoolfile = "'+${folders.inbox}'";
+ record = if folders.sent == null then null else "'+${folders.sent}'";
+ postponed = "'+${folders.drafts}'";
+ trash = "'+${folders.trash}'";
+ };
+
+ mtaSection = account:
+ with account;
+ let passCmd = concatStringsSep " " passwordCommand;
+ in if neomutt.sendMailCommand != null then {
+ sendmail = "'${neomutt.sendMailCommand}'";
+ } else
+ let
+ smtpProto = if smtp.tls.enable then "smtps" else "smtp";
+ smtpPort = if smtp.port != null then ":${toString smtp.port}" else "";
+ smtpBaseUrl =
+ "${smtpProto}://${escape userName}@${smtp.host}${smtpPort}";
+ in {
+ smtp_url = "'${smtpBaseUrl}'";
+ smtp_pass = "'`${passCmd}`'";
+ };
+
+ genMaildirAccountConfig = account:
+ with account;
+ let
+ folderHook = mapAttrsToList setOption (genCommonFolderHooks account // {
+ folder = "'${account.maildir.absPath}'";
+ });
+ in ''
+ ${concatStringsSep "\n" folderHook}
+ '';
+
+ registerAccount = account:
+ with account; ''
+ # register account ${name}
+ mailboxes "${account.maildir.absPath}/${folders.inbox}"
+ folder-hook ${account.maildir.absPath}/ " \
+ source ${accountFilename account} "
+ '';
+
+ mraSection = account:
+ with account;
+ if account.maildir != null then
+ genMaildirAccountConfig account
+ else
+ throw "Only maildir is supported at the moment";
+
+ optionsStr = attrs: concatStringsSep "\n" (mapAttrsToList setOption attrs);
+
+ sidebarSection = ''
+ # Sidebar
+ set sidebar_visible = yes
+ set sidebar_short_path = ${yesno cfg.sidebar.shortPath}
+ set sidebar_width = ${toString cfg.sidebar.width}
+ set sidebar_format = '${cfg.sidebar.format}'
+ '';
+
+ bindSection = concatMapStringsSep "\n"
+ (bind: ''bind ${bind.map} ${bind.key} "${bind.action}"'') cfg.binds;
+
+ macroSection = concatMapStringsSep "\n"
+ (bind: ''macro ${bind.map} ${bind.key} "${bind.action}"'') cfg.macros;
+
+ mailCheckSection = ''
+ set mail_check_stats
+ set mail_check_stats_interval = ${toString cfg.checkStatsInterval}
+ '';
+
+ notmuchSection = account:
+ with account; ''
+ # notmuch section
+ set nm_default_uri = "notmuch://${config.accounts.email.maildirBasePath}"
+ virtual-mailboxes "My INBOX" "notmuch://?query=tag:inbox"
+ '';
+
+ accountStr = account:
+ with account;
+ ''
+ # Generated by Home Manager.
+ set ssl_force_tls = yes
+ set certificate_file=${config.accounts.email.certificatesFile}
+
+ # GPG section
+ set crypt_use_gpgme = yes
+ set crypt_autosign = ${yesno (gpg.signByDefault or false)}
+ set pgp_use_gpg_agent = yes
+ set mbox_type = ${if maildir != null then "Maildir" else "mbox"}
+ set sort = "${cfg.sort}"
+
+ # MTA section
+ ${optionsStr (mtaSection account)}
+
+ ${optionalString (cfg.checkStatsInterval != null) mailCheckSection}
+
+ ${optionalString cfg.sidebar.enable sidebarSection}
+
+ # MRA section
+ ${mraSection account}
+
+ # Extra configuration
+ ${account.neomutt.extraConfig}
+ '' + optionalString (account.signature.showSignature != "none") ''
+ set signature = ${pkgs.writeText "signature.txt" account.signature.text}
+ '' + optionalString account.notmuch.enable (notmuchSection account);
+
+in {
+ options = {
+ programs.neomutt = {
+ enable = mkEnableOption "the NeoMutt mail client";
+
+ sidebar = mkOption {
+ type = sidebarModule;
+ default = { };
+ description = "Options related to the sidebar.";
+ };
+
+ binds = mkOption {
+ type = types.listOf bindModule;
+ default = [ ];
+ description = "List of keybindings.";
+ };
+
+ macros = mkOption {
+ type = types.listOf bindModule;
+ default = [ ];
+ description = "List of macros.";
+ };
+
+ sort = mkOption {
+ # allow users to choose any option from sortOptions, or any option prefixed with "reverse-"
+ type = types.enum
+ (sortOptions ++ (map (option: "reverse-" + option) sortOptions));
+ default = "threads";
+ description = "Sorting method on messages.";
+ };
+
+ vimKeys = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Enable vim-like bindings.";
+ };
+
+ checkStatsInterval = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 60;
+ description = "Enable and set the interval of automatic mail check.";
+ };
+
+ editor = mkOption {
+ type = types.str;
+ default = "$EDITOR";
+ description = "Select the editor used for writing mail.";
+ };
+
+ settings = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ description = "Extra configuration appended to the end.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Extra configuration appended to the end.";
+ };
+ };
+
+ accounts.email.accounts = mkOption {
+ type = with types; attrsOf (submodule (import ./neomutt-accounts.nix));
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.neomutt ];
+ home.file = let
+ rcFile = account: {
+ "${accountFilename account}".text = accountStr account;
+ };
+ in foldl' (a: b: a // b) { } (map rcFile neomuttAccounts);
+
+ xdg.configFile."neomutt/neomuttrc" = mkIf (neomuttAccounts != [ ]) {
+ text = let primary = filter (a: a.primary) neomuttAccounts;
+ in ''
+ # Generated by Home Manager.
+ set header_cache = "${config.xdg.cacheHome}/neomutt/headers/"
+ set message_cachedir = "${config.xdg.cacheHome}/neomutt/messages/"
+ set editor = "${cfg.editor}"
+ set implicit_autoview = yes
+
+ alternative_order text/enriched text/plain text
+
+ set delete = yes
+
+ # Binds
+ ${bindSection}
+
+ # Macros
+ ${macroSection}
+
+ ${optionalString cfg.vimKeys
+ "source ${pkgs.neomutt}/share/doc/neomutt/vim-keys/vim-keys.rc"}
+
+ # Extra configuration
+ ${optionsStr cfg.settings}
+
+ ${cfg.extraConfig}
+ '' + concatMapStringsSep "\n" registerAccount neomuttAccounts +
+ # source primary account
+ "source ${accountFilename (builtins.head primary)}";
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/neovim.nix b/infra/libkookie/home-manager/modules/programs/neovim.nix
new file mode 100644
index 000000000000..4752100ea235
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/neovim.nix
@@ -0,0 +1,266 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.neovim;
+
+ extraPythonPackageType = mkOptionType {
+ name = "extra-python-packages";
+ description = "python packages in python.withPackages format";
+ check = with types;
+ (x: if isFunction x then isList (x pkgs.pythonPackages) else false);
+ merge = mergeOneOption;
+ };
+
+ extraPython3PackageType = mkOptionType {
+ name = "extra-python3-packages";
+ description = "python3 packages in python.withPackages format";
+ check = with types;
+ (x: if isFunction x then isList (x pkgs.python3Packages) else false);
+ merge = mergeOneOption;
+ };
+
+ pluginWithConfigType = types.submodule {
+ options = {
+ config = mkOption {
+ type = types.lines;
+ description = "vimscript for this plugin to be placed in init.vim";
+ default = "";
+ };
+
+ optional = mkEnableOption "optional" // {
+ description = "Don't load by default (load with :packadd)";
+ };
+
+ plugin = mkOption {
+ type = types.package;
+ description = "vim plugin";
+ };
+ };
+ };
+
+ # A function to get the configuration string (if any) from an element of 'plugins'
+ pluginConfig = p:
+ if builtins.hasAttr "plugin" p && builtins.hasAttr "config" p then ''
+ " ${p.plugin.pname} {{{
+ ${p.config}
+ " }}}
+ '' else
+ "";
+
+ moduleConfigure = optionalAttrs (cfg.extraConfig != ""
+ || (lib.filter (hasAttr "config") cfg.plugins) != [ ]) {
+ customRC = cfg.extraConfig
+ + pkgs.lib.concatMapStrings pluginConfig cfg.plugins;
+
+ packages.home-manager = {
+ start = filter (f: f != null) (map (x:
+ if x ? plugin && x.optional == true then null else (x.plugin or x))
+ cfg.plugins);
+ opt = filter (f: f != null)
+ (map (x: if x ? plugin && x.optional == true then x.plugin else null)
+ cfg.plugins);
+ };
+ };
+ extraMakeWrapperArgs = lib.optionalString (cfg.extraPackages != [ ])
+ ''--prefix PATH : "${lib.makeBinPath cfg.extraPackages}"'';
+
+in {
+ options = {
+ programs.neovim = {
+ enable = mkEnableOption "Neovim";
+
+ viAlias = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Symlink <command>vi</command> to <command>nvim</command> binary.
+ '';
+ };
+
+ vimAlias = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Symlink <command>vim</command> to <command>nvim</command> binary.
+ '';
+ };
+
+ vimdiffAlias = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Alias <command>vimdiff</command> to <command>nvim -d</command>.
+ '';
+ };
+
+ withNodeJs = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable node provider. Set to <literal>true</literal> to
+ use Node plugins.
+ '';
+ };
+
+ withPython = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Enable Python 2 provider. Set to <literal>true</literal> to
+ use Python 2 plugins.
+ '';
+ };
+
+ extraPythonPackages = mkOption {
+ type = with types; either extraPythonPackageType (listOf package);
+ default = (_: [ ]);
+ defaultText = "ps: []";
+ example = literalExample "(ps: with ps; [ pandas jedi ])";
+ description = ''
+ A function in python.withPackages format, which returns a
+ list of Python 2 packages required for your plugins to work.
+ '';
+ };
+
+ withRuby = mkOption {
+ type = types.nullOr types.bool;
+ default = true;
+ description = ''
+ Enable ruby provider.
+ '';
+ };
+
+ withPython3 = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Enable Python 3 provider. Set to <literal>true</literal> to
+ use Python 3 plugins.
+ '';
+ };
+
+ extraPython3Packages = mkOption {
+ type = with types; either extraPython3PackageType (listOf package);
+ default = (_: [ ]);
+ defaultText = "ps: []";
+ example = literalExample "(ps: with ps; [ python-language-server ])";
+ description = ''
+ A function in python.withPackages format, which returns a
+ list of Python 3 packages required for your plugins to work.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.neovim-unwrapped;
+ defaultText = literalExample "pkgs.neovim-unwrapped";
+ description = "The package to use for the neovim binary.";
+ };
+
+ finalPackage = mkOption {
+ type = types.package;
+ visible = false;
+ readOnly = true;
+ description = "Resulting customized neovim package.";
+ };
+
+ configure = mkOption {
+ type = types.attrsOf types.anything;
+ default = { };
+ example = literalExample ''
+ configure = {
+ customRC = $''''
+ " here your custom configuration goes!
+ $'''';
+ packages.myVimPackage = with pkgs.vimPlugins; {
+ # loaded on launch
+ start = [ fugitive ];
+ # manually loadable by calling `:packadd $plugin-name`
+ opt = [ ];
+ };
+ };
+ '';
+ description = ''
+ Generate your init file from your list of plugins and custom commands,
+ and loads it from the store via <command>nvim -u /nix/store/hash-vimrc</command>
+
+ </para><para>
+
+ This option is mutually exclusive with <varname>extraConfig</varname>
+ and <varname>plugins</varname>.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ set nocompatible
+ set nobackup
+ '';
+ description = ''
+ Custom vimrc lines.
+
+ </para><para>
+
+ This option is mutually exclusive with <varname>configure</varname>.
+ '';
+ };
+
+ extraPackages = mkOption {
+ type = with types; listOf package;
+ default = [ ];
+ example = "[ pkgs.shfmt ]";
+ description = "Extra packages available to nvim.";
+ };
+
+ plugins = mkOption {
+ type = with types; listOf (either package pluginWithConfigType);
+ default = [ ];
+ example = literalExample ''
+ with pkgs.vimPlugins; [
+ yankring
+ vim-nix
+ { plugin = vim-startify;
+ config = "let g:startify_change_to_vcs_root = 0";
+ }
+ ]
+ '';
+ description = ''
+ List of vim plugins to install optionally associated with
+ configuration to be placed in init.vim.
+
+ </para><para>
+
+ This option is mutually exclusive with <varname>configure</varname>.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [{
+ assertion = cfg.configure == { } || moduleConfigure == { };
+ message = "The programs.neovim option configure is mutually exclusive"
+ + " with extraConfig and plugins.";
+ }];
+
+ home.packages = [ cfg.finalPackage ];
+
+ programs.neovim.finalPackage = pkgs.wrapNeovim cfg.package {
+ inherit (cfg)
+ extraPython3Packages withPython3 extraPythonPackages withPython
+ withNodeJs withRuby viAlias vimAlias;
+
+ extraMakeWrapperArgs = extraMakeWrapperArgs;
+ configure = cfg.configure // moduleConfigure;
+ };
+
+ programs.bash.shellAliases = mkIf cfg.vimdiffAlias { vimdiff = "nvim -d"; };
+ programs.fish.shellAliases = mkIf cfg.vimdiffAlias { vimdiff = "nvim -d"; };
+ programs.zsh.shellAliases = mkIf cfg.vimdiffAlias { vimdiff = "nvim -d"; };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/newsboat.nix b/infra/libkookie/home-manager/modules/programs/newsboat.nix
new file mode 100644
index 000000000000..793b30680bf7
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/newsboat.nix
@@ -0,0 +1,123 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.programs.newsboat;
+ wrapQuote = x: ''"${x}"'';
+
+in {
+ options = {
+ programs.newsboat = {
+ enable = mkEnableOption "the Newsboat feed reader";
+
+ urls = mkOption {
+ type = types.listOf (types.submodule {
+ options = {
+ url = mkOption {
+ type = types.str;
+ example = "http://example.com";
+ description = "Feed URL.";
+ };
+
+ tags = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "foo" "bar" ];
+ description = "Feed tags.";
+ };
+
+ title = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "ORF News";
+ description = "Feed title.";
+ };
+ };
+ });
+ default = [ ];
+ example = [{
+ url = "http://example.com";
+ tags = [ "foo" "bar" ];
+ }];
+ description = "List of news feeds.";
+ };
+
+ maxItems = mkOption {
+ type = types.int;
+ default = 0;
+ description = "Maximum number of items per feed, 0 for infinite.";
+ };
+
+ reloadThreads = mkOption {
+ type = types.int;
+ default = 5;
+ description = "How many threads to use for updating the feeds.";
+ };
+
+ autoReload = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable automatic reloading while newsboat is running.
+ '';
+ };
+
+ reloadTime = mkOption {
+ type = types.nullOr types.int;
+ default = 60;
+ description = "Time in minutes between reloads.";
+ };
+
+ browser = mkOption {
+ type = types.str;
+ default = "${pkgs.xdg_utils}/bin/xdg-open";
+ description = "External browser to use.";
+ };
+
+ queries = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ example = { "foo" = ''rssurl =~ "example.com"''; };
+ description = "A list of queries to use.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration values that will be appended to the end.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.newsboat ];
+ home.file.".newsboat/urls".text = let
+ mkUrlEntry = u:
+ concatStringsSep " " ([ u.url ] ++ map wrapQuote u.tags
+ ++ optional (u.title != null) (wrapQuote "~${u.title}"));
+ urls = map mkUrlEntry cfg.urls;
+
+ mkQueryEntry = n: v: ''"query:${n}:${escape [ ''"'' ] v}"'';
+ queries = mapAttrsToList mkQueryEntry cfg.queries;
+ in concatStringsSep "\n"
+ (if versionAtLeast config.home.stateVersion "20.03" then
+ queries ++ urls
+ else
+ urls ++ queries) + "\n";
+
+ home.file.".newsboat/config".text = ''
+ max-items ${toString cfg.maxItems}
+ browser ${cfg.browser}
+ reload-threads ${toString cfg.reloadThreads}
+ auto-reload ${if cfg.autoReload then "yes" else "no"}
+ ${optionalString (cfg.reloadTime != null)
+ (toString "reload-time ${toString cfg.reloadTime}")}
+ prepopulate-query-feeds yes
+
+ ${cfg.extraConfig}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/noti.nix b/infra/libkookie/home-manager/modules/programs/noti.nix
new file mode 100644
index 000000000000..348555eef515
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/noti.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.noti;
+
+in {
+ meta.maintainers = [ maintainers.marsam ];
+
+ options.programs.noti = {
+ enable = mkEnableOption "Noti";
+
+ settings = mkOption {
+ type = types.attrsOf (types.attrsOf types.str);
+ default = { };
+ description = ''
+ Configuration written to
+ <filename>~/.config/noti/noti.yaml</filename>.
+ </para><para>
+ See
+ <citerefentry>
+ <refentrytitle>noti.yaml</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry>.
+ for the full list of options.
+ '';
+ example = literalExample ''
+ {
+ say = {
+ voice = "Alex";
+ };
+ slack = {
+ token = "1234567890abcdefg";
+ channel = "@jaime";
+ };
+ }
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.noti ];
+
+ xdg.configFile."noti/noti.yaml" =
+ mkIf (cfg.settings != { }) { text = generators.toYAML { } cfg.settings; };
+ };
+
+}
diff --git a/infra/libkookie/home-manager/modules/programs/notmuch.nix b/infra/libkookie/home-manager/modules/programs/notmuch.nix
new file mode 100644
index 000000000000..b93cc6a5ed8e
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/notmuch.nix
@@ -0,0 +1,198 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.notmuch;
+
+ mkIniKeyValue = key: value:
+ let
+ tweakVal = v:
+ if isString v then
+ v
+ else if isList v then
+ concatMapStringsSep ";" tweakVal v
+ else if isBool v then
+ (if v then "true" else "false")
+ else
+ toString v;
+ in "${key}=${tweakVal value}";
+
+ notmuchIni = recursiveUpdate {
+ database = { path = config.accounts.email.maildirBasePath; };
+
+ maildir = { synchronize_flags = cfg.maildir.synchronizeFlags; };
+
+ new = {
+ ignore = cfg.new.ignore;
+ tags = cfg.new.tags;
+ };
+
+ user = let
+ accounts = filter (a: a.notmuch.enable)
+ (attrValues config.accounts.email.accounts);
+ primary = filter (a: a.primary) accounts;
+ secondaries = filter (a: !a.primary) accounts;
+ in {
+ name = catAttrs "realName" primary;
+ primary_email = catAttrs "address" primary;
+ other_email = catAttrs "aliases" primary ++ catAttrs "address" secondaries
+ ++ catAttrs "aliases" secondaries;
+ };
+
+ search = { exclude_tags = cfg.search.excludeTags; };
+ } cfg.extraConfig;
+
+in {
+ options = {
+ programs.notmuch = {
+ enable = mkEnableOption "Notmuch mail indexer";
+
+ new = mkOption {
+ type = types.submodule {
+ options = {
+ ignore = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = ''
+ A list to specify files and directories that will not be
+ searched for messages by <command>notmuch new</command>.
+ '';
+ };
+
+ tags = mkOption {
+ type = types.listOf types.str;
+ default = [ "unread" "inbox" ];
+ example = [ "new" ];
+ description = ''
+ A list of tags that will be added to all messages
+ incorporated by <command>notmuch new</command>.
+ '';
+ };
+ };
+ };
+ default = { };
+ description = ''
+ Options related to email processing performed by
+ <command>notmuch new</command>.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.attrsOf (types.attrsOf types.str);
+ default = { };
+ description = ''
+ Options that should be appended to the notmuch configuration file.
+ '';
+ };
+
+ hooks = {
+ preNew = mkOption {
+ type = types.lines;
+ default = "";
+ example = "mbsync --all";
+ description = ''
+ Bash statements run before scanning or importing new
+ messages into the database.
+ '';
+ };
+
+ postNew = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ notmuch tag +nixos -- tag:new and from:nixos1@discoursemail.com
+ '';
+ description = ''
+ Bash statements run after new messages have been imported
+ into the database and initial tags have been applied.
+ '';
+ };
+
+ postInsert = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Bash statements run after a message has been inserted
+ into the database and initial tags have been applied.
+ '';
+ };
+ };
+
+ maildir = {
+ synchronizeFlags = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to synchronize Maildir flags.
+ '';
+ };
+ };
+
+ search = {
+ excludeTags = mkOption {
+ type = types.listOf types.str;
+ default = [ "deleted" "spam" ];
+ example = [ "trash" "spam" ];
+ description = ''
+ A list of tags that will be excluded from search results by
+ default. Using an excluded tag in a query will override that
+ exclusion.
+ '';
+ };
+ };
+ };
+
+ accounts.email.accounts = mkOption {
+ type = with types;
+ attrsOf (submodule {
+ options.notmuch.enable = mkEnableOption "notmuch indexing";
+ });
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [
+ {
+ assertion = notmuchIni.user.name != [ ];
+ message = "notmuch: Must have a user name set.";
+ }
+ {
+ assertion = notmuchIni.user.primary_email != [ ];
+ message = "notmuch: Must have a user primary email address set.";
+ }
+ ];
+
+ home.packages = [ pkgs.notmuch ];
+
+ home.sessionVariables = {
+ NOTMUCH_CONFIG = "${config.xdg.configHome}/notmuch/notmuchrc";
+ NMBGIT = "${config.xdg.dataHome}/notmuch/nmbug";
+ };
+
+ xdg.configFile."notmuch/notmuchrc".text =
+ let toIni = generators.toINI { mkKeyValue = mkIniKeyValue; };
+ in ''
+ # Generated by Home Manager.
+
+ '' + toIni notmuchIni;
+
+ home.file = let
+ hook = name: cmds: {
+ "${notmuchIni.database.path}/.notmuch/hooks/${name}".source =
+ pkgs.writeShellScript name ''
+ export PATH="${pkgs.notmuch}/bin''${PATH:+:}$PATH"
+ export NOTMUCH_CONFIG="${config.xdg.configHome}/notmuch/notmuchrc"
+ export NMBGIT="${config.xdg.dataHome}/notmuch/nmbug"
+
+ ${cmds}
+ '';
+ };
+ in optionalAttrs (cfg.hooks.preNew != "") (hook "pre-new" cfg.hooks.preNew)
+ // optionalAttrs (cfg.hooks.postNew != "")
+ (hook "post-new" cfg.hooks.postNew)
+ // optionalAttrs (cfg.hooks.postInsert != "")
+ (hook "post-insert" cfg.hooks.postInsert);
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/nushell.nix b/infra/libkookie/home-manager/modules/programs/nushell.nix
new file mode 100644
index 000000000000..1eb42f9515c2
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/nushell.nix
@@ -0,0 +1,68 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.nushell;
+
+ configFile = config:
+ pkgs.runCommand "config.toml" {
+ buildInputs = [ pkgs.remarshal ];
+ preferLocalBuild = true;
+ allowSubstitutes = false;
+ } ''
+ remarshal -if json -of toml \
+ < ${pkgs.writeText "config.json" (builtins.toJSON config)} \
+ > $out
+ '';
+
+in {
+ meta.maintainers = [ maintainers.Philipp-M ];
+
+ options.programs.nushell = {
+ enable = mkEnableOption "nushell";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.nushell;
+ defaultText = literalExample "pkgs.nushell";
+ description = "The package to use for nushell.";
+ };
+
+ settings = mkOption {
+ type = with types;
+ let
+ prim = oneOf [ bool int str ];
+ primOrPrimAttrs = either prim (attrsOf prim);
+ entry = either prim (listOf primOrPrimAttrs);
+ entryOrAttrsOf = t: either entry (attrsOf t);
+ entries = entryOrAttrsOf (entryOrAttrsOf entry);
+ in attrsOf entries // { description = "Nushell configuration"; };
+ default = { };
+ example = literalExample ''
+ {
+ edit_mode = "vi";
+ startup = [ "alias la [] { ls -a }" "alias e [msg] { echo $msg }" ];
+ key_timeout = 10;
+ completion_mode = "circular";
+ no_auto_pivot = true;
+ }
+ '';
+ description = ''
+ Configuration written to
+ <filename>~/.config/nushell/config.toml</filename>.
+ </para><para>
+ See <link xlink:href="https://www.nushell.sh/book/en/configuration.html" /> for the full list
+ of options.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+
+ xdg.configFile."nu/config.toml" =
+ mkIf (cfg.settings != { }) { source = configFile cfg.settings; };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/obs-studio.nix b/infra/libkookie/home-manager/modules/programs/obs-studio.nix
new file mode 100644
index 000000000000..6df5978384c7
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/obs-studio.nix
@@ -0,0 +1,47 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.obs-studio;
+ package = pkgs.obs-studio;
+
+ mkPluginEnv = packages:
+ let
+ pluginDirs = map (pkg: "${pkg}/share/obs/obs-plugins") packages;
+ plugins = concatMapStringsSep " " (p: "${p}/*") pluginDirs;
+ in pkgs.runCommand "obs-studio-plugins" {
+ preferLocalBuild = true;
+ allowSubstitutes = false;
+ } ''
+ mkdir $out
+ [[ '${plugins}' ]] || exit 0
+ for plugin in ${plugins}; do
+ ln -s "$plugin" $out/
+ done
+ '';
+
+in {
+ meta.maintainers = [ maintainers.adisbladis ];
+
+ options = {
+ programs.obs-studio = {
+ enable = mkEnableOption "obs-studio";
+
+ plugins = mkOption {
+ default = [ ];
+ example = literalExample "[ pkgs.obs-linuxbrowser ]";
+ description = "Optional OBS plugins.";
+ type = types.listOf types.package;
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ package ];
+
+ xdg.configFile."obs-studio/plugins" =
+ mkIf (cfg.plugins != [ ]) { source = mkPluginEnv cfg.plugins; };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/offlineimap-accounts.nix b/infra/libkookie/home-manager/modules/programs/offlineimap-accounts.nix
new file mode 100644
index 000000000000..afc7a0199724
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/offlineimap-accounts.nix
@@ -0,0 +1,51 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+
+ extraConfigType = with types; attrsOf (either (either str int) bool);
+
+in {
+ options.offlineimap = {
+ enable = mkEnableOption "OfflineIMAP";
+
+ extraConfig.account = mkOption {
+ type = extraConfigType;
+ default = { };
+ example = { autorefresh = 20; };
+ description = ''
+ Extra configuration options to add to the account section.
+ '';
+ };
+
+ extraConfig.local = mkOption {
+ type = extraConfigType;
+ default = { };
+ example = { sync_deletes = true; };
+ description = ''
+ Extra configuration options to add to the local account
+ section.
+ '';
+ };
+
+ extraConfig.remote = mkOption {
+ type = extraConfigType;
+ default = { };
+ example = {
+ maxconnections = 2;
+ expunge = false;
+ };
+ description = ''
+ Extra configuration options to add to the remote account
+ section.
+ '';
+ };
+
+ postSyncHookCommand = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Command to run after fetching new mails.";
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/offlineimap.nix b/infra/libkookie/home-manager/modules/programs/offlineimap.nix
new file mode 100644
index 000000000000..b6ba847e9b79
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/offlineimap.nix
@@ -0,0 +1,178 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.offlineimap;
+
+ accounts = filter (a: a.offlineimap.enable)
+ (attrValues config.accounts.email.accounts);
+
+ toIni = generators.toINI {
+ mkKeyValue = key: value:
+ let
+ value' = if isBool value then
+ (if value then "yes" else "no")
+ else
+ toString value;
+ in "${key} = ${value'}";
+ };
+
+ # Generates a script to fetch only a specific account.
+ #
+ # Note, these scripts are not actually created and installed at the
+ # moment. It will need some thinking on whether this is a good idea
+ # and whether other modules should have some similar functionality.
+ #
+ # Perhaps have a single tool `email` that wraps the command?
+ # Something like
+ #
+ # $ email <account name> <program name> <program args>
+ genOfflineImapScript = account:
+ with account;
+ pkgs.writeShellScriptBin "offlineimap-${name}" ''
+ exec ${pkgs.offlineimap}/bin/offlineimap -a${account.name} "$@"
+ '';
+
+ accountStr = account:
+ with account;
+ let
+ postSyncHook = optionalAttrs (offlineimap.postSyncHookCommand != "") {
+ postsynchook = pkgs.writeShellScriptBin "postsynchook"
+ offlineimap.postSyncHookCommand + "/bin/postsynchook";
+ };
+
+ localType =
+ if account.flavor == "gmail.com" then "GmailMaildir" else "Maildir";
+
+ remoteType = if account.flavor == "gmail.com" then "Gmail" else "IMAP";
+
+ remoteHost =
+ optionalAttrs (imap.host != null) { remotehost = imap.host; };
+
+ remotePort =
+ optionalAttrs ((imap.port or null) != null) { remoteport = imap.port; };
+
+ ssl = if imap.tls.enable then {
+ ssl = true;
+ sslcacertfile = imap.tls.certificatesFile;
+ starttls = imap.tls.useStartTls;
+ } else {
+ ssl = false;
+ };
+
+ remotePassEval =
+ let arglist = concatMapStringsSep "," (x: "'${x}'") passwordCommand;
+ in optionalAttrs (passwordCommand != null) {
+ remotepasseval = ''get_pass("${name}", [${arglist}])'';
+ };
+ in toIni {
+ "Account ${name}" = {
+ localrepository = "${name}-local";
+ remoterepository = "${name}-remote";
+ } // postSyncHook // offlineimap.extraConfig.account;
+
+ "Repository ${name}-local" = {
+ type = localType;
+ localfolders = maildir.absPath;
+ } // offlineimap.extraConfig.local;
+
+ "Repository ${name}-remote" = {
+ type = remoteType;
+ remoteuser = userName;
+ } // remoteHost // remotePort // remotePassEval // ssl
+ // offlineimap.extraConfig.remote;
+ };
+
+ extraConfigType = with types; attrsOf (either (either str int) bool);
+
+in {
+ options = {
+ programs.offlineimap = {
+ enable = mkEnableOption "OfflineIMAP";
+
+ pythonFile = mkOption {
+ type = types.lines;
+ default = ''
+ import subprocess
+
+ def get_pass(service, cmd):
+ return subprocess.check_output(cmd, )
+ '';
+ description = ''
+ Python code that can then be used in other parts of the
+ configuration.
+ '';
+ };
+
+ extraConfig.general = mkOption {
+ type = extraConfigType;
+ default = { };
+ example = {
+ maxage = 30;
+ ui = "blinkenlights";
+ };
+ description = ''
+ Extra configuration options added to the
+ <option>general</option> section.
+ '';
+ };
+
+ extraConfig.default = mkOption {
+ type = extraConfigType;
+ default = { };
+ example = { gmailtrashfolder = "[Gmail]/Papierkorb"; };
+ description = ''
+ Extra configuration options added to the
+ <option>DEFAULT</option> section.
+ '';
+ };
+
+ extraConfig.mbnames = mkOption {
+ type = extraConfigType;
+ default = { };
+ example = literalExample ''
+ {
+ filename = "~/.config/mutt/mailboxes";
+ header = "'mailboxes '";
+ peritem = "'+%(accountname)s/%(foldername)s'";
+ sep = "' '";
+ footer = "'\\n'";
+ }
+ '';
+ description = ''
+ Extra configuration options added to the
+ <code>mbnames</code> section.
+ '';
+ };
+ };
+
+ accounts.email.accounts = mkOption {
+ type = with types;
+ attrsOf (submodule (import ./offlineimap-accounts.nix));
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.offlineimap ];
+
+ xdg.configFile."offlineimap/get_settings.py".text = cfg.pythonFile;
+
+ xdg.configFile."offlineimap/config".text = ''
+ # Generated by Home Manager.
+ # See https://github.com/OfflineIMAP/offlineimap/blob/master/offlineimap.conf
+ # for an exhaustive list of options.
+ '' + toIni ({
+ general = {
+ accounts = concatMapStringsSep "," (a: a.name) accounts;
+ pythonfile = "${config.xdg.configHome}/offlineimap/get_settings.py";
+ metadata = "${config.xdg.dataHome}/offlineimap";
+ } // cfg.extraConfig.general;
+ } // optionalAttrs (cfg.extraConfig.mbnames != { }) {
+ mbnames = { enabled = true; } // cfg.extraConfig.mbnames;
+ } // optionalAttrs (cfg.extraConfig.default != { }) {
+ DEFAULT = cfg.extraConfig.default;
+ }) + "\n" + concatStringsSep "\n" (map accountStr accounts);
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/opam.nix b/infra/libkookie/home-manager/modules/programs/opam.nix
new file mode 100644
index 000000000000..a61ff7878dfe
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/opam.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.opam;
+
+in {
+ meta.maintainers = [ maintainers.marsam ];
+
+ options.programs.opam = {
+ enable = mkEnableOption "Opam";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.opam;
+ defaultText = literalExample "pkgs.opam";
+ description = "Opam package to install.";
+ };
+
+ enableBashIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Bash integration.
+ '';
+ };
+
+ enableZshIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Zsh integration.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+
+ programs.bash.initExtra = mkIf cfg.enableBashIntegration ''
+ eval "$(${cfg.package}/bin/opam env --shell=bash)"
+ '';
+
+ programs.zsh.initExtra = mkIf cfg.enableZshIntegration ''
+ eval "$(${cfg.package}/bin/opam env --shell=zsh)"
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/password-store.nix b/infra/libkookie/home-manager/modules/programs/password-store.nix
new file mode 100644
index 000000000000..db31146a1ba3
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/password-store.nix
@@ -0,0 +1,62 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.password-store;
+
+in {
+ meta.maintainers = with maintainers; [ pacien ];
+
+ options.programs.password-store = {
+ enable = mkEnableOption "Password store";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.pass;
+ defaultText = literalExample "pkgs.pass";
+ example = literalExample ''
+ pkgs.pass.withExtensions (exts: [ exts.pass-otp ])
+ '';
+ description = ''
+ The <literal>pass</literal> package to use.
+ Can be used to specify extensions.
+ '';
+ };
+
+ settings = mkOption rec {
+ type = with types; attrsOf str;
+ apply = mergeAttrs default;
+ default = {
+ PASSWORD_STORE_DIR = "${config.xdg.dataHome}/password-store";
+ };
+ defaultText = literalExample ''
+ { PASSWORD_STORE_DIR = "$XDG_DATA_HOME/password-store"; }
+ '';
+ example = literalExample ''
+ {
+ PASSWORD_STORE_DIR = "/some/directory";
+ PASSWORD_STORE_KEY = "12345678";
+ PASSWORD_STORE_CLIP_TIME = "60";
+ }
+ '';
+ description = ''
+ The <literal>pass</literal> environment variables dictionary.
+ </para><para>
+ See the "Environment variables" section of
+ <citerefentry>
+ <refentrytitle>pass</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ and the extension man pages for more information about the
+ available keys.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+ home.sessionVariables = cfg.settings;
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/pazi.nix b/infra/libkookie/home-manager/modules/programs/pazi.nix
new file mode 100644
index 000000000000..e1a08eb615a3
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/pazi.nix
@@ -0,0 +1,55 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.pazi;
+
+in {
+ meta.maintainers = [ maintainers.marsam ];
+
+ options.programs.pazi = {
+ enable = mkEnableOption "pazi";
+
+ enableBashIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Bash integration.
+ '';
+ };
+
+ enableZshIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Zsh integration.
+ '';
+ };
+
+ enableFishIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Fish integration.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.pazi ];
+
+ programs.bash.initExtra = mkIf cfg.enableBashIntegration ''
+ eval "$(${pkgs.pazi}/bin/pazi init bash)"
+ '';
+
+ programs.zsh.initExtra = mkIf cfg.enableZshIntegration ''
+ eval "$(${pkgs.pazi}/bin/pazi init zsh)"
+ '';
+
+ programs.fish.shellInit = mkIf cfg.enableFishIntegration ''
+ ${pkgs.pazi}/bin/pazi init fish | source
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/pet.nix b/infra/libkookie/home-manager/modules/programs/pet.nix
new file mode 100644
index 000000000000..0da205dab9f9
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/pet.nix
@@ -0,0 +1,88 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.pet;
+
+ format = pkgs.formats.toml { };
+
+ snippetType = types.submodule {
+ options = {
+ description = mkOption {
+ type = types.str;
+ default = "";
+ example = "Count the number of commits in the current branch";
+ description = ''
+ Description of the snippet.
+ '';
+ };
+
+ command = mkOption {
+ type = types.str;
+ default = "";
+ example = "git rev-list --count HEAD";
+ description = ''
+ The command.
+ '';
+ };
+
+ output = mkOption {
+ type = types.str;
+ default = "";
+ example = "473";
+ description = ''
+ Example output of the command.
+ '';
+ };
+ };
+ };
+
+in {
+ options.programs.pet = {
+ enable = mkEnableOption "pet";
+
+ settings = mkOption {
+ type = format.type;
+ default = { };
+ description = ''
+ Settings written to <filename>config.toml</filename>. See the pet
+ documentation for details.
+ '';
+ };
+
+ selectcmdPackage = mkOption {
+ type = types.package;
+ default = pkgs.fzf;
+ defaultText = literalExample "pkgs.fzf";
+ description = ''
+ The package needed for the <varname>settings.selectcmd</varname>.
+ '';
+ };
+
+ snippets = mkOption {
+ type = types.listOf snippetType;
+ default = [ ];
+ description = ''
+ The snippets.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ programs.pet.settings = {
+ selectcmd = mkDefault "fzf";
+ snippetfile = config.xdg.configHome + "/pet/snippet.toml";
+ };
+
+ home.packages = [ pkgs.pet cfg.selectcmdPackage ];
+
+ xdg.configFile = {
+ "pet/config.toml".source =
+ format.generate "config.toml" { General = cfg.settings; };
+ "pet/snippet.toml".source =
+ format.generate "snippet.toml" { snippets = cfg.snippets; };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/pidgin.nix b/infra/libkookie/home-manager/modules/programs/pidgin.nix
new file mode 100644
index 000000000000..a375fd1b2bd9
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/pidgin.nix
@@ -0,0 +1,34 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.pidgin;
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ programs.pidgin = {
+ enable = mkEnableOption "Pidgin messaging client";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.pidgin;
+ defaultText = literalExample "pkgs.pidgin";
+ description = "The Pidgin package to use.";
+ };
+
+ plugins = mkOption {
+ default = [ ];
+ example = literalExample "[ pkgs.pidgin-otr pkgs.pidgin-osd ]";
+ description = "Plugins that should be available to Pidgin.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ (cfg.package.override { inherit (cfg) plugins; }) ];
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/powerline-go.nix b/infra/libkookie/home-manager/modules/programs/powerline-go.nix
new file mode 100644
index 000000000000..8f5db8f260e1
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/powerline-go.nix
@@ -0,0 +1,144 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.powerline-go;
+
+ # Convert an option value to a string to be passed as argument to
+ # powerline-go:
+ valueToString = value:
+ if builtins.isList value then
+ builtins.concatStringsSep "," (builtins.map valueToString value)
+ else if builtins.isAttrs value then
+ valueToString
+ (mapAttrsToList (key: val: "${valueToString key}=${valueToString val}")
+ value)
+ else
+ builtins.toString value;
+
+ modulesArgument = optionalString (cfg.modules != null)
+ "-modules ${valueToString cfg.modules}";
+
+ newlineArgument = optionalString cfg.newline "-newline";
+
+ pathAliasesArgument = optionalString (cfg.pathAliases != null)
+ "-path-aliases ${valueToString cfg.pathAliases}";
+
+ otherSettingPairArgument = name: value:
+ if value == true then "-${name}" else "-${name} ${valueToString value}";
+
+ otherSettingsArgument = optionalString (cfg.settings != { })
+ (concatStringsSep " "
+ (mapAttrsToList otherSettingPairArgument cfg.settings));
+
+ commandLineArguments = ''
+ ${modulesArgument} ${newlineArgument} ${pathAliasesArgument} ${otherSettingsArgument}
+ '';
+
+in {
+ meta.maintainers = [ maintainers.DamienCassou ];
+
+ options = {
+ programs.powerline-go = {
+ enable = mkEnableOption
+ "Powerline-go, a beautiful and useful low-latency prompt for your shell";
+
+ modules = mkOption {
+ default = null;
+ type = types.nullOr (types.listOf types.str);
+ description = ''
+ List of module names to load. The list of all available
+ modules as well as the choice of default ones are at
+ <link xlink:href="https://github.com/justjanne/powerline-go"/>.
+ '';
+ example = [ "host" "ssh" "cwd" "gitlite" "jobs" "exit" ];
+ };
+
+ newline = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Set to true if the prompt should be on a line of its own.
+ '';
+ example = true;
+ };
+
+ pathAliases = mkOption {
+ default = null;
+ type = types.nullOr (types.attrsOf types.str);
+ description = ''
+ Pairs of full-path and corresponding desired short name. You
+ may use '~' to represent your home directory but you should
+ protect it to avoid shell substitution.
+ '';
+ example = literalExample ''
+ { "\\~/projects/home-manager" = "prj:home-manager"; }
+ '';
+ };
+
+ settings = mkOption {
+ default = { };
+ type = with types; attrsOf (oneOf [ bool int str (listOf str) ]);
+ description = ''
+ This can be any key/value pair as described in
+ <link xlink:href="https://github.com/justjanne/powerline-go"/>.
+ '';
+ example = literalExample ''
+ {
+ hostname-only-if-ssh = true;
+ numeric-exit-codes = true;
+ cwd-max-depth = 7;
+ ignore-repos = [ "/home/me/big-project" "/home/me/huge-project" ];
+ }
+ '';
+ };
+
+ extraUpdatePS1 = mkOption {
+ default = "";
+ description = "Shell code to execute after the prompt is set.";
+ example = ''
+ PS1=$PS1"NixOS> ";
+ '';
+ type = types.str;
+ };
+ };
+ };
+
+ config = {
+ programs.bash.initExtra =
+ mkIf (cfg.enable && config.programs.bash.enable) ''
+ function _update_ps1() {
+ local old_exit_status=$?
+ PS1="$(${pkgs.powerline-go}/bin/powerline-go -error $old_exit_status ${commandLineArguments})"
+ ${cfg.extraUpdatePS1}
+ return $old_exit_status
+ }
+
+ if [ "$TERM" != "linux" ]; then
+ PROMPT_COMMAND="_update_ps1;$PROMPT_COMMAND"
+ fi
+ '';
+
+ programs.zsh.initExtra = mkIf (cfg.enable && config.programs.zsh.enable) ''
+ function powerline_precmd() {
+ PS1="$(${pkgs.powerline-go}/bin/powerline-go -error $? -shell zsh ${commandLineArguments})"
+ ${cfg.extraUpdatePS1}
+ }
+
+ function install_powerline_precmd() {
+ for s in "$\{precmd_functions[@]}"; do
+ if [ "$s" = "powerline_precmd" ]; then
+ return
+ fi
+ done
+ precmd_functions+=(powerline_precmd)
+ }
+
+ if [ "$TERM" != "linux" ]; then
+ install_powerline_precmd
+ fi
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/qutebrowser.nix b/infra/libkookie/home-manager/modules/programs/qutebrowser.nix
new file mode 100644
index 000000000000..282861d90f89
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/qutebrowser.nix
@@ -0,0 +1,268 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.qutebrowser;
+
+ formatLine = o: n: v:
+ let
+ formatValue = v:
+ if builtins.isNull v then
+ "None"
+ else if builtins.isBool v then
+ (if v then "True" else "False")
+ else if builtins.isString v then
+ ''"${v}"''
+ else if builtins.isList v then
+ "[${concatStringsSep ", " (map formatValue v)}]"
+ else
+ builtins.toString v;
+ in if builtins.isAttrs v then
+ concatStringsSep "\n" (mapAttrsToList (formatLine "${o}${n}.") v)
+ else
+ "${o}${n} = ${formatValue v}";
+
+ formatDictLine = o: n: v: ''${o}['${n}'] = "${v}"'';
+
+ formatKeyBindings = m: b:
+ let
+ formatKeyBinding = m: k: c:
+ ''config.bind("${k}", "${escape [ ''"'' ] c}", mode="${m}")'';
+ in concatStringsSep "\n" (mapAttrsToList (formatKeyBinding m) b);
+
+in {
+ options.programs.qutebrowser = {
+ enable = mkEnableOption "qutebrowser";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.qutebrowser;
+ defaultText = literalExample "pkgs.qutebrowser";
+ description = "Qutebrowser package to install.";
+ };
+
+ aliases = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ description = ''
+ Aliases for commands.
+ '';
+ };
+
+ searchEngines = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ description = ''
+ Search engines that can be used via the address bar. Maps a search
+ engine name (such as <literal>DEFAULT</literal>, or
+ <literal>ddg</literal>) to a URL with a <literal>{}</literal>
+ placeholder. The placeholder will be replaced by the search term, use
+ <literal>{{</literal> and <literal>}}</literal> for literal
+ <literal>{/}</literal> signs. The search engine named
+ <literal>DEFAULT</literal> is used when
+ <literal>url.auto_search</literal> is turned on and something else than
+ a URL was entered to be opened. Other search engines can be used by
+ prepending the search engine name to the search term, for example
+ <literal>:open google qutebrowser</literal>.
+ '';
+ example = literalExample ''
+ {
+ w = "https://en.wikipedia.org/wiki/Special:Search?search={}&go=Go&ns0=1";
+ aw = "https://wiki.archlinux.org/?search={}";
+ nw = "https://nixos.wiki/index.php?search={}";
+ g = "https://www.google.com/search?hl=en&q={}";
+ }
+ '';
+ };
+
+ settings = mkOption {
+ type = types.attrsOf types.anything;
+ default = { };
+ description = ''
+ Options to add to qutebrowser <filename>config.py</filename> file.
+ See <link xlink:href="https://qutebrowser.org/doc/help/settings.html"/>
+ for options.
+ '';
+ example = literalExample ''
+ {
+ colors = {
+ hints = {
+ bg = "#000000";
+ fg = "#ffffff";
+ };
+ tabs.bar.bg = "#000000";
+ };
+ tabs.tabs_are_windows = true;
+ }
+ '';
+ };
+
+ keyMappings = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ description = ''
+ This setting can be used to map keys to other keys. When the key used
+ as dictionary-key is pressed, the binding for the key used as
+ dictionary-value is invoked instead. This is useful for global
+ remappings of keys, for example to map Ctrl-[ to Escape. Note that when
+ a key is bound (via <literal>bindings.default</literal> or
+ <literal>bindings.commands</literal>), the mapping is ignored.
+ '';
+ };
+
+ enableDefaultBindings = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Disable to prevent loading default key bindings.
+ '';
+ };
+
+ keyBindings = mkOption {
+ type = types.attrsOf (types.attrsOf types.str);
+ default = { };
+ description = ''
+ Key bindings mapping keys to commands in different modes. This setting
+ is a dictionary containing mode names and dictionaries mapping keys to
+ commands: <literal>{mode: {key: command}}</literal> If you want to map
+ a key to another key, check the <literal>keyMappings</literal> setting
+ instead. For modifiers, you can use either <literal>-</literal> or
+ <literal>+</literal> as delimiters, and these names:
+
+ <itemizedlist>
+ <listitem><para>
+ Control: <literal>Control</literal>, <literal>Ctrl</literal>
+ </para></listitem>
+ <listitem><para>
+ Meta: <literal>Meta</literal>, <literal>Windows</literal>,
+ <literal>Mod4</literal>
+ </para></listitem>
+ <listitem><para>
+ Alt: <literal>Alt</literal>, <literal>Mod1</literal>
+ </para></listitem>
+ <listitem><para>
+ Shift: <literal>Shift</literal>
+ </para></listitem>
+ </itemizedlist>
+
+ For simple keys (no <literal>&lt;&gt;</literal>-signs), a capital
+ letter means the key is pressed with Shift. For special keys (with
+ <literal>&lt;&gt;</literal>-signs), you need to explicitly add
+ <literal>Shift-</literal> to match a key pressed with shift. If you
+ want a binding to do nothing, bind it to the <literal>nop</literal>
+ command. If you want a default binding to be passed through to the
+ website, bind it to null. Note that some commands which are only useful
+ for bindings (but not used interactively) are hidden from the command
+ completion. See <literal>:</literal>help for a full list of available
+ commands. The following modes are available:
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>normal</literal></term>
+ <listitem><para>
+ Default mode, where most commands are invoked.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>insert</literal></term>
+ <listitem><para>
+ Entered when an input field is focused on a website, or by
+ pressing i in normal mode. Passes through almost all keypresses
+ to the website, but has some bindings like
+ <literal>&lt;Ctrl-e&gt;</literal> to open an external editor.
+ Note that single keys can’t be bound in this mode.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>hint</literal></term>
+ <listitem><para>
+ Entered when f is pressed to select links with the keyboard. Note
+ that single keys can’t be bound in this mode.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>passthrough</literal></term>
+ <listitem><para>
+ Similar to insert mode, but passes through all keypresses except
+ <literal>&lt;Escape&gt;</literal> to leave the mode. It might be
+ useful to bind <literal>&lt;Escape&gt;</literal> to some other
+ key in this mode if you want to be able to send an Escape key to
+ the website as well. Note that single keys can’t be bound in this
+ mode.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>command</literal></term>
+ <listitem><para>
+ Entered when pressing the : key in order to enter a command. Note
+ that single keys can’t be bound in this mode.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>prompt</literal></term>
+ <listitem><para>
+ Entered when there’s a prompt to display, like for download
+ locations or when invoked from JavaScript.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>yesno</literal></term>
+ <listitem><para>
+ Entered when there’s a yes/no prompt displayed.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>caret</literal></term>
+ <listitem><para>
+ Entered when pressing the v mode, used to select text using the
+ keyboard.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>register</literal></term>
+ <listitem><para>
+ Entered when qutebrowser is waiting for a register name/key for
+ commands like <literal>:set-mark</literal>.
+ </para></listitem>
+ </varlistentry>
+ </variablelist>
+ '';
+ example = literalExample ''
+ {
+ normal = {
+ "<Ctrl-v>" = "spawn mpv {url}";
+ ",p" = "spawn --userscript qute-pass";
+ ",l" = '''config-cycle spellcheck.languages ["en-GB"] ["en-US"]''';
+ };
+ prompt = {
+ "<Ctrl-y>" = "prompt-yes";
+ };
+ }
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra lines added to qutebrowser <filename>config.py</filename> file.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+
+ xdg.configFile."qutebrowser/config.py".text = concatStringsSep "\n" ([ ]
+ ++ mapAttrsToList (formatLine "c.") cfg.settings
+ ++ mapAttrsToList (formatDictLine "c.aliases") cfg.aliases
+ ++ mapAttrsToList (formatDictLine "c.url.searchengines") cfg.searchEngines
+ ++ mapAttrsToList (formatDictLine "c.bindings.key_mappings")
+ cfg.keyMappings
+ ++ optional (!cfg.enableDefaultBindings) "c.bindings.default = {}"
+ ++ mapAttrsToList formatKeyBindings cfg.keyBindings
+ ++ optional (cfg.extraConfig != "") cfg.extraConfig);
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/readline.nix b/infra/libkookie/home-manager/modules/programs/readline.nix
new file mode 100644
index 000000000000..2f79df6e1037
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/readline.nix
@@ -0,0 +1,77 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.readline;
+
+ mkSetVariableStr = n: v:
+ let
+ mkValueStr = v:
+ if v == true then
+ "on"
+ else if v == false then
+ "off"
+ else if isInt v then
+ toString v
+ else if isString v then
+ v
+ else
+ abort ("values ${toPretty v} is of unsupported type");
+ in "set ${n} ${mkValueStr v}";
+
+ mkBindingStr = k: v: ''"${k}": ${v}'';
+
+in {
+ options.programs.readline = {
+ enable = mkEnableOption "readline";
+
+ bindings = mkOption {
+ default = { };
+ type = types.attrsOf types.str;
+ example = literalExample ''
+ { "\\C-h" = "backward-kill-word"; }
+ '';
+ description = "Readline bindings.";
+ };
+
+ variables = mkOption {
+ type = with types; attrsOf (either str (either int bool));
+ default = { };
+ example = { expand-tilde = true; };
+ description = ''
+ Readline customization variable assignments.
+ '';
+ };
+
+ includeSystemConfig = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to include the system-wide configuration.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Configuration lines appended unchanged to the end of the
+ <filename>~/.inputrc</filename> file.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.file.".inputrc".text = let
+ configStr = concatStringsSep "\n"
+ (optional cfg.includeSystemConfig "$include /etc/inputrc"
+ ++ mapAttrsToList mkSetVariableStr cfg.variables
+ ++ mapAttrsToList mkBindingStr cfg.bindings);
+ in ''
+ # Generated by Home Manager.
+
+ ${configStr}
+ ${cfg.extraConfig}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/rofi-pass.nix b/infra/libkookie/home-manager/modules/programs/rofi-pass.nix
new file mode 100644
index 000000000000..da75299e6736
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/rofi-pass.nix
@@ -0,0 +1,46 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.rofi.pass;
+
+in {
+ meta.maintainers = [ maintainers.seylerius ];
+
+ options.programs.rofi.pass = {
+ enable = mkEnableOption "rofi integration with password-store";
+
+ stores = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = ''
+ Directory roots of your password-stores.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ URL_field='url'
+ USERNAME_field='user'
+ AUTOTYPE_field='autotype'
+ '';
+ description = ''
+ Extra configuration to be added at to the rofi-pass config file.
+ Additional examples can be found at
+ <link xlink:href="https://github.com/carnager/rofi-pass/blob/master/config.example"/>.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.rofi-pass ];
+
+ xdg.configFile."rofi-pass/config".text = optionalString (cfg.stores != [ ])
+ ("root=" + (concatStringsSep ":" cfg.stores) + "\n") + cfg.extraConfig
+ + optionalString (cfg.extraConfig != "") "\n";
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/rofi.nix b/infra/libkookie/home-manager/modules/programs/rofi.nix
new file mode 100644
index 000000000000..734bcc423e66
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/rofi.nix
@@ -0,0 +1,338 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+with builtins;
+
+let
+
+ cfg = config.programs.rofi;
+
+ colorOption = description:
+ mkOption {
+ type = types.str;
+ description = description;
+ };
+
+ rowColorSubmodule = types.submodule {
+ options = {
+ background = colorOption "Background color";
+ foreground = colorOption "Foreground color";
+ backgroundAlt = colorOption "Alternative background color";
+ highlight = mkOption {
+ type = types.submodule {
+ options = {
+ background = colorOption "Highlight background color";
+ foreground = colorOption "Highlight foreground color";
+ };
+ };
+ description = "Color settings for highlighted row.";
+ };
+ };
+ };
+
+ windowColorSubmodule = types.submodule {
+ options = {
+ background = colorOption "Window background color";
+ border = colorOption "Window border color";
+ separator = colorOption "Separator color";
+ };
+ };
+
+ colorsSubmodule = types.submodule {
+ options = {
+ window = mkOption {
+ default = null;
+ type = windowColorSubmodule;
+ description = "Window color settings.";
+ };
+ rows = mkOption {
+ default = null;
+ type = types.submodule {
+ options = {
+ normal = mkOption {
+ default = null;
+ type = types.nullOr rowColorSubmodule;
+ description = "Normal row color settings.";
+ };
+ active = mkOption {
+ default = null;
+ type = types.nullOr rowColorSubmodule;
+ description = "Active row color settings.";
+ };
+ urgent = mkOption {
+ default = null;
+ type = types.nullOr rowColorSubmodule;
+ description = "Urgent row color settings.";
+ };
+ };
+ };
+ description = "Rows color settings.";
+ };
+ };
+ };
+
+ valueToString = value:
+ if isBool value then (if value then "true" else "else") else toString value;
+
+ windowColorsToString = window:
+ concatStringsSep ", " (with window; [ background border separator ]);
+
+ rowsColorsToString = rows: ''
+ ${optionalString (rows.normal != null)
+ (setOption "color-normal" (rowColorsToString rows.normal))}
+ ${optionalString (rows.active != null)
+ (setOption "color-active" (rowColorsToString rows.active))}
+ ${optionalString (rows.urgent != null)
+ (setOption "color-urgent" (rowColorsToString rows.urgent))}
+ '';
+
+ rowColorsToString = row:
+ concatStringsSep ", " (with row; [
+ background
+ foreground
+ backgroundAlt
+ highlight.background
+ highlight.foreground
+ ]);
+
+ setOption = name: value:
+ optionalString (value != null) "rofi.${name}: ${valueToString value}";
+
+ setColorScheme = colors:
+ optionalString (colors != null) ''
+ ${optionalString (colors.window != null) setOption "color-window"
+ (windowColorsToString colors.window)}
+ ${optionalString (colors.rows != null) (rowsColorsToString colors.rows)}
+ '';
+
+ locationsMap = {
+ center = 0;
+ top-left = 1;
+ top = 2;
+ top-right = 3;
+ right = 4;
+ bottom-right = 5;
+ bottom = 6;
+ bottom-left = 7;
+ left = 8;
+ };
+
+ themeName = if (cfg.theme == null) then
+ null
+ else if (lib.isString cfg.theme) then
+ cfg.theme
+ else
+ lib.removeSuffix ".rasi" (baseNameOf cfg.theme);
+
+ themePath = if (lib.isString cfg.theme) then null else cfg.theme;
+
+in {
+ options.programs.rofi = {
+ enable = mkEnableOption
+ "Rofi: A window switcher, application launcher and dmenu replacement";
+
+ package = mkOption {
+ default = pkgs.rofi;
+ type = types.package;
+ description = ''
+ Package providing the <command>rofi</command> binary.
+ '';
+ example = literalExample ''
+ pkgs.rofi.override { plugins = [ pkgs.rofi-emoji ]; };
+ '';
+ };
+
+ width = mkOption {
+ default = null;
+ type = types.nullOr types.int;
+ description = "Window width";
+ example = 100;
+ };
+
+ lines = mkOption {
+ default = null;
+ type = types.nullOr types.int;
+ description = "Number of lines";
+ example = 10;
+ };
+
+ borderWidth = mkOption {
+ default = null;
+ type = types.nullOr types.int;
+ description = "Border width";
+ example = 1;
+ };
+
+ rowHeight = mkOption {
+ default = null;
+ type = types.nullOr types.int;
+ description = "Row height (in chars)";
+ example = 1;
+ };
+
+ padding = mkOption {
+ default = null;
+ type = types.nullOr types.int;
+ description = "Padding";
+ example = 400;
+ };
+
+ font = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ example = "Droid Sans Mono 14";
+ description = "Font to use.";
+ };
+
+ scrollbar = mkOption {
+ default = null;
+ type = types.nullOr types.bool;
+ description = "Whether to show a scrollbar.";
+ };
+
+ terminal = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ Path to the terminal which will be used to run console applications
+ '';
+ example = "\${pkgs.gnome3.gnome_terminal}/bin/gnome-terminal";
+ };
+
+ separator = mkOption {
+ default = null;
+ type = types.nullOr (types.enum [ "none" "dash" "solid" ]);
+ description = "Separator style";
+ example = "solid";
+ };
+
+ cycle = mkOption {
+ default = null;
+ type = types.nullOr types.bool;
+ description = "Whether to cycle through the results list.";
+ };
+
+ fullscreen = mkOption {
+ default = null;
+ type = types.nullOr types.bool;
+ description = "Whether to run rofi fullscreen.";
+ };
+
+ location = mkOption {
+ default = "center";
+ type = types.enum (builtins.attrNames locationsMap);
+ description = "The location rofi appears on the screen.";
+ };
+
+ xoffset = mkOption {
+ default = 0;
+ type = types.int;
+ description = ''
+ Offset in the x-axis in pixels relative to the chosen location.
+ '';
+ };
+
+ yoffset = mkOption {
+ default = 0;
+ type = types.int;
+ description = ''
+ Offset in the y-axis in pixels relative to the chosen location.
+ '';
+ };
+
+ colors = mkOption {
+ default = null;
+ type = types.nullOr colorsSubmodule;
+ description = ''
+ Color scheme settings. Colors can be specified in CSS color
+ formats. This option may become deprecated in the future and
+ therefore the <varname>programs.rofi.theme</varname> option
+ should be used whenever possible.
+ '';
+ example = literalExample ''
+ colors = {
+ window = {
+ background = "argb:583a4c54";
+ border = "argb:582a373e";
+ separator = "#c3c6c8";
+ };
+
+ rows = {
+ normal = {
+ background = "argb:58455a64";
+ foreground = "#fafbfc";
+ backgroundAlt = "argb:58455a64";
+ highlight = {
+ background = "#00bcd4";
+ foreground = "#fafbfc";
+ };
+ };
+ };
+ };
+ '';
+ };
+
+ theme = mkOption {
+ default = null;
+ type = with types; nullOr (either str path);
+ example = "Arc";
+ description = ''
+ Name of theme or path to theme file in rasi format. Available
+ named themes can be viewed using the
+ <command>rofi-theme-selector</command> tool.
+ '';
+ };
+
+ configPath = mkOption {
+ default = "${config.xdg.configHome}/rofi/config";
+ defaultText = "$XDG_CONFIG_HOME/rofi/config";
+ type = types.str;
+ description = "Path where to put generated configuration file.";
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = "Additional configuration to add.";
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [{
+ assertion = cfg.theme == null || cfg.colors == null;
+ message = ''
+ Cannot use the rofi options 'theme' and 'colors' simultaneously.
+ '';
+ }];
+
+ home.packages = [ cfg.package ];
+
+ home.file."${cfg.configPath}".text = ''
+ ${setOption "width" cfg.width}
+ ${setOption "lines" cfg.lines}
+ ${setOption "font" cfg.font}
+ ${setOption "bw" cfg.borderWidth}
+ ${setOption "eh" cfg.rowHeight}
+ ${setOption "padding" cfg.padding}
+ ${setOption "separator-style" cfg.separator}
+ ${setOption "hide-scrollbar"
+ (if (cfg.scrollbar != null) then (!cfg.scrollbar) else cfg.scrollbar)}
+ ${setOption "terminal" cfg.terminal}
+ ${setOption "cycle" cfg.cycle}
+ ${setOption "fullscreen" cfg.fullscreen}
+ ${setOption "location" (builtins.getAttr cfg.location locationsMap)}
+ ${setOption "xoffset" cfg.xoffset}
+ ${setOption "yoffset" cfg.yoffset}
+
+ ${setColorScheme cfg.colors}
+ ${setOption "theme" themeName}
+
+ ${cfg.extraConfig}
+ '';
+
+ xdg.dataFile = mkIf (themePath != null) {
+ "rofi/themes/${themeName}.rasi".source = themePath;
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/rtorrent.nix b/infra/libkookie/home-manager/modules/programs/rtorrent.nix
new file mode 100644
index 000000000000..7beeb2e4221f
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/rtorrent.nix
@@ -0,0 +1,34 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.rtorrent;
+
+in {
+ meta.maintainers = [ maintainers.marsam ];
+
+ options.programs.rtorrent = {
+ enable = mkEnableOption "rTorrent";
+
+ settings = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Configuration written to
+ <filename>~/.config/rtorrent/rtorrent.rc</filename>. See
+ <link xlink:href="https://github.com/rakshasa/rtorrent/wiki/Config-Guide" />
+ for explanation about possible values.
+ '';
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.rtorrent ];
+
+ xdg.configFile."rtorrent/rtorrent.rc" =
+ mkIf (cfg.settings != "") { text = cfg.settings; };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/skim.nix b/infra/libkookie/home-manager/modules/programs/skim.nix
new file mode 100644
index 000000000000..40377054bf99
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/skim.nix
@@ -0,0 +1,136 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.skim;
+
+in {
+ options.programs.skim = {
+ enable = mkEnableOption "skim - a command-line fuzzy finder";
+
+ defaultCommand = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "fd --type f";
+ description = ''
+ The command that gets executed as the default source for skim
+ when running.
+ '';
+ };
+
+ defaultOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "--height 40%" "--prompt ⟫" ];
+ description = ''
+ Extra command line options given to skim by default.
+ '';
+ };
+
+ fileWidgetCommand = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "fd --type f";
+ description = ''
+ The command that gets executed as the source for skim for the
+ CTRL-T keybinding.
+ '';
+ };
+
+ fileWidgetOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "--preview 'head {}'" ];
+ description = ''
+ Command line options for the CTRL-T keybinding.
+ '';
+ };
+
+ changeDirWidgetCommand = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "fd --type d";
+ description = ''
+ The command that gets executed as the source for skim for the
+ ALT-C keybinding.
+ '';
+ };
+
+ changeDirWidgetOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "--preview 'tree -C {} | head -200'" ];
+ description = ''
+ Command line options for the ALT-C keybinding.
+ '';
+ };
+
+ historyWidgetOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "--tac" "--exact" ];
+ description = ''
+ Command line options for the CTRL-R keybinding.
+ '';
+ };
+
+ enableBashIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Bash integration.
+ '';
+ };
+
+ enableZshIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Zsh integration.
+ '';
+ };
+
+ enableFishIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Fish integration.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.skim ];
+
+ home.sessionVariables = mapAttrs (n: v: toString v)
+ (filterAttrs (n: v: v != [ ] && v != null) {
+ SKIM_ALT_C_COMMAND = cfg.changeDirWidgetCommand;
+ SKIM_ALT_C_OPTS = cfg.changeDirWidgetOptions;
+ SKIM_CTRL_R_OPTS = cfg.historyWidgetOptions;
+ SKIM_CTRL_T_COMMAND = cfg.fileWidgetCommand;
+ SKIM_CTRL_T_OPTS = cfg.fileWidgetOptions;
+ SKIM_DEFAULT_COMMAND = cfg.defaultCommand;
+ SKIM_DEFAULT_OPTIONS = cfg.defaultOptions;
+ });
+
+ programs.bash.initExtra = mkIf cfg.enableBashIntegration ''
+ if [[ :$SHELLOPTS: =~ :(vi|emacs): ]]; then
+ . ${pkgs.skim}/share/skim/completion.bash
+ . ${pkgs.skim}/share/skim/key-bindings.bash
+ fi
+ '';
+
+ programs.zsh.initExtra = mkIf cfg.enableZshIntegration ''
+ if [[ $options[zle] = on ]]; then
+ . ${pkgs.skim}/share/skim/completion.zsh
+ . ${pkgs.skim}/share/skim/key-bindings.zsh
+ fi
+ '';
+
+ programs.fish.shellInit = mkIf cfg.enableFishIntegration ''
+ source ${pkgs.skim}/share/skim/key-bindings.fish && skim_key_bindings
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/ssh.nix b/infra/libkookie/home-manager/modules/programs/ssh.nix
new file mode 100644
index 000000000000..ae1f221803c4
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/ssh.nix
@@ -0,0 +1,492 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.ssh;
+
+ isPath = x: builtins.substring 0 1 (toString x) == "/";
+
+ addressPort = entry:
+ if isPath entry.address
+ then " ${entry.address}"
+ else " [${entry.address}]:${toString entry.port}";
+
+ yn = flag: if flag then "yes" else "no";
+
+ unwords = builtins.concatStringsSep " ";
+
+ bindOptions = {
+ address = mkOption {
+ type = types.str;
+ default = "localhost";
+ example = "example.org";
+ description = "The address where to bind the port.";
+ };
+
+ port = mkOption {
+ type = types.port;
+ example = 8080;
+ description = "Specifies port number to bind on bind address.";
+ };
+ };
+
+ dynamicForwardModule = types.submodule {
+ options = bindOptions;
+ };
+
+ forwardModule = types.submodule {
+ options = {
+ bind = bindOptions;
+
+ host = {
+ address = mkOption {
+ type = types.str;
+ example = "example.org";
+ description = "The address where to forward the traffic to.";
+ };
+
+ port = mkOption {
+ type = types.port;
+ example = 80;
+ description = "Specifies port number to forward the traffic to.";
+ };
+ };
+ };
+ };
+
+ matchBlockModule = types.submodule ({ dagName, ... }: {
+ options = {
+ host = mkOption {
+ type = types.str;
+ example = "*.example.org";
+ description = ''
+ The host pattern used by this conditional block.
+ '';
+ };
+
+ port = mkOption {
+ type = types.nullOr types.port;
+ default = null;
+ description = "Specifies port number to connect on remote host.";
+ };
+
+ forwardAgent = mkOption {
+ default = null;
+ type = types.nullOr types.bool;
+ description = ''
+ Whether the connection to the authentication agent (if any)
+ will be forwarded to the remote machine.
+ '';
+ };
+
+ forwardX11 = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Specifies whether X11 connections will be automatically redirected
+ over the secure channel and <envar>DISPLAY</envar> set.
+ '';
+ };
+
+ forwardX11Trusted = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Specifies whether remote X11 clients will have full access to the
+ original X11 display.
+ '';
+ };
+
+ identitiesOnly = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Specifies that ssh should only use the authentication
+ identity explicitly configured in the
+ <filename>~/.ssh/config</filename> files or passed on the
+ ssh command-line, even if <command>ssh-agent</command>
+ offers more identities.
+ '';
+ };
+
+ identityFile = mkOption {
+ type = with types; either (listOf str) (nullOr str);
+ default = [];
+ apply = p:
+ if p == null then []
+ else if isString p then [p]
+ else p;
+ description = ''
+ Specifies files from which the user identity is read.
+ Identities will be tried in the given order.
+ '';
+ };
+
+ user = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Specifies the user to log in as.";
+ };
+
+ hostname = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Specifies the real host name to log into.";
+ };
+
+ serverAliveInterval = mkOption {
+ type = types.int;
+ default = 0;
+ description =
+ "Set timeout in seconds after which response will be requested.";
+ };
+
+ serverAliveCountMax = mkOption {
+ type = types.ints.positive;
+ default = 3;
+ description = ''
+ Sets the number of server alive messages which may be sent
+ without SSH receiving any messages back from the server.
+ '';
+ };
+
+ sendEnv = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ description = ''
+ Environment variables to send from the local host to the
+ server.
+ '';
+ };
+
+ compression = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = ''
+ Specifies whether to use compression. Omitted from the host
+ block when <literal>null</literal>.
+ '';
+ };
+
+ checkHostIP = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Check the host IP address in the
+ <filename>known_hosts</filename> file.
+ '';
+ };
+
+ proxyCommand = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "The command to use to connect to the server.";
+ };
+
+ proxyJump = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "The proxy host to use to connect to the server.";
+ };
+
+ certificateFile = mkOption {
+ type = with types; either (listOf str) (nullOr str);
+ default = [];
+ apply = p:
+ if p == null then []
+ else if isString p then [p]
+ else p;
+ description = ''
+ Specifies files from which the user certificate is read.
+ '';
+ };
+
+ addressFamily = mkOption {
+ default = null;
+ type = types.nullOr (types.enum ["any" "inet" "inet6"]);
+ description = ''
+ Specifies which address family to use when connecting.
+ '';
+ };
+
+ localForwards = mkOption {
+ type = types.listOf forwardModule;
+ default = [];
+ example = literalExample ''
+ [
+ {
+ bind.port = 8080;
+ host.address = "10.0.0.13";
+ host.port = 80;
+ }
+ ];
+ '';
+ description = ''
+ Specify local port forwardings. See
+ <citerefentry>
+ <refentrytitle>ssh_config</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry> for <literal>LocalForward</literal>.
+ '';
+ };
+
+ remoteForwards = mkOption {
+ type = types.listOf forwardModule;
+ default = [];
+ example = literalExample ''
+ [
+ {
+ bind.port = 8080;
+ host.address = "10.0.0.13";
+ host.port = 80;
+ }
+ ];
+ '';
+ description = ''
+ Specify remote port forwardings. See
+ <citerefentry>
+ <refentrytitle>ssh_config</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry> for <literal>RemoteForward</literal>.
+ '';
+ };
+
+ dynamicForwards = mkOption {
+ type = types.listOf dynamicForwardModule;
+ default = [];
+ example = literalExample ''
+ [ { port = 8080; } ];
+ '';
+ description = ''
+ Specify dynamic port forwardings. See
+ <citerefentry>
+ <refentrytitle>ssh_config</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry> for <literal>DynamicForward</literal>.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = types.attrsOf types.str;
+ default = {};
+ description = "Extra configuration options for the host.";
+ };
+ };
+
+ config.host = mkDefault dagName;
+ });
+
+ matchBlockStr = cf: concatStringsSep "\n" (
+ ["Host ${cf.host}"]
+ ++ optional (cf.port != null) " Port ${toString cf.port}"
+ ++ optional (cf.forwardAgent != null) " ForwardAgent ${yn cf.forwardAgent}"
+ ++ optional cf.forwardX11 " ForwardX11 yes"
+ ++ optional cf.forwardX11Trusted " ForwardX11Trusted yes"
+ ++ optional cf.identitiesOnly " IdentitiesOnly yes"
+ ++ optional (cf.user != null) " User ${cf.user}"
+ ++ optional (cf.hostname != null) " HostName ${cf.hostname}"
+ ++ optional (cf.addressFamily != null) " AddressFamily ${cf.addressFamily}"
+ ++ optional (cf.sendEnv != []) " SendEnv ${unwords cf.sendEnv}"
+ ++ optional (cf.serverAliveInterval != 0)
+ " ServerAliveInterval ${toString cf.serverAliveInterval}"
+ ++ optional (cf.serverAliveCountMax != 3)
+ " ServerAliveCountMax ${toString cf.serverAliveCountMax}"
+ ++ optional (cf.compression != null) " Compression ${yn cf.compression}"
+ ++ optional (!cf.checkHostIP) " CheckHostIP no"
+ ++ optional (cf.proxyCommand != null) " ProxyCommand ${cf.proxyCommand}"
+ ++ optional (cf.proxyJump != null) " ProxyJump ${cf.proxyJump}"
+ ++ map (file: " IdentityFile ${file}") cf.identityFile
+ ++ map (file: " CertificateFile ${file}") cf.certificateFile
+ ++ map (f: " LocalForward" + addressPort f.bind + addressPort f.host) cf.localForwards
+ ++ map (f: " RemoteForward" + addressPort f.bind + addressPort f.host) cf.remoteForwards
+ ++ map (f: " DynamicForward" + addressPort f) cf.dynamicForwards
+ ++ mapAttrsToList (n: v: " ${n} ${v}") cf.extraOptions
+ );
+
+in
+
+{
+ meta.maintainers = [ maintainers.rycee ];
+
+ options.programs.ssh = {
+ enable = mkEnableOption "SSH client configuration";
+
+ forwardAgent = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether the connection to the authentication agent (if any)
+ will be forwarded to the remote machine.
+ '';
+ };
+
+ compression = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Specifies whether to use compression.";
+ };
+
+ serverAliveInterval = mkOption {
+ type = types.int;
+ default = 0;
+ description = ''
+ Set default timeout in seconds after which response will be requested.
+ '';
+ };
+
+ serverAliveCountMax = mkOption {
+ type = types.ints.positive;
+ default = 3;
+ description = ''
+ Sets the default number of server alive messages which may be
+ sent without SSH receiving any messages back from the server.
+ '';
+ };
+
+ hashKnownHosts = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Indicates that
+ <citerefentry>
+ <refentrytitle>ssh</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ should hash host names and addresses when they are added to
+ the known hosts file.
+ '';
+ };
+
+ userKnownHostsFile = mkOption {
+ type = types.str;
+ default = "~/.ssh/known_hosts";
+ description = ''
+ Specifies one or more files to use for the user host key
+ database, separated by whitespace. The default is
+ <filename>~/.ssh/known_hosts</filename>.
+ '';
+ };
+
+ controlMaster = mkOption {
+ default = "no";
+ type = types.enum ["yes" "no" "ask" "auto" "autoask"];
+ description = ''
+ Configure sharing of multiple sessions over a single network connection.
+ '';
+ };
+
+ controlPath = mkOption {
+ type = types.str;
+ default = "~/.ssh/master-%r@%n:%p";
+ description = ''
+ Specify path to the control socket used for connection sharing.
+ '';
+ };
+
+ controlPersist = mkOption {
+ type = types.str;
+ default = "no";
+ example = "10m";
+ description = ''
+ Whether control socket should remain open in the background.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration.
+ '';
+ };
+
+ extraOptionOverrides = mkOption {
+ type = types.attrsOf types.str;
+ default = {};
+ description = ''
+ Extra SSH configuration options that take precedence over any
+ host specific configuration.
+ '';
+ };
+
+ matchBlocks = mkOption {
+ type = hm.types.listOrDagOf matchBlockModule;
+ default = {};
+ example = literalExample ''
+ {
+ "john.example.com" = {
+ hostname = "example.com";
+ user = "john";
+ };
+ foo = lib.hm.dag.entryBefore ["john.example.com"] {
+ hostname = "example.com";
+ identityFile = "/home/john/.ssh/foo_rsa";
+ };
+ };
+ '';
+ description = ''
+ Specify per-host settings. Note, if the order of rules matter
+ then use the DAG functions to express the dependencies as
+ shown in the example.
+ </para><para>
+ See
+ <citerefentry>
+ <refentrytitle>ssh_config</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry>
+ for more information.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [
+ {
+ assertion =
+ let
+ # `builtins.any`/`lib.lists.any` does not return `true` if there are no elements.
+ any' = pred: items: if items == [] then true else any pred items;
+ # Check that if `entry.address` is defined, and is a path, that `entry.port` has not
+ # been defined.
+ noPathWithPort = entry: entry ? address && isPath entry.address -> !(entry ? port);
+ checkDynamic = block: any' noPathWithPort block.dynamicForwards;
+ checkBindAndHost = fwd: noPathWithPort fwd.bind && noPathWithPort fwd.host;
+ checkLocal = block: any' checkBindAndHost block.localForwards;
+ checkRemote = block: any' checkBindAndHost block.remoteForwards;
+ checkMatchBlock = block: all (fn: fn block) [ checkLocal checkRemote checkDynamic ];
+ in any' checkMatchBlock (map (block: block.data) (builtins.attrValues cfg.matchBlocks));
+ message = "Forwarded paths cannot have ports.";
+ }
+ ];
+
+ home.file.".ssh/config".text =
+ let
+ sortedMatchBlocks = hm.dag.topoSort cfg.matchBlocks;
+ sortedMatchBlocksStr = builtins.toJSON sortedMatchBlocks;
+ matchBlocks =
+ if sortedMatchBlocks ? result
+ then sortedMatchBlocks.result
+ else abort "Dependency cycle in SSH match blocks: ${sortedMatchBlocksStr}";
+ in ''
+ ${concatStringsSep "\n" (
+ mapAttrsToList (n: v: "${n} ${v}") cfg.extraOptionOverrides)}
+
+ ${concatStringsSep "\n\n" (map (block: matchBlockStr block.data) matchBlocks)}
+
+ Host *
+ ForwardAgent ${yn cfg.forwardAgent}
+ Compression ${yn cfg.compression}
+ ServerAliveInterval ${toString cfg.serverAliveInterval}
+ ServerAliveCountMax ${toString cfg.serverAliveCountMax}
+ HashKnownHosts ${yn cfg.hashKnownHosts}
+ UserKnownHostsFile ${cfg.userKnownHostsFile}
+ ControlMaster ${cfg.controlMaster}
+ ControlPath ${cfg.controlPath}
+ ControlPersist ${cfg.controlPersist}
+
+ ${replaceStrings ["\n"] ["\n "] cfg.extraConfig}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/starship.nix b/infra/libkookie/home-manager/modules/programs/starship.nix
new file mode 100644
index 000000000000..8462d331501a
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/starship.nix
@@ -0,0 +1,109 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.starship;
+
+ configFile = config:
+ pkgs.runCommand "config.toml" {
+ buildInputs = [ pkgs.remarshal ];
+ preferLocalBuild = true;
+ allowSubstitutes = false;
+ } ''
+ remarshal -if json -of toml \
+ < ${pkgs.writeText "config.json" (builtins.toJSON config)} \
+ > $out
+ '';
+
+in {
+ meta.maintainers = [ maintainers.marsam ];
+
+ options.programs.starship = {
+ enable = mkEnableOption "starship";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.starship;
+ defaultText = literalExample "pkgs.starship";
+ description = "The package to use for the starship binary.";
+ };
+
+ settings = mkOption {
+ type = with types;
+ let
+ prim = either bool (either int str);
+ primOrPrimAttrs = either prim (attrsOf prim);
+ entry = either prim (listOf primOrPrimAttrs);
+ entryOrAttrsOf = t: either entry (attrsOf t);
+ entries = entryOrAttrsOf (entryOrAttrsOf entry);
+ in attrsOf entries // { description = "Starship configuration"; };
+ default = { };
+ example = literalExample ''
+ {
+ add_newline = false;
+ prompt_order = [ "line_break" "package" "line_break" "character" ];
+ scan_timeout = 10;
+ character.symbol = "➜";
+ }
+ '';
+ description = ''
+ Configuration written to
+ <filename>~/.config/starship.toml</filename>.
+ </para><para>
+ See <link xlink:href="https://starship.rs/config/" /> for the full list
+ of options.
+ '';
+ };
+
+ enableBashIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Bash integration.
+ '';
+ };
+
+ enableZshIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Zsh integration.
+ '';
+ };
+
+ enableFishIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Fish integration.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+
+ xdg.configFile."starship.toml" =
+ mkIf (cfg.settings != { }) { source = configFile cfg.settings; };
+
+ programs.bash.initExtra = mkIf cfg.enableBashIntegration ''
+ if [[ $TERM != "dumb" && (-z $INSIDE_EMACS || $INSIDE_EMACS == "vterm") ]]; then
+ eval "$(${cfg.package}/bin/starship init bash)"
+ fi
+ '';
+
+ programs.zsh.initExtra = mkIf cfg.enableZshIntegration ''
+ if [ -z "$INSIDE_EMACS" ]; then
+ eval "$(${cfg.package}/bin/starship init zsh)"
+ fi
+ '';
+
+ programs.fish.promptInit = mkIf cfg.enableFishIntegration ''
+ if test "$TERM" != "dumb" -a \( -z "$INSIDE_EMACS" -o "$INSIDE_EMACS" = "vterm" \)
+ eval (${cfg.package}/bin/starship init fish)
+ end
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/taskwarrior.nix b/infra/libkookie/home-manager/modules/programs/taskwarrior.nix
new file mode 100644
index 000000000000..6a887e0f5b18
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/taskwarrior.nix
@@ -0,0 +1,112 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.taskwarrior;
+
+ themePath = theme: "${pkgs.taskwarrior}/share/doc/task/rc/${theme}.theme";
+
+ includeTheme = location:
+ if location == null then
+ ""
+ else if isString location then
+ "include ${themePath location}"
+ else
+ "include ${location}";
+
+ formatValue = value:
+ if isBool value then
+ if value then "true" else "false"
+ else if isList value then
+ concatMapStringsSep "," formatValue value
+ else
+ toString value;
+
+ formatLine = key: value: "${key}=${formatValue value}";
+
+ formatSet = key: values:
+ (concatStringsSep "\n"
+ (mapAttrsToList (subKey: subValue: formatPair "${key}.${subKey}" subValue)
+ values));
+
+ formatPair = key: value:
+ if isAttrs value then formatSet key value else formatLine key value;
+
+in {
+ options = {
+ programs.taskwarrior = {
+ enable = mkEnableOption "Task Warrior";
+
+ config = mkOption {
+ type = types.attrsOf types.anything;
+ default = { };
+ example = literalExample ''
+ {
+ confirmation = false;
+ report.minimal.filter = "status:pending";
+ report.active.columns = [ "id" "start" "entry.age" "priority" "project" "due" "description" ];
+ report.active.labels = [ "ID" "Started" "Age" "Priority" "Project" "Due" "Description" ];
+ taskd = {
+ certificate = "/path/to/cert";
+ key = "/path/to/key";
+ ca = "/path/to/ca";
+ server = "host.domain:53589";
+ credentials = "Org/First Last/cf31f287-ee9e-43a8-843e-e8bbd5de4294";
+ };
+ }
+ '';
+ description = ''
+ Key-value configuration written to
+ <filename>~/.taskrc</filename>.
+ '';
+ };
+
+ dataLocation = mkOption {
+ type = types.str;
+ default = "${config.xdg.dataHome}/task";
+ defaultText = "$XDG_DATA_HOME/task";
+ description = ''
+ Location where Task Warrior will store its data.
+ </para><para>
+ Home Manager will attempt to create this directory.
+ '';
+ };
+
+ colorTheme = mkOption {
+ type = with types; nullOr (either str path);
+ default = null;
+ example = "dark-blue-256";
+ description = ''
+ Either one of the default provided theme as string, or a
+ path to a theme configuration file.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Additional content written at the end of
+ <filename>~/.taskrc</filename>.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.taskwarrior ];
+
+ home.file."${cfg.dataLocation}/.keep".text = "";
+
+ home.file.".taskrc".text = ''
+ data.location=${cfg.dataLocation}
+ ${includeTheme cfg.colorTheme}
+
+ ${concatStringsSep "\n" (mapAttrsToList formatPair cfg.config)}
+
+ ${cfg.extraConfig}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/termite.nix b/infra/libkookie/home-manager/modules/programs/termite.nix
new file mode 100644
index 000000000000..e3d704424e8e
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/termite.nix
@@ -0,0 +1,387 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.termite;
+
+ vteInitStr = ''
+ # See https://github.com/thestinger/termite#id1
+ if [[ $TERM == xterm-termite ]]; then
+ . ${pkgs.termite.vte-ng}/etc/profile.d/vte.sh
+ fi
+ '';
+
+in {
+ options = {
+ programs.termite = {
+ enable = mkEnableOption "Termite VTE-based terminal";
+
+ allowBold = mkOption {
+ default = null;
+ type = types.nullOr types.bool;
+ description = ''
+ Allow the output of bold characters when the bold escape sequence appears.
+ '';
+ };
+
+ audibleBell = mkOption {
+ default = null;
+ type = types.nullOr types.bool;
+ description = "Have the terminal beep on the terminal bell.";
+ };
+
+ clickableUrl = mkOption {
+ default = null;
+ type = types.nullOr types.bool;
+ description = ''
+ Auto-detected URLs can be clicked on to open them in your browser.
+ Only enabled if a browser is configured or detected.
+ '';
+ };
+
+ dynamicTitle = mkOption {
+ default = null;
+ type = types.nullOr types.bool;
+ description = ''
+ Settings dynamic title allows the terminal and the shell to
+ update the terminal's title.
+ '';
+ };
+
+ fullscreen = mkOption {
+ default = null;
+ type = types.nullOr types.bool;
+ description = "Enables entering fullscreen mode by pressing F11.";
+ };
+
+ mouseAutohide = mkOption {
+ default = null;
+ type = types.nullOr types.bool;
+ description = ''
+ Automatically hide the mouse pointer when you start typing.
+ '';
+ };
+
+ scrollOnOutput = mkOption {
+ default = null;
+ type = types.nullOr types.bool;
+ description = "Scroll to the bottom when the shell generates output.";
+ };
+
+ scrollOnKeystroke = mkOption {
+ default = null;
+ type = types.nullOr types.bool;
+ description = ''
+ Scroll to the bottom automatically when a key is pressed.
+ '';
+ };
+
+ searchWrap = mkOption {
+ default = null;
+ type = types.nullOr types.bool;
+ description = "Search from top again when you hit the bottom.";
+ };
+
+ urgentOnBell = mkOption {
+ default = null;
+ type = types.nullOr types.bool;
+ description = "Sets the window as urgent on the terminal bell.";
+ };
+
+ font = mkOption {
+ default = null;
+ example = "Monospace 12";
+ type = types.nullOr types.str;
+ description = "The font description for the terminal's font.";
+ };
+
+ geometry = mkOption {
+ default = null;
+ example = "640x480";
+ type = types.nullOr types.str;
+ description = "The default window geometry for new terminal windows.";
+ };
+
+ iconName = mkOption {
+ default = null;
+ example = "terminal";
+ type = types.nullOr types.str;
+ description =
+ "The name of the icon to be used for the terminal process.";
+ };
+
+ scrollbackLines = mkOption {
+ default = null;
+ example = 10000;
+ type = types.nullOr types.int;
+ description =
+ "Set the number of lines to limit the terminal's scrollback.";
+ };
+
+ browser = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ example = "${pkgs.xdg_utils}/xdg-open";
+ description = ''
+ Set the default browser for opening links. If its not set, $BROWSER is read.
+ If that's not set, url hints will be disabled.
+ '';
+ };
+
+ cursorBlink = mkOption {
+ default = null;
+ example = "system";
+ type = types.nullOr (types.enum [ "system" "on" "off" ]);
+ description = ''
+ Specify the how the terminal's cursor should behave.
+ Accepts system to respect the gtk global configuration,
+ on and off to explicitly enable or disable them.
+ '';
+ };
+
+ cursorShape = mkOption {
+ default = null;
+ example = "block";
+ type = types.nullOr (types.enum [ "block" "underline" "ibeam" ]);
+ description = ''
+ Specify how the cursor should look. Accepts block, ibeam and underline.
+ '';
+ };
+
+ filterUnmatchedUrls = mkOption {
+ default = null;
+ type = types.nullOr types.bool;
+ description =
+ "Whether to hide url hints not matching input in url hints mode.";
+ };
+
+ modifyOtherKeys = mkOption {
+ default = null;
+ type = types.nullOr types.bool;
+ description = ''
+ Emit escape sequences for extra keys,
+ like the modifyOtherKeys resource for
+ <citerefentry>
+ <refentrytitle>xterm</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>.
+ '';
+ };
+
+ sizeHints = mkOption {
+ default = null;
+ type = types.nullOr types.bool;
+ description = ''
+ Enable size hints. Locks the terminal resizing
+ to increments of the terminal's cell size.
+ Requires a window manager that respects scroll hints.
+ '';
+ };
+
+ scrollbar = mkOption {
+ default = null;
+ type = types.nullOr (types.enum [ "off" "left" "right" ]);
+ description = "Scrollbar position.";
+ };
+
+ backgroundColor = mkOption {
+ default = null;
+ example = "rgba(63, 63, 63, 0.8)";
+ type = types.nullOr types.str;
+ description = "Background color value.";
+ };
+
+ cursorColor = mkOption {
+ default = null;
+ example = "#dcdccc";
+ type = types.nullOr types.str;
+ description = "Cursor color value.";
+ };
+
+ cursorForegroundColor = mkOption {
+ default = null;
+ example = "#dcdccc";
+ type = types.nullOr types.str;
+ description = "Cursor foreground color value.";
+ };
+
+ foregroundColor = mkOption {
+ default = null;
+ example = "#dcdccc";
+ type = types.nullOr types.str;
+ description = "Foreground color value.";
+ };
+
+ foregroundBoldColor = mkOption {
+ default = null;
+ example = "#ffffff";
+ type = types.nullOr types.str;
+ description = "Foreground bold color value.";
+ };
+
+ highlightColor = mkOption {
+ default = null;
+ example = "#2f2f2f";
+ type = types.nullOr types.str;
+ description = "highlight color value.";
+ };
+
+ hintsActiveBackgroundColor = mkOption {
+ default = null;
+ example = "#3f3f3f";
+ type = types.nullOr types.str;
+ description = "Hints active background color value.";
+ };
+
+ hintsActiveForegroundColor = mkOption {
+ default = null;
+ example = "#e68080";
+ type = types.nullOr types.str;
+ description = "Hints active foreground color value.";
+ };
+
+ hintsBackgroundColor = mkOption {
+ default = null;
+ example = "#3f3f3f";
+ type = types.nullOr types.str;
+ description = "Hints background color value.";
+ };
+
+ hintsForegroundColor = mkOption {
+ default = null;
+ example = "#dcdccc";
+ type = types.nullOr types.str;
+ description = "Hints foreground color value.";
+ };
+
+ hintsBorderColor = mkOption {
+ default = null;
+ example = "#3f3f3f";
+ type = types.nullOr types.str;
+ description = "Hints border color value.";
+ };
+
+ hintsBorderWidth = mkOption {
+ default = null;
+ example = "0.5";
+ type = types.nullOr types.str;
+ description = "Hints border width.";
+ };
+
+ hintsFont = mkOption {
+ default = null;
+ example = "Monospace 12";
+ type = types.nullOr types.str;
+ description = "The font description for the hints font.";
+ };
+
+ hintsPadding = mkOption {
+ default = null;
+ example = 2;
+ type = types.nullOr types.int;
+ description = "Hints padding.";
+ };
+
+ hintsRoundness = mkOption {
+ default = null;
+ example = "0.2";
+ type = types.nullOr types.str;
+ description = "Hints roundness.";
+ };
+
+ optionsExtra = mkOption {
+ default = "";
+ example = "fullscreen = true";
+ type = types.lines;
+ description =
+ "Extra options that should be added to [options] section.";
+ };
+
+ colorsExtra = mkOption {
+ default = "";
+ example = ''
+ color0 = #3f3f3f
+ color1 = #705050
+ color2 = #60b48a
+ '';
+ type = types.lines;
+ description =
+ "Extra colors options that should be added to [colors] section.";
+ };
+
+ hintsExtra = mkOption {
+ default = "";
+ example = "border = #3f3f3f";
+ type = types.lines;
+ description =
+ "Extra hints options that should be added to [hints] section.";
+ };
+ };
+ };
+
+ config = (let
+ boolToString = v: if v then "true" else "false";
+ optionalBoolean = name: val:
+ lib.optionalString (val != null) "${name} = ${boolToString val}";
+ optionalInteger = name: val:
+ lib.optionalString (val != null) "${name} = ${toString val}";
+ optionalString = name: val:
+ lib.optionalString (val != null) "${name} = ${val}";
+ in mkIf cfg.enable {
+ home.packages = [ pkgs.termite ];
+ xdg.configFile."termite/config".text = ''
+ [options]
+ ${optionalBoolean "allow_bold" cfg.allowBold}
+ ${optionalBoolean "audible_bell" cfg.audibleBell}
+ ${optionalString "browser" cfg.browser}
+ ${optionalBoolean "clickable_url" cfg.clickableUrl}
+ ${optionalString "cursor_blink" cfg.cursorBlink}
+ ${optionalString "cursor_shape" cfg.cursorShape}
+ ${optionalBoolean "dynamic_title" cfg.dynamicTitle}
+ ${optionalBoolean "filter_unmatched_urls" cfg.filterUnmatchedUrls}
+ ${optionalString "font" cfg.font}
+ ${optionalBoolean "fullscreen" cfg.fullscreen}
+ ${optionalString "geometry" cfg.geometry}
+ ${optionalString "icon_name" cfg.iconName}
+ ${optionalBoolean "modify_other_keys" cfg.modifyOtherKeys}
+ ${optionalBoolean "mouse_autohide" cfg.mouseAutohide}
+ ${optionalBoolean "scroll_on_keystroke" cfg.scrollOnKeystroke}
+ ${optionalBoolean "scroll_on_output" cfg.scrollOnOutput}
+ ${optionalInteger "scrollback_lines" cfg.scrollbackLines}
+ ${optionalString "scrollbar" cfg.scrollbar}
+ ${optionalBoolean "search_wrap" cfg.searchWrap}
+ ${optionalBoolean "size_hints" cfg.sizeHints}
+ ${optionalBoolean "urgent_on_bell" cfg.urgentOnBell}
+
+ ${cfg.optionsExtra}
+
+ [colors]
+ ${optionalString "background" cfg.backgroundColor}
+ ${optionalString "cursor" cfg.cursorColor}
+ ${optionalString "cursor_foreground" cfg.cursorForegroundColor}
+ ${optionalString "foreground" cfg.foregroundColor}
+ ${optionalString "foreground_bold" cfg.foregroundBoldColor}
+ ${optionalString "highlight" cfg.highlightColor}
+
+ ${cfg.colorsExtra}
+
+ [hints]
+ ${optionalString "active_background" cfg.hintsActiveBackgroundColor}
+ ${optionalString "active_foreground" cfg.hintsActiveForegroundColor}
+ ${optionalString "background" cfg.hintsBackgroundColor}
+ ${optionalString "border" cfg.hintsBorderColor}
+ ${optionalInteger "border_width" cfg.hintsBorderWidth}
+ ${optionalString "font" cfg.hintsFont}
+ ${optionalString "foreground" cfg.hintsForegroundColor}
+ ${optionalInteger "padding" cfg.hintsPadding}
+ ${optionalInteger "roundness" cfg.hintsRoundness}
+
+ ${cfg.hintsExtra}
+ '';
+
+ programs.bash.initExtra = vteInitStr;
+ programs.zsh.initExtra = vteInitStr;
+ });
+}
diff --git a/infra/libkookie/home-manager/modules/programs/texlive.nix b/infra/libkookie/home-manager/modules/programs/texlive.nix
new file mode 100644
index 000000000000..08a376d654a7
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/texlive.nix
@@ -0,0 +1,46 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.texlive;
+
+ texlivePkgs = cfg.extraPackages pkgs.texlive;
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ programs.texlive = {
+ enable = mkEnableOption "Texlive";
+
+ extraPackages = mkOption {
+ default = tpkgs: { inherit (tpkgs) collection-basic; };
+ defaultText = "tpkgs: { inherit (tpkgs) collection-basic; }";
+ example = literalExample ''
+ tpkgs: { inherit (tpkgs) collection-fontsrecommended algorithms; }
+ '';
+ description = "Extra packages available to Texlive.";
+ };
+
+ package = mkOption {
+ type = types.package;
+ description = "Resulting customized Texlive package.";
+ readOnly = true;
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [{
+ assertion = texlivePkgs != { };
+ message = "Must provide at least one extra package in"
+ + " 'programs.texlive.extraPackages'.";
+ }];
+
+ home.packages = [ cfg.package ];
+
+ programs.texlive.package = pkgs.texlive.combine texlivePkgs;
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/tmux.nix b/infra/libkookie/home-manager/modules/programs/tmux.nix
new file mode 100644
index 000000000000..295df490a362
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/tmux.nix
@@ -0,0 +1,344 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.tmux;
+
+ pluginName = p: if types.package.check p then p.pname else p.plugin.pname;
+
+ pluginModule = types.submodule {
+ options = {
+ plugin = mkOption {
+ type = types.package;
+ description = "Path of the configuration file to include.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ description = "Additional configuration for the associated plugin.";
+ default = "";
+ };
+ };
+ };
+
+ defaultKeyMode = "emacs";
+ defaultResize = 5;
+ defaultShortcut = "b";
+ defaultTerminal = "screen";
+ defaultShell = null;
+
+ boolToStr = value: if value then "on" else "off";
+
+ tmuxConf = ''
+ ${optionalString cfg.sensibleOnTop ''
+ # ============================================= #
+ # Start with defaults from the Sensible plugin #
+ # --------------------------------------------- #
+ run-shell ${pkgs.tmuxPlugins.sensible.rtp}
+ # ============================================= #
+ ''}
+ set -g default-terminal "${cfg.terminal}"
+ set -g base-index ${toString cfg.baseIndex}
+ setw -g pane-base-index ${toString cfg.baseIndex}
+ ${optionalString (cfg.shell != null) ''
+ # We need to set default-shell before calling new-session
+ set -g default-shell "${cfg.shell}"
+ ''}
+ ${optionalString cfg.newSession "new-session"}
+
+ ${optionalString cfg.reverseSplit ''
+ bind v split-window -h
+ bind s split-window -v
+ ''}
+
+ set -g status-keys ${cfg.keyMode}
+ set -g mode-keys ${cfg.keyMode}
+
+ ${optionalString (cfg.keyMode == "vi" && cfg.customPaneNavigationAndResize) ''
+ bind h select-pane -L
+ bind j select-pane -D
+ bind k select-pane -U
+ bind l select-pane -R
+
+ bind -r H resize-pane -L ${toString cfg.resizeAmount}
+ bind -r J resize-pane -D ${toString cfg.resizeAmount}
+ bind -r K resize-pane -U ${toString cfg.resizeAmount}
+ bind -r L resize-pane -R ${toString cfg.resizeAmount}
+ ''}
+
+ ${if cfg.prefix != null
+ then ''
+ # rebind main key: ${cfg.prefix}
+ unbind C-${defaultShortcut}
+ set -g prefix ${cfg.prefix}
+ bind ${cfg.prefix} send-prefix
+ ''
+ else optionalString (cfg.shortcut != defaultShortcut) ''
+ # rebind main key: C-${cfg.shortcut}
+ unbind C-${defaultShortcut}
+ set -g prefix C-${cfg.shortcut}
+ bind ${cfg.shortcut} send-prefix
+ bind C-${cfg.shortcut} last-window
+ ''
+ }
+
+ ${optionalString cfg.disableConfirmationPrompt ''
+ bind-key & kill-window
+ bind-key x kill-pane
+ ''}
+
+ setw -g aggressive-resize ${boolToStr cfg.aggressiveResize}
+ setw -g clock-mode-style ${if cfg.clock24 then "24" else "12"}
+ set -s escape-time ${toString cfg.escapeTime}
+ set -g history-limit ${toString cfg.historyLimit}
+ '';
+
+ configPlugins = {
+ assertions = [(
+ let
+ hasBadPluginName = p: !(hasPrefix "tmuxplugin" (pluginName p));
+ badPlugins = filter hasBadPluginName cfg.plugins;
+ in
+ {
+ assertion = badPlugins == [];
+ message =
+ "Invalid tmux plugin (not prefixed with \"tmuxplugins\"): "
+ + concatMapStringsSep ", " pluginName badPlugins;
+ }
+ )];
+
+ home.file.".tmux.conf".text = ''
+ # ============================================= #
+ # Load plugins with Home Manager #
+ # --------------------------------------------- #
+
+ ${(concatMapStringsSep "\n\n" (p: ''
+ # ${pluginName p}
+ # ---------------------
+ ${p.extraConfig or ""}
+ run-shell ${
+ if types.package.check p
+ then p.rtp
+ else p.plugin.rtp
+ }
+ '') cfg.plugins)}
+ # ============================================= #
+ '';
+ };
+in
+
+{
+ options = {
+ programs.tmux = {
+ aggressiveResize = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Resize the window to the size of the smallest session for
+ which it is the current window.
+ '';
+ };
+
+ baseIndex = mkOption {
+ default = 0;
+ example = 1;
+ type = types.ints.unsigned;
+ description = "Base index for windows and panes.";
+ };
+
+ clock24 = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Use 24 hour clock.";
+ };
+
+ customPaneNavigationAndResize = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Override the hjkl and HJKL bindings for pane navigation and
+ resizing in VI mode.
+ '';
+ };
+
+ disableConfirmationPrompt = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Disable confirmation prompt before killing a pane or window
+ '';
+ };
+
+ enable = mkEnableOption "tmux";
+
+ escapeTime = mkOption {
+ default = 500;
+ example = 0;
+ type = types.ints.unsigned;
+ description = ''
+ Time in milliseconds for which tmux waits after an escape is
+ input.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Additional configuration to add to
+ <filename>tmux.conf</filename>.
+ '';
+ };
+
+ historyLimit = mkOption {
+ default = 2000;
+ example = 5000;
+ type = types.ints.positive;
+ description = "Maximum number of lines held in window history.";
+ };
+
+ keyMode = mkOption {
+ default = defaultKeyMode;
+ example = "vi";
+ type = types.enum [ "emacs" "vi" ];
+ description = "VI or Emacs style shortcuts.";
+ };
+
+ newSession = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Automatically spawn a session if trying to attach and none
+ are running.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.tmux;
+ defaultText = literalExample "pkgs.tmux";
+ example = literalExample "pkgs.tmux";
+ description = "The tmux package to install";
+ };
+
+ reverseSplit = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Reverse the window split shortcuts.";
+ };
+
+ resizeAmount = mkOption {
+ default = defaultResize;
+ example = 10;
+ type = types.ints.positive;
+ description = "Number of lines/columns when resizing.";
+ };
+
+ sensibleOnTop = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Run the sensible plugin at the top of the configuration. It
+ is possible to override the sensible settings using the
+ <option>programs.tmux.extraConfig</option> option.
+ '';
+ };
+
+ prefix = mkOption {
+ default = null;
+ example = "C-a";
+ type = types.nullOr types.str;
+ description = ''
+ Set the prefix key. Overrules the "shortcut" option when set.
+ '';
+ };
+
+ shortcut = mkOption {
+ default = defaultShortcut;
+ example = "a";
+ type = types.str;
+ description = ''
+ CTRL following by this key is used as the main shortcut.
+ '';
+ };
+
+ terminal = mkOption {
+ default = defaultTerminal;
+ example = "screen-256color";
+ type = types.str;
+ description = "Set the $TERM variable.";
+ };
+
+ shell = mkOption {
+ default = defaultShell;
+ example = "\${pkgs.zsh}/bin/zsh";
+ type = with types; nullOr str;
+ description = "Set the default-shell tmux variable.";
+ };
+
+ secureSocket = mkOption {
+ default = pkgs.stdenv.isLinux;
+ type = types.bool;
+ description = ''
+ Store tmux socket under <filename>/run</filename>, which is more
+ secure than <filename>/tmp</filename>, but as a downside it doesn't
+ survive user logout.
+ '';
+ };
+
+ tmuxp.enable = mkEnableOption "tmuxp";
+
+ tmuxinator.enable = mkEnableOption "tmuxinator";
+
+ plugins = mkOption {
+ type = with types;
+ listOf (either package pluginModule)
+ // { description = "list of plugin packages or submodules"; };
+ description = ''
+ List of tmux plugins to be included at the end of your tmux
+ configuration. The sensible plugin, however, is defaulted to
+ run at the top of your configuration.
+ '';
+ default = [ ];
+ example = literalExample ''
+ with pkgs; [
+ tmuxPlugins.cpu
+ {
+ plugin = tmuxPlugins.resurrect;
+ extraConfig = "set -g @resurrect-strategy-nvim 'session'";
+ }
+ {
+ plugin = tmuxPlugins.continuum;
+ extraConfig = '''
+ set -g @continuum-restore 'on'
+ set -g @continuum-save-interval '60' # minutes
+ ''';
+ }
+ ]
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable (
+ mkMerge ([
+ {
+ home.packages = [ cfg.package ]
+ ++ optional cfg.tmuxinator.enable pkgs.tmuxinator
+ ++ optional cfg.tmuxp.enable pkgs.tmuxp;
+ }
+ (mkIf cfg.secureSocket {
+ home.sessionVariables = {
+ TMUX_TMPDIR = ''''${XDG_RUNTIME_DIR:-"/run/user/\$(id -u)"}'';
+ };
+ })
+
+ # config file ~/.tmux.conf
+ { home.file.".tmux.conf".text = mkBefore tmuxConf; }
+ (mkIf (cfg.plugins != []) configPlugins)
+ { home.file.".tmux.conf".text = mkAfter cfg.extraConfig; }
+ ])
+ );
+}
diff --git a/infra/libkookie/home-manager/modules/programs/urxvt.nix b/infra/libkookie/home-manager/modules/programs/urxvt.nix
new file mode 100644
index 000000000000..5eb3d90d7924
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/urxvt.nix
@@ -0,0 +1,156 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.urxvt;
+
+in {
+ options.programs.urxvt = {
+ enable = mkEnableOption "rxvt-unicode terminal emulator";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.rxvt_unicode;
+ defaultText = literalExample "pkgs.rxvt_unicode";
+ description = "rxvt-unicode package to install.";
+ };
+
+ fonts = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = "List of fonts to be used.";
+ example = [ "xft:Droid Sans Mono Nerd Font:size=9" ];
+ };
+
+ keybindings = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ description = "Mapping of keybindings to actions";
+ example = literalExample ''
+ {
+ "Shift-Control-C" = "eval:selection_to_clipboard";
+ "Shift-Control-V" = "eval:paste_clipboard";
+ }
+ '';
+ };
+
+ iso14755 = mkOption {
+ type = types.bool;
+ default = true;
+ description =
+ "ISO14755 support for viewing and entering unicode characters.";
+ };
+
+ scroll = {
+ bar = mkOption {
+ type = types.submodule {
+ options = {
+ enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to enable the scrollbar";
+ };
+
+ style = mkOption {
+ type = types.enum [ "rxvt" "plain" "next" "xterm" ];
+ default = "plain";
+ description = "Scrollbar style.";
+ };
+
+ align = mkOption {
+ type = types.enum [ "top" "bottom" "center" ];
+ default = "center";
+ description = "Scrollbar alignment.";
+ };
+
+ position = mkOption {
+ type = types.enum [ "left" "right" ];
+ default = "right";
+ description = "Scrollbar position.";
+ };
+
+ floating = mkOption {
+ type = types.bool;
+ default = true;
+ description =
+ "Whether to display an rxvt scrollbar without a trough.";
+ };
+ };
+ };
+ default = { };
+ description = "Scrollbar settings.";
+ };
+
+ lines = mkOption {
+ type = types.ints.unsigned;
+ default = 10000;
+ description = "Number of lines to save in the scrollback buffer.";
+ };
+
+ keepPosition = mkOption {
+ type = types.bool;
+ default = true;
+ description =
+ "Whether to keep a scroll position when TTY receives new lines.";
+ };
+
+ scrollOnKeystroke = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to scroll to bottom on keyboard input.";
+ };
+
+ scrollOnOutput = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to scroll to bottom on TTY output.";
+ };
+ };
+
+ transparent = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable pseudo-transparency.";
+ };
+
+ shading = mkOption {
+ type = types.ints.between 0 200;
+ default = 100;
+ description =
+ "Darken (0 .. 99) or lighten (101 .. 200) the transparent background.";
+ };
+
+ extraConfig = mkOption {
+ default = { };
+ type = types.attrsOf types.anything;
+ description = "Additional configuration to add.";
+ example = { "shading" = 15; };
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+
+ xresources.properties = {
+ "URxvt.scrollBar" = cfg.scroll.bar.enable;
+ "URxvt.scrollstyle" = cfg.scroll.bar.style;
+ "URxvt.scrollBar_align" = cfg.scroll.bar.align;
+ "URxvt.scrollBar_right" = cfg.scroll.bar.position == "right";
+ "URxvt.scrollBar_floating" = cfg.scroll.bar.floating;
+ "URxvt.saveLines" = cfg.scroll.lines;
+ "URxvt.scrollWithBuffer" = cfg.scroll.keepPosition;
+ "URxvt.scrollTtyKeypress" = cfg.scroll.scrollOnKeystroke;
+ "URxvt.scrollTtyOutput" = cfg.scroll.scrollOnOutput;
+ "URxvt.transparent" = cfg.transparent;
+ "URxvt.shading" = cfg.shading;
+ "URxvt.iso14755" = cfg.iso14755;
+ } // flip mapAttrs' cfg.keybindings
+ (kb: action: nameValuePair "URxvt.keysym.${kb}" action)
+ // optionalAttrs (cfg.fonts != [ ]) {
+ "URxvt.font" = concatStringsSep "," cfg.fonts;
+ } // flip mapAttrs' cfg.extraConfig (k: v: nameValuePair "URxvt.${k}" v);
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/vim.nix b/infra/libkookie/home-manager/modules/programs/vim.nix
new file mode 100644
index 000000000000..3325bf225169
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/vim.nix
@@ -0,0 +1,171 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.vim;
+ defaultPlugins = [ pkgs.vimPlugins.vim-sensible ];
+
+ knownSettings = {
+ background = types.enum [ "dark" "light" ];
+ backupdir = types.listOf types.str;
+ copyindent = types.bool;
+ directory = types.listOf types.str;
+ expandtab = types.bool;
+ hidden = types.bool;
+ history = types.int;
+ ignorecase = types.bool;
+ modeline = types.bool;
+ mouse = types.enum [ "n" "v" "i" "c" "h" "a" "r" ];
+ mousefocus = types.bool;
+ mousehide = types.bool;
+ mousemodel = types.enum [ "extend" "popup" "popup_setpos" ];
+ number = types.bool;
+ relativenumber = types.bool;
+ shiftwidth = types.int;
+ smartcase = types.bool;
+ tabstop = types.int;
+ undodir = types.listOf types.str;
+ undofile = types.bool;
+ };
+
+ vimSettingsType = types.submodule {
+ options = let
+ opt = name: type:
+ mkOption {
+ type = types.nullOr type;
+ default = null;
+ visible = false;
+ };
+ in mapAttrs opt knownSettings;
+ };
+
+ setExpr = name: value:
+ let
+ v = if isBool value then
+ (if value then "" else "no") + name
+ else
+ "${name}=${
+ if isList value then concatStringsSep "," value else toString value
+ }";
+ in optionalString (value != null) ("set " + v);
+
+ plugins = let
+ vpkgs = pkgs.vimPlugins;
+ getPkg = p:
+ if isDerivation p then
+ [ p ]
+ else
+ optional (isString p && hasAttr p vpkgs) vpkgs.${p};
+ in concatMap getPkg cfg.plugins;
+
+in {
+ options = {
+ programs.vim = {
+ enable = mkEnableOption "Vim";
+
+ plugins = mkOption {
+ type = with types; listOf (either str package);
+ default = defaultPlugins;
+ example = literalExample "[ pkgs.vimPlugins.YankRing ]";
+ description = ''
+ List of vim plugins to install. To get a list of supported plugins run:
+ <command>nix-env -f '&lt;nixpkgs&gt;' -qaP -A vimPlugins</command>.
+
+ </para><para>
+
+ Note: String values are deprecated, please use actual packages.
+ '';
+ };
+
+ settings = mkOption {
+ type = vimSettingsType;
+ default = { };
+ example = literalExample ''
+ {
+ expandtab = true;
+ history = 1000;
+ background = "dark";
+ }
+ '';
+ description = ''
+ At attribute set of Vim settings. The attribute names and
+ corresponding values must be among the following supported
+ options.
+
+ <informaltable frame="none"><tgroup cols="1"><tbody>
+ ${concatStringsSep "\n" (mapAttrsToList (n: v: ''
+ <row>
+ <entry><varname>${n}</varname></entry>
+ <entry>${v.description}</entry>
+ </row>
+ '') knownSettings)}
+ </tbody></tgroup></informaltable>
+
+ See the Vim documentation for detailed descriptions of these
+ options. Note, use <varname>extraConfig</varname> to
+ manually set any options not listed above.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ set nocompatible
+ set nobackup
+ '';
+ description = "Custom .vimrc lines";
+ };
+
+ package = mkOption {
+ type = types.package;
+ description = "Resulting customized vim package";
+ readOnly = true;
+ };
+ };
+ };
+
+ config = (let
+ customRC = ''
+ ${concatStringsSep "\n" (filter (v: v != "") (mapAttrsToList setExpr
+ (builtins.intersectAttrs knownSettings cfg.settings)))}
+
+ ${cfg.extraConfig}
+ '';
+
+ vim = pkgs.vim_configurable.customize {
+ name = "vim";
+ vimrcConfig = {
+ inherit customRC;
+
+ packages.home-manager.start = plugins;
+ };
+ };
+ in mkIf cfg.enable {
+ assertions = let
+ packagesNotFound =
+ filter (p: isString p && (!hasAttr p pkgs.vimPlugins)) cfg.plugins;
+ in [{
+ assertion = packagesNotFound == [ ];
+ message = "Following VIM plugin not found in pkgs.vimPlugins: ${
+ concatMapStringsSep ", " (p: ''"${p}"'') packagesNotFound
+ }";
+ }];
+
+ warnings = let stringPlugins = filter isString cfg.plugins;
+ in optional (stringPlugins != [ ]) ''
+ Specifying VIM plugins using strings is deprecated, found ${
+ concatMapStringsSep ", " (p: ''"${p}"'') stringPlugins
+ } as strings.
+ '';
+
+ home.packages = [ cfg.package ];
+
+ programs.vim = {
+ package = vim;
+ plugins = defaultPlugins;
+ };
+ });
+}
diff --git a/infra/libkookie/home-manager/modules/programs/vscode.nix b/infra/libkookie/home-manager/modules/programs/vscode.nix
new file mode 100644
index 000000000000..5f4400257e82
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/vscode.nix
@@ -0,0 +1,137 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.vscode;
+
+ vscodePname = cfg.package.pname;
+
+ jsonFormat = pkgs.formats.json { };
+
+ configDir = {
+ "vscode" = "Code";
+ "vscode-insiders" = "Code - Insiders";
+ "vscodium" = "VSCodium";
+ }.${vscodePname};
+
+ extensionDir = {
+ "vscode" = "vscode";
+ "vscode-insiders" = "vscode-insiders";
+ "vscodium" = "vscode-oss";
+ }.${vscodePname};
+
+ userDir = if pkgs.stdenv.hostPlatform.isDarwin then
+ "Library/Application Support/${configDir}/User"
+ else
+ "${config.xdg.configHome}/${configDir}/User";
+
+ configFilePath = "${userDir}/settings.json";
+ keybindingsFilePath = "${userDir}/keybindings.json";
+
+ # TODO: On Darwin where are the extensions?
+ extensionPath = ".${extensionDir}/extensions";
+
+in {
+ options = {
+ programs.vscode = {
+ enable = mkEnableOption "Visual Studio Code";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.vscode;
+ example = literalExample "pkgs.vscodium";
+ description = ''
+ Version of Visual Studio Code to install.
+ '';
+ };
+
+ userSettings = mkOption {
+ type = jsonFormat.type;
+ default = { };
+ example = literalExample ''
+ {
+ "update.channel" = "none";
+ "[nix]"."editor.tabSize" = 2;
+ }
+ '';
+ description = ''
+ Configuration written to Visual Studio Code's
+ <filename>settings.json</filename>.
+ '';
+ };
+
+ keybindings = mkOption {
+ type = types.listOf (types.submodule {
+ options = {
+ key = mkOption {
+ type = types.str;
+ example = "ctrl+c";
+ description = "The key or key-combination to bind.";
+ };
+
+ command = mkOption {
+ type = types.str;
+ example = "editor.action.clipboardCopyAction";
+ description = "The VS Code command to execute.";
+ };
+
+ when = mkOption {
+ type = types.str;
+ default = "";
+ example = "textInputFocus";
+ description = "Optional context filter.";
+ };
+ };
+ });
+ default = [ ];
+ example = literalExample ''
+ [
+ {
+ key = "ctrl+c";
+ command = "editor.action.clipboardCopyAction";
+ when = "textInputFocus";
+ }
+ ]
+ '';
+ description = ''
+ Keybindings written to Visual Studio Code's
+ <filename>keybindings.json</filename>.
+ '';
+ };
+
+ extensions = mkOption {
+ type = types.listOf types.package;
+ default = [ ];
+ example = literalExample "[ pkgs.vscode-extensions.bbenoist.Nix ]";
+ description = ''
+ The extensions Visual Studio Code should be started with.
+ These will override but not delete manually installed ones.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+
+ # Adapted from https://discourse.nixos.org/t/vscode-extensions-setup/1801/2
+ home.file = let
+ subDir = "share/vscode/extensions";
+ toPaths = path:
+ # Links every dir in path to the extension path.
+ mapAttrsToList
+ (k: _: { "${extensionPath}/${k}".source = "${path}/${subDir}/${k}"; })
+ (builtins.readDir (path + "/${subDir}"));
+ toSymlink = concatMap toPaths cfg.extensions;
+ in foldr (a: b: a // b) {
+ "${configFilePath}" = mkIf (cfg.userSettings != { }) {
+ source = jsonFormat.generate "vscode-user-settings" cfg.userSettings;
+ };
+ "${keybindingsFilePath}" = mkIf (cfg.keybindings != [ ]) {
+ source = jsonFormat.generate "vscode-keybindings" cfg.keybindings;
+ };
+ } toSymlink;
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/vscode/haskell.nix b/infra/libkookie/home-manager/modules/programs/vscode/haskell.nix
new file mode 100644
index 000000000000..ee84e7071023
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/vscode/haskell.nix
@@ -0,0 +1,62 @@
+{ pkgs, config, lib, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.vscode.haskell;
+
+ defaultHieNixExe = hie-nix.hies + "/bin/hie-wrapper";
+ defaultHieNixExeText =
+ literalExample ''"''${pkgs.hie-nix.hies}/bin/hie-wrapper"'';
+
+ hie-nix = pkgs.hie-nix or (abort ''
+ vscode.haskell: pkgs.hie-nix missing. Please add an overlay such as:
+ ${exampleOverlay}
+ '');
+
+ exampleOverlay = ''
+ nixpkgs.overlays = [
+ (self: super: { hie-nix = import ~/src/hie-nix {}; })
+ ]
+ '';
+
+in {
+ options.programs.vscode.haskell = {
+ enable = mkEnableOption "Haskell integration for Visual Studio Code";
+
+ hie.enable = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to enable Haskell IDE engine integration.";
+ };
+
+ hie.executablePath = mkOption {
+ type = types.path;
+ default = defaultHieNixExe;
+ defaultText = defaultHieNixExeText;
+ description = ''
+ The path to the Haskell IDE Engine executable.
+ </para><para>
+ Because hie-nix is not packaged in Nixpkgs, you need to add it as an
+ overlay or set this option. Example overlay configuration:
+ <programlisting language="nix">${exampleOverlay}</programlisting>
+ '';
+ example = literalExample ''
+ (import ~/src/haskell-ide-engine {}).hies + "/bin/hie-wrapper";
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ programs.vscode.userSettings = mkIf cfg.hie.enable {
+ "languageServerHaskell.enableHIE" = true;
+ "languageServerHaskell.hieExecutablePath" = cfg.hie.executablePath;
+ };
+
+ programs.vscode.extensions =
+ [ pkgs.vscode-extensions.justusadam.language-haskell ]
+ ++ lib.optional cfg.hie.enable
+ pkgs.vscode-extensions.alanz.vscode-hie-server;
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/waybar.nix b/infra/libkookie/home-manager/modules/programs/waybar.nix
new file mode 100644
index 000000000000..a2b8aa0f8aaf
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/waybar.nix
@@ -0,0 +1,373 @@
+{ config, lib, pkgs, ... }:
+
+let
+ inherit (lib)
+ attrByPath attrNames concatMap concatMapStringsSep elem filter filterAttrs
+ flip foldl' hasPrefix mergeAttrs optionalAttrs stringLength subtractLists
+ types unique;
+ inherit (lib.options) literalExample mkEnableOption mkOption;
+ inherit (lib.modules) mkIf mkMerge;
+
+ cfg = config.programs.waybar;
+
+ # Used when generating warnings
+ modulesPath = "programs.waybar.settings.[].modules";
+
+ jsonFormat = pkgs.formats.json { };
+
+ # Taken from <https://github.com/Alexays/Waybar/blob/adaf84304865e143e4e83984aaea6f6a7c9d4d96/src/factory.cpp>
+ defaultModuleNames = [
+ "sway/mode"
+ "sway/workspaces"
+ "sway/window"
+ "sway/language"
+ "wlr/taskbar"
+ "river/tags"
+ "idle_inhibitor"
+ "memory"
+ "cpu"
+ "clock"
+ "disk"
+ "tray"
+ "network"
+ "backlight"
+ "pulseaudio"
+ "mpd"
+ "sndio"
+ "temperature"
+ "bluetooth"
+ "battery"
+ ];
+
+ isValidCustomModuleName = x:
+ elem x defaultModuleNames || (hasPrefix "custom/" x && stringLength x > 7);
+
+ margins = let
+ mkMargin = name: {
+ "margin-${name}" = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 10;
+ description = "Margins value without unit.";
+ };
+ };
+ margins = map mkMargin [ "top" "left" "bottom" "right" ];
+ in foldl' mergeAttrs { } margins;
+
+ waybarBarConfig = with lib.types;
+ submodule {
+ options = {
+ layer = mkOption {
+ type = nullOr (enum [ "top" "bottom" ]);
+ default = null;
+ description = ''
+ Decide if the bar is displayed in front (<code>"top"</code>)
+ of the windows or behind (<code>"bottom"</code>).
+ '';
+ example = "top";
+ };
+
+ output = mkOption {
+ type = nullOr (either str (listOf str));
+ default = null;
+ example = literalExample ''
+ [ "DP-1" "!DP-2" "!DP-3" ]
+ '';
+ description = ''
+ Specifies on which screen this bar will be displayed.
+ Exclamation mark(!) can be used to exclude specific output.
+ '';
+ };
+
+ position = mkOption {
+ type = nullOr (enum [ "top" "bottom" "left" "right" ]);
+ default = null;
+ example = "right";
+ description = "Bar position relative to the output.";
+ };
+
+ height = mkOption {
+ type = nullOr ints.unsigned;
+ default = null;
+ example = 5;
+ description =
+ "Height to be used by the bar if possible. Leave blank for a dynamic value.";
+ };
+
+ width = mkOption {
+ type = nullOr ints.unsigned;
+ default = null;
+ example = 5;
+ description =
+ "Width to be used by the bar if possible. Leave blank for a dynamic value.";
+ };
+
+ modules-left = mkOption {
+ type = listOf str;
+ default = [ ];
+ description = "Modules that will be displayed on the left.";
+ example = literalExample ''
+ [ "sway/workspaces" "sway/mode" "wlr/taskbar" ]
+ '';
+ };
+
+ modules-center = mkOption {
+ type = listOf str;
+ default = [ ];
+ description = "Modules that will be displayed in the center.";
+ example = literalExample ''
+ [ "sway/window" ]
+ '';
+ };
+
+ modules-right = mkOption {
+ type = listOf str;
+ default = [ ];
+ description = "Modules that will be displayed on the right.";
+ example = literalExample ''
+ [ "mpd" "custom/mymodule#with-css-id" "temperature" ]
+ '';
+ };
+
+ modules = mkOption {
+ type = jsonFormat.type;
+ default = { };
+ description = "Modules configuration.";
+ example = literalExample ''
+ {
+ "sway/window" = {
+ max-length = 50;
+ };
+ "clock" = {
+ format-alt = "{:%a, %d. %b %H:%M}";
+ };
+ }
+ '';
+ };
+
+ margin = mkOption {
+ type = nullOr str;
+ default = null;
+ description = "Margins value using the CSS format without units.";
+ example = "20 5";
+ };
+
+ inherit (margins) margin-top margin-left margin-bottom margin-right;
+
+ name = mkOption {
+ type = nullOr str;
+ default = null;
+ description =
+ "Optional name added as a CSS class, for styling multiple waybars.";
+ example = "waybar-1";
+ };
+
+ gtk-layer-shell = mkOption {
+ type = nullOr bool;
+ default = null;
+ example = false;
+ description =
+ "Option to disable the use of gtk-layer-shell for popups.";
+ };
+ };
+ };
+in {
+ meta.maintainers = with lib.maintainers; [ berbiche ];
+
+ options.programs.waybar = with lib.types; {
+ enable = mkEnableOption "Waybar";
+
+ package = mkOption {
+ type = package;
+ default = pkgs.waybar;
+ defaultText = "pkgs.waybar";
+ description = ''
+ Waybar package to use. Set to <code>null</code> to use the default module.
+ '';
+ };
+
+ settings = mkOption {
+ type = listOf waybarBarConfig;
+ default = [ ];
+ description = ''
+ Configuration for Waybar, see <link
+ xlink:href="https://github.com/Alexays/Waybar/wiki/Configuration"/>
+ for supported values.
+ '';
+ example = literalExample ''
+ [
+ {
+ layer = "top";
+ position = "top";
+ height = 30;
+ output = [
+ "eDP-1"
+ "HDMI-A-1"
+ ];
+ modules-left = [ "sway/workspaces" "sway/mode" "wlr/taskbar" ];
+ modules-center = [ "sway/window" "custom/hello-from-waybar" ];
+ modules-right = [ "mpd" "custom/mymodule#with-css-id" "temperature" ];
+ modules = {
+ "sway/workspaces" = {
+ disable-scroll = true;
+ all-outputs = true;
+ };
+ "custom/hello-from-waybar" = {
+ format = "hello {}";
+ max-length = 40;
+ interval = "once";
+ exec = pkgs.writeShellScript "hello-from-waybar" '''
+ echo "from within waybar"
+ ''';
+ };
+ };
+ }
+ ]
+ '';
+ };
+
+ systemd.enable = mkEnableOption "Waybar systemd integration";
+
+ style = mkOption {
+ type = nullOr str;
+ default = null;
+ description = ''
+ CSS style of the bar.
+ See <link xlink:href="https://github.com/Alexays/Waybar/wiki/Configuration"/>
+ for the documentation.
+ '';
+ example = ''
+ * {
+ border: none;
+ border-radius: 0;
+ font-family: Source Code Pro;
+ }
+ window#waybar {
+ background: #16191C;
+ color: #AAB2BF;
+ }
+ #workspaces button {
+ padding: 0 5px;
+ }
+ '';
+ };
+ };
+
+ config = let
+ writePrettyJSON = jsonFormat.generate;
+
+ configSource = let
+ # Removes nulls because Waybar ignores them for most values
+ removeNulls = filterAttrs (_: v: v != null);
+
+ # Makes the actual valid configuration Waybar accepts
+ # (strips our custom settings before converting to JSON)
+ makeConfiguration = configuration:
+ let
+ # The "modules" option is not valid in the JSON
+ # as its descendants have to live at the top-level
+ settingsWithoutModules = removeAttrs configuration [ "modules" ];
+ settingsModules =
+ optionalAttrs (configuration.modules != { }) configuration.modules;
+ in removeNulls (settingsWithoutModules // settingsModules);
+ # The clean list of configurations
+ finalConfiguration = map makeConfiguration cfg.settings;
+ in writePrettyJSON "waybar-config.json" finalConfiguration;
+
+ #
+ # Warnings are generated based on the following things:
+ # 1. A `module` is referenced in any of `modules-{left,center,right}` that is neither
+ # a default module name nor defined in `modules`.
+ # 2. A `module` is defined in `modules` but is not referenced in either of
+ # `modules-{left,center,right}`.
+ # 3. A custom `module` configuration is defined in `modules` but has an invalid name
+ # for a custom module (i.e. not "custom/my-module-name").
+ #
+ warnings = let
+ mkUnreferencedModuleWarning = name:
+ "The module '${name}' defined in '${modulesPath}' is not referenced "
+ + "in either `modules-left`, `modules-center` or `modules-right` of Waybar's options";
+ mkUndefinedModuleWarning = settings: name:
+ let
+ # Locations where the module is undefined (a combination modules-{left,center,right})
+ locations = flip filter [ "left" "center" "right" ]
+ (x: elem name settings."modules-${x}");
+ mkPath = loc: "'${modulesPath}-${loc}'";
+ # The modules-{left,center,right} configuration that includes
+ # an undefined module
+ path = concatMapStringsSep " and " mkPath locations;
+ in "The module '${name}' defined in ${path} is neither "
+ + "a default module or a custom module declared in '${modulesPath}'";
+ mkInvalidModuleNameWarning = name:
+ "The custom module '${name}' defined in '${modulesPath}' is not a valid "
+ + "module name. A custom module's name must start with 'custom/' "
+ + "like 'custom/mymodule' for instance";
+
+ allFaultyModules = flip map cfg.settings (settings:
+ let
+ allModules = unique
+ (concatMap (x: attrByPath [ "modules-${x}" ] [ ] settings) [
+ "left"
+ "center"
+ "right"
+ ]);
+ declaredModules = attrNames settings.modules;
+ # Modules declared in `modules` but not referenced in `modules-{left,center,right}`
+ unreferencedModules = subtractLists allModules declaredModules;
+ # Modules listed in modules-{left,center,right} that are not default modules
+ nonDefaultModules = subtractLists defaultModuleNames allModules;
+ # Modules referenced in `modules-{left,center,right}` but not declared in `modules`
+ undefinedModules = subtractLists declaredModules nonDefaultModules;
+ # Check for invalid module names
+ invalidModuleNames =
+ filter (m: !isValidCustomModuleName m) declaredModules;
+ in {
+ # The Waybar bar configuration (since config.settings is a list)
+ inherit settings;
+ undef = undefinedModules;
+ unref = unreferencedModules;
+ invalidName = invalidModuleNames;
+ });
+
+ allWarnings = flip concatMap allFaultyModules
+ ({ settings, undef, unref, invalidName }:
+ let
+ unreferenced = map mkUnreferencedModuleWarning unref;
+ undefined = map (mkUndefinedModuleWarning settings) undef;
+ invalid = map mkInvalidModuleNameWarning invalidName;
+ in undefined ++ unreferenced ++ invalid);
+ in allWarnings;
+
+ in mkIf cfg.enable (mkMerge [
+ { home.packages = [ cfg.package ]; }
+ (mkIf (cfg.settings != [ ]) {
+ # Generate warnings about defined but unreferenced modules
+ inherit warnings;
+
+ xdg.configFile."waybar/config".source = configSource;
+ })
+ (mkIf (cfg.style != null) {
+ xdg.configFile."waybar/style.css".text = cfg.style;
+ })
+ (mkIf cfg.systemd.enable {
+ systemd.user.services.waybar = {
+ Unit = {
+ Description =
+ "Highly customizable Wayland bar for Sway and Wlroots based compositors.";
+ Documentation = "https://github.com/Alexays/Waybar/wiki";
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ Type = "dbus";
+ BusName = "fr.arouillard.waybar";
+ ExecStart = "${cfg.package}/bin/waybar";
+ Restart = "always";
+ RestartSec = "1sec";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ })
+ ]);
+}
diff --git a/infra/libkookie/home-manager/modules/programs/z-lua.nix b/infra/libkookie/home-manager/modules/programs/z-lua.nix
new file mode 100644
index 000000000000..d722ac6a2f0e
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/z-lua.nix
@@ -0,0 +1,90 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.z-lua;
+
+ aliases = {
+ zz = "z -c"; # restrict matches to subdirs of $PWD
+ zi = "z -i"; # cd with interactive selection
+ zf = "z -I"; # use fzf to select in multiple matches
+ zb = "z -b"; # quickly cd to the parent directory
+ zh = "z -I -t ."; # fzf
+ };
+
+in {
+ meta.maintainers = [ maintainers.marsam ];
+
+ options.programs.z-lua = {
+ enable = mkEnableOption "z.lua";
+
+ options = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "enhanced" "once" "fzf" ];
+ description = ''
+ List of options to pass to z.lua.
+ '';
+ };
+
+ enableBashIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Bash integration.
+ '';
+ };
+
+ enableZshIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Zsh integration.
+ '';
+ };
+
+ enableFishIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Fish integration.
+ '';
+ };
+
+ enableAliases = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Whether to enable recommended z.lua aliases.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.z-lua ];
+
+ programs.bash.initExtra = mkIf cfg.enableBashIntegration ''
+ eval "$(${pkgs.z-lua}/bin/z --init bash ${
+ concatStringsSep " " cfg.options
+ })"
+ '';
+
+ programs.zsh.initExtra = mkIf cfg.enableZshIntegration ''
+ eval "$(${pkgs.z-lua}/bin/z --init zsh ${
+ concatStringsSep " " cfg.options
+ })"
+ '';
+
+ programs.fish.shellInit = mkIf cfg.enableFishIntegration ''
+ source (${pkgs.z-lua}/bin/z --init fish ${
+ concatStringsSep " " cfg.options
+ } | psub)
+ '';
+
+ programs.bash.shellAliases = mkIf cfg.enableAliases aliases;
+
+ programs.zsh.shellAliases = mkIf cfg.enableAliases aliases;
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/zathura.nix b/infra/libkookie/home-manager/modules/programs/zathura.nix
new file mode 100644
index 000000000000..64a77cb3bee1
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/zathura.nix
@@ -0,0 +1,65 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.zathura;
+
+ formatLine = n: v:
+ let
+ formatValue = v:
+ if isBool v then (if v then "true" else "false") else toString v;
+ in ''set ${n} "${formatValue v}"'';
+
+in {
+ meta.maintainers = [ maintainers.rprospero ];
+
+ options.programs.zathura = {
+ enable = mkEnableOption ''
+ Zathura, a highly customizable and functional document viewer
+ focused on keyboard interaction'';
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.zathura;
+ defaultText = "pkgs.zathura";
+ description = "The Zathura package to use";
+ };
+
+ options = mkOption {
+ default = { };
+ type = with types; attrsOf (either str (either bool int));
+ description = ''
+ Add <option>:set</option> command options to zathura and make
+ them permanent. See
+ <citerefentry>
+ <refentrytitle>zathurarc</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry>
+ for the full list of options.
+ '';
+ example = {
+ default-bg = "#000000";
+ default-fg = "#FFFFFF";
+ };
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Additional commands for zathura that will be added to the
+ <filename>zathurarc</filename> file.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+
+ xdg.configFile."zathura/zathurarc".text = concatStringsSep "\n" ([ ]
+ ++ optional (cfg.extraConfig != "") cfg.extraConfig
+ ++ mapAttrsToList formatLine cfg.options) + "\n";
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/zoxide.nix b/infra/libkookie/home-manager/modules/programs/zoxide.nix
new file mode 100644
index 000000000000..842ff109294f
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/zoxide.nix
@@ -0,0 +1,79 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.zoxide;
+
+in {
+ meta.maintainers = [ maintainers.marsam ];
+
+ options.programs.zoxide = {
+ enable = mkEnableOption "zoxide";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.zoxide;
+ defaultText = literalExample "pkgs.zoxide";
+ description = ''
+ Zoxide package to install.
+ '';
+ };
+
+ options = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "--no-aliases" ];
+ description = ''
+ List of options to pass to zoxide.
+ '';
+ };
+
+ enableBashIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Bash integration.
+ '';
+ };
+
+ enableZshIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Zsh integration.
+ '';
+ };
+
+ enableFishIntegration = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Whether to enable Fish integration.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+
+ programs.bash.initExtra = mkIf cfg.enableBashIntegration ''
+ eval "$(${cfg.package}/bin/zoxide init bash ${
+ concatStringsSep " " cfg.options
+ })"
+ '';
+
+ programs.zsh.initExtra = mkIf cfg.enableZshIntegration ''
+ eval "$(${cfg.package}/bin/zoxide init zsh ${
+ concatStringsSep " " cfg.options
+ })"
+ '';
+
+ programs.fish.shellInit = mkIf cfg.enableFishIntegration ''
+ ${cfg.package}/bin/zoxide init fish ${
+ concatStringsSep " " cfg.options
+ } | source
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/zplug.nix b/infra/libkookie/home-manager/modules/programs/zplug.nix
new file mode 100644
index 000000000000..6cb5e98e3135
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/zplug.nix
@@ -0,0 +1,60 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.zsh.zplug;
+
+ pluginModule = types.submodule ({ config, ... }: {
+ options = {
+ name = mkOption {
+ type = types.str;
+ description = "The name of the plugin.";
+ };
+
+ tags = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = "The plugin tags.";
+ };
+ };
+
+ });
+
+in {
+ options.programs.zsh.zplug = {
+ enable = mkEnableOption "zplug - a zsh plugin manager";
+
+ plugins = mkOption {
+ default = [ ];
+ type = types.listOf pluginModule;
+ description = "List of zplug plugins.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.zplug ];
+
+ programs.zsh.initExtraBeforeCompInit = ''
+ source ${pkgs.zplug}/init.zsh
+
+ ${optionalString (cfg.plugins != [ ]) ''
+ ${concatStrings (map (plugin: ''
+ zplug "${plugin.name}"${
+ optionalString (plugin.tags != [ ]) ''
+ ${concatStrings (map (tag: ", ${tag}") plugin.tags)}
+ ''
+ }
+ '') cfg.plugins)}
+ ''}
+
+ if ! zplug check; then
+ zplug install
+ fi
+
+ zplug load
+ '';
+
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/programs/zsh.nix b/infra/libkookie/home-manager/modules/programs/zsh.nix
new file mode 100644
index 000000000000..9e3c517d23ed
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/zsh.nix
@@ -0,0 +1,541 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.zsh;
+
+ relToDotDir = file: (optionalString (cfg.dotDir != null) (cfg.dotDir + "/")) + file;
+
+ pluginsDir = if cfg.dotDir != null then
+ relToDotDir "plugins" else ".zsh/plugins";
+
+ envVarsStr = config.lib.zsh.exportAll cfg.sessionVariables;
+ localVarsStr = config.lib.zsh.defineAll cfg.localVariables;
+
+ aliasesStr = concatStringsSep "\n" (
+ mapAttrsToList (k: v: "alias ${k}=${lib.escapeShellArg v}") cfg.shellAliases
+ );
+
+ globalAliasesStr = concatStringsSep "\n" (
+ mapAttrsToList (k: v: "alias -g ${k}=${lib.escapeShellArg v}") cfg.shellGlobalAliases
+ );
+
+ dirHashesStr = concatStringsSep "\n" (
+ mapAttrsToList (k: v: ''hash -d ${k}="${v}"'') cfg.dirHashes
+ );
+
+ zdotdir = "$HOME/" + cfg.dotDir;
+
+ bindkeyCommands = {
+ emacs = "bindkey -e";
+ viins = "bindkey -v";
+ vicmd = "bindkey -a";
+ };
+
+ stateVersion = config.home.stateVersion;
+
+ historyModule = types.submodule ({ config, ... }: {
+ options = {
+ size = mkOption {
+ type = types.int;
+ default = 10000;
+ description = "Number of history lines to keep.";
+ };
+
+ save = mkOption {
+ type = types.int;
+ defaultText = 10000;
+ default = config.size;
+ description = "Number of history lines to save.";
+ };
+
+ path = mkOption {
+ type = types.str;
+ default = if versionAtLeast stateVersion "20.03"
+ then "$HOME/.zsh_history"
+ else relToDotDir ".zsh_history";
+ example = literalExample ''"''${config.xdg.dataHome}/zsh/zsh_history"'';
+ description = "History file location";
+ };
+
+ ignoreDups = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Do not enter command lines into the history list
+ if they are duplicates of the previous event.
+ '';
+ };
+
+ ignoreSpace = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Do not enter command lines into the history list
+ if the first character is a space.
+ '';
+ };
+
+ expireDuplicatesFirst = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Expire duplicates first.";
+ };
+
+ extended = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Save timestamp into the history file.";
+ };
+
+ share = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Share command history between zsh sessions.";
+ };
+ };
+ });
+
+ pluginModule = types.submodule ({ config, ... }: {
+ options = {
+ src = mkOption {
+ type = types.path;
+ description = ''
+ Path to the plugin folder.
+
+ Will be added to <envar>fpath</envar> and <envar>PATH</envar>.
+ '';
+ };
+
+ name = mkOption {
+ type = types.str;
+ description = ''
+ The name of the plugin.
+
+ Don't forget to add <option>file</option>
+ if the script name does not follow convention.
+ '';
+ };
+
+ file = mkOption {
+ type = types.str;
+ description = "The plugin script to source.";
+ };
+ };
+
+ config.file = mkDefault "${config.name}.plugin.zsh";
+ });
+
+ ohMyZshModule = types.submodule {
+ options = {
+ enable = mkEnableOption "oh-my-zsh";
+
+ plugins = mkOption {
+ default = [];
+ example = [ "git" "sudo" ];
+ type = types.listOf types.str;
+ description = ''
+ List of oh-my-zsh plugins
+ '';
+ };
+
+ custom = mkOption {
+ default = "";
+ type = types.str;
+ example = "$HOME/my_customizations";
+ description = ''
+ Path to a custom oh-my-zsh package to override config of
+ oh-my-zsh. See <link xlink:href="https://github.com/robbyrussell/oh-my-zsh/wiki/Customization"/>
+ for more information.
+ '';
+ };
+
+ theme = mkOption {
+ default = "";
+ example = "robbyrussell";
+ type = types.str;
+ description = ''
+ Name of the theme to be used by oh-my-zsh.
+ '';
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ example = ''
+ zstyle :omz:plugins:ssh-agent identities id_rsa id_rsa2 id_github
+ '';
+ type = types.lines;
+ description = ''
+ Extra settings for plugins.
+ '';
+ };
+ };
+ };
+
+in
+
+{
+ options = {
+ programs.zsh = {
+ enable = mkEnableOption "Z shell (Zsh)";
+
+ autocd = mkOption {
+ default = null;
+ description = ''
+ Automatically enter into a directory if typed directly into shell.
+ '';
+ type = types.nullOr types.bool;
+ };
+
+ cdpath = mkOption {
+ default = [];
+ description = ''
+ List of paths to autocomplete calls to `cd`.
+ '';
+ type = types.listOf types.str;
+ };
+
+ dotDir = mkOption {
+ default = null;
+ example = ".config/zsh";
+ description = ''
+ Directory where the zsh configuration and more should be located,
+ relative to the users home directory. The default is the home
+ directory.
+ '';
+ type = types.nullOr types.str;
+ };
+
+ shellAliases = mkOption {
+ default = {};
+ example = literalExample ''
+ {
+ ll = "ls -l";
+ ".." = "cd ..";
+ }
+ '';
+ description = ''
+ An attribute set that maps aliases (the top level attribute names in
+ this option) to command strings or directly to build outputs.
+ '';
+ type = types.attrsOf types.str;
+ };
+
+ shellGlobalAliases = mkOption {
+ default = {};
+ example = literalExample ''
+ {
+ UUID = "$(uuidgen | tr -d \\n)";
+ G = "| grep";
+ }
+ '';
+ description = ''
+ Similar to <varname><link linkend="opt-programs.zsh.shellAliases">opt-programs.zsh.shellAliases</link></varname>,
+ but are substituted anywhere on a line.
+ '';
+ type = types.attrsOf types.str;
+ };
+
+ dirHashes = mkOption {
+ default = {};
+ example = literalExample ''
+ {
+ docs = "$HOME/Documents";
+ vids = "$HOME/Videos";
+ dl = "$HOME/Downloads";
+ }
+ '';
+ description = ''
+ An attribute set that adds to named directory hash table.
+ '';
+ type = types.attrsOf types.str;
+ };
+
+ enableCompletion = mkOption {
+ default = true;
+ description = ''
+ Enable zsh completion. Don't forget to add
+ <programlisting language="nix">
+ environment.pathsToLink = [ "/share/zsh" ];
+ </programlisting>
+ to your system configuration to get completion for system packages (e.g. systemd).
+ '';
+ type = types.bool;
+ };
+
+ enableAutosuggestions = mkOption {
+ default = false;
+ description = "Enable zsh autosuggestions";
+ };
+
+ history = mkOption {
+ type = historyModule;
+ default = {};
+ description = "Options related to commands history configuration.";
+ };
+
+ defaultKeymap = mkOption {
+ type = types.nullOr (types.enum (attrNames bindkeyCommands));
+ default = null;
+ example = "emacs";
+ description = "The default base keymap to use.";
+ };
+
+ sessionVariables = mkOption {
+ default = {};
+ type = types.attrs;
+ example = { MAILCHECK = 30; };
+ description = "Environment variables that will be set for zsh session.";
+ };
+
+ initExtraBeforeCompInit = mkOption {
+ default = "";
+ type = types.lines;
+ description = "Extra commands that should be added to <filename>.zshrc</filename> before compinit.";
+ };
+
+ initExtra = mkOption {
+ default = "";
+ type = types.lines;
+ description = "Extra commands that should be added to <filename>.zshrc</filename>.";
+ };
+
+ initExtraFirst = mkOption {
+ default = "";
+ type = types.lines;
+ description = "Commands that should be added to top of <filename>.zshrc</filename>.";
+ };
+
+ envExtra = mkOption {
+ default = "";
+ type = types.lines;
+ description = "Extra commands that should be added to <filename>.zshenv</filename>.";
+ };
+
+ profileExtra = mkOption {
+ default = "";
+ type = types.lines;
+ description = "Extra commands that should be added to <filename>.zprofile</filename>.";
+ };
+
+ loginExtra = mkOption {
+ default = "";
+ type = types.lines;
+ description = "Extra commands that should be added to <filename>.zlogin</filename>.";
+ };
+
+ logoutExtra = mkOption {
+ default = "";
+ type = types.lines;
+ description = "Extra commands that should be added to <filename>.zlogout</filename>.";
+ };
+
+ plugins = mkOption {
+ type = types.listOf pluginModule;
+ default = [];
+ example = literalExample ''
+ [
+ {
+ # will source zsh-autosuggestions.plugin.zsh
+ name = "zsh-autosuggestions";
+ src = pkgs.fetchFromGitHub {
+ owner = "zsh-users";
+ repo = "zsh-autosuggestions";
+ rev = "v0.4.0";
+ sha256 = "0z6i9wjjklb4lvr7zjhbphibsyx51psv50gm07mbb0kj9058j6kc";
+ };
+ }
+ {
+ name = "enhancd";
+ file = "init.sh";
+ src = pkgs.fetchFromGitHub {
+ owner = "b4b4r07";
+ repo = "enhancd";
+ rev = "v2.2.1";
+ sha256 = "0iqa9j09fwm6nj5rpip87x3hnvbbz9w9ajgm6wkrd5fls8fn8i5g";
+ };
+ }
+ ]
+ '';
+ description = "Plugins to source in <filename>.zshrc</filename>.";
+ };
+
+ oh-my-zsh = mkOption {
+ type = ohMyZshModule;
+ default = {};
+ description = "Options to configure oh-my-zsh.";
+ };
+
+ localVariables = mkOption {
+ type = types.attrs;
+ default = {};
+ example = { POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=["dir" "vcs"]; };
+ description = ''
+ Extra local variables defined at the top of <filename>.zshrc</filename>.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ (mkIf (cfg.envExtra != "") {
+ home.file."${relToDotDir ".zshenv"}".text = cfg.envExtra;
+ })
+
+ (mkIf (cfg.profileExtra != "") {
+ home.file."${relToDotDir ".zprofile"}".text = cfg.profileExtra;
+ })
+
+ (mkIf (cfg.loginExtra != "") {
+ home.file."${relToDotDir ".zlogin"}".text = cfg.loginExtra;
+ })
+
+ (mkIf (cfg.logoutExtra != "") {
+ home.file."${relToDotDir ".zlogout"}".text = cfg.logoutExtra;
+ })
+
+ (mkIf cfg.oh-my-zsh.enable {
+ home.file."${relToDotDir ".zshenv"}".text = ''
+ ZSH="${pkgs.oh-my-zsh}/share/oh-my-zsh";
+ ZSH_CACHE_DIR="${config.xdg.cacheHome}/oh-my-zsh";
+ '';
+ })
+
+ (mkIf (cfg.dotDir != null) {
+ home.file."${relToDotDir ".zshenv"}".text = ''
+ ZDOTDIR=${zdotdir}
+ '';
+
+ # When dotDir is set, only use ~/.zshenv to source ZDOTDIR/.zshenv,
+ # This is so that if ZDOTDIR happens to be
+ # already set correctly (by e.g. spawning a zsh inside a zsh), all env
+ # vars still get exported
+ home.file.".zshenv".text = ''
+ source ${zdotdir}/.zshenv
+ '';
+ })
+
+ {
+ home.packages = with pkgs; [ zsh ]
+ ++ optional cfg.enableCompletion nix-zsh-completions
+ ++ optional cfg.oh-my-zsh.enable oh-my-zsh;
+
+ home.file."${relToDotDir ".zshrc"}".text = ''
+ ${cfg.initExtraFirst}
+
+ typeset -U path cdpath fpath manpath
+
+ ${optionalString (cfg.cdpath != []) ''
+ cdpath+=(${concatStringsSep " " cfg.cdpath})
+ ''}
+
+ for profile in ''${(z)NIX_PROFILES}; do
+ fpath+=($profile/share/zsh/site-functions $profile/share/zsh/$ZSH_VERSION/functions $profile/share/zsh/vendor-completions)
+ done
+
+ HELPDIR="${pkgs.zsh}/share/zsh/$ZSH_VERSION/help"
+
+ ${optionalString (cfg.defaultKeymap != null) ''
+ # Use ${cfg.defaultKeymap} keymap as the default.
+ ${getAttr cfg.defaultKeymap bindkeyCommands}
+ ''}
+
+ ${localVarsStr}
+
+ ${cfg.initExtraBeforeCompInit}
+
+ ${concatStrings (map (plugin: ''
+ path+="$HOME/${pluginsDir}/${plugin.name}"
+ fpath+="$HOME/${pluginsDir}/${plugin.name}"
+ '') cfg.plugins)}
+
+ # Oh-My-Zsh/Prezto calls compinit during initialization,
+ # calling it twice causes sight start up slowdown
+ # as all $fpath entries will be traversed again.
+ ${optionalString (cfg.enableCompletion && !cfg.oh-my-zsh.enable && !cfg.prezto.enable)
+ "autoload -U compinit && compinit"
+ }
+
+ ${optionalString cfg.enableAutosuggestions
+ "source ${pkgs.zsh-autosuggestions}/share/zsh-autosuggestions/zsh-autosuggestions.zsh"
+ }
+
+ # Environment variables
+ . "${config.home.profileDirectory}/etc/profile.d/hm-session-vars.sh"
+ ${envVarsStr}
+
+ ${optionalString cfg.oh-my-zsh.enable ''
+ # oh-my-zsh extra settings for plugins
+ ${cfg.oh-my-zsh.extraConfig}
+ # oh-my-zsh configuration generated by NixOS
+ ${optionalString (cfg.oh-my-zsh.plugins != [])
+ "plugins=(${concatStringsSep " " cfg.oh-my-zsh.plugins})"
+ }
+ ${optionalString (cfg.oh-my-zsh.custom != "")
+ "ZSH_CUSTOM=\"${cfg.oh-my-zsh.custom}\""
+ }
+ ${optionalString (cfg.oh-my-zsh.theme != "")
+ "ZSH_THEME=\"${cfg.oh-my-zsh.theme}\""
+ }
+ source $ZSH/oh-my-zsh.sh
+ ''}
+
+ ${optionalString cfg.prezto.enable
+ (builtins.readFile "${pkgs.zsh-prezto}/runcoms/zshrc")}
+
+ ${concatStrings (map (plugin: ''
+ if [ -f "$HOME/${pluginsDir}/${plugin.name}/${plugin.file}" ]; then
+ source "$HOME/${pluginsDir}/${plugin.name}/${plugin.file}"
+ fi
+ '') cfg.plugins)}
+
+ # History options should be set in .zshrc and after oh-my-zsh sourcing.
+ # See https://github.com/nix-community/home-manager/issues/177.
+ HISTSIZE="${toString cfg.history.size}"
+ SAVEHIST="${toString cfg.history.save}"
+ ${if versionAtLeast config.home.stateVersion "20.03"
+ then ''HISTFILE="${cfg.history.path}"''
+ else ''HISTFILE="$HOME/${cfg.history.path}"''}
+ mkdir -p "$(dirname "$HISTFILE")"
+
+ setopt HIST_FCNTL_LOCK
+ ${if cfg.history.ignoreDups then "setopt" else "unsetopt"} HIST_IGNORE_DUPS
+ ${if cfg.history.ignoreSpace then "setopt" else "unsetopt"} HIST_IGNORE_SPACE
+ ${if cfg.history.expireDuplicatesFirst then "setopt" else "unsetopt"} HIST_EXPIRE_DUPS_FIRST
+ ${if cfg.history.share then "setopt" else "unsetopt"} SHARE_HISTORY
+ ${if cfg.history.extended then "setopt" else "unsetopt"} EXTENDED_HISTORY
+ ${if cfg.autocd != null then "${if cfg.autocd then "setopt" else "unsetopt"} autocd" else ""}
+
+ ${cfg.initExtra}
+
+ # Aliases
+ ${aliasesStr}
+
+ # Global Aliases
+ ${globalAliasesStr}
+
+ # Named Directory Hashes
+ ${dirHashesStr}
+ '';
+ }
+
+ (mkIf cfg.oh-my-zsh.enable {
+ # Make sure we create a cache directory since some plugins expect it to exist
+ # See: https://github.com/nix-community/home-manager/issues/761
+ home.file."${config.xdg.cacheHome}/oh-my-zsh/.keep".text = "";
+ })
+
+ (mkIf (cfg.plugins != []) {
+ # Many plugins require compinit to be called
+ # but allow the user to opt out.
+ programs.zsh.enableCompletion = mkDefault true;
+
+ home.file =
+ foldl' (a: b: a // b) {}
+ (map (plugin: { "${pluginsDir}/${plugin.name}".source = plugin.src; })
+ cfg.plugins);
+ })
+ ]);
+}
diff --git a/infra/libkookie/home-manager/modules/programs/zsh/prezto.nix b/infra/libkookie/home-manager/modules/programs/zsh/prezto.nix
new file mode 100644
index 000000000000..1bd1be584326
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/programs/zsh/prezto.nix
@@ -0,0 +1,543 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.zsh.prezto;
+
+ relToDotDir = file:
+ (optionalString (config.programs.zsh.dotDir != null)
+ (config.programs.zsh.dotDir + "/")) + file;
+
+ preztoModule = types.submodule {
+ options = {
+ enable = mkEnableOption "prezto";
+
+ caseSensitive = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ example = true;
+ description =
+ "Set case-sensitivity for completion, history lookup, etc.";
+ };
+
+ color = mkOption {
+ type = types.nullOr types.bool;
+ default = true;
+ example = false;
+ description = "Color output (auto set to 'no' on dumb terminals)";
+ };
+
+ pmoduleDirs = mkOption {
+ type = types.listOf types.path;
+ default = [ ];
+ example = [ "$HOME/.zprezto-contrib" ];
+ description = "Add additional directories to load prezto modules from";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Additional configuration to add to <filename>.zpreztorc</filename>.
+ '';
+ };
+
+ extraModules = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "attr" "stat" ];
+ description = "Set the Zsh modules to load (man zshmodules).";
+ };
+
+ extraFunctions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "zargs" "zmv" ];
+ description = "Set the Zsh functions to load (man zshcontrib).";
+ };
+
+ pmodules = mkOption {
+ type = types.listOf types.str;
+ default = [
+ "environment"
+ "terminal"
+ "editor"
+ "history"
+ "directory"
+ "spectrum"
+ "utility"
+ "completion"
+ "prompt"
+ ];
+ description =
+ "Set the Prezto modules to load (browse modules). The order matters.";
+ };
+
+ autosuggestions.color = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "fg=blue";
+ description = "Set the query found color.";
+ };
+
+ completions.ignoredHosts = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "0.0.0.0" "127.0.0.1" ];
+ description =
+ "Set the entries to ignore in static */etc/hosts* for host completion.";
+ };
+
+ editor = {
+ keymap = mkOption {
+ type = types.nullOr (types.enum [ "emacs" "vi" ]);
+ default = "emacs";
+ example = "vi";
+ description = "Set the key mapping style to 'emacs' or 'vi'.";
+ };
+
+ dotExpansion = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ example = true;
+ description = "Auto convert .... to ../..";
+ };
+
+ promptContext = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ example = true;
+ description = "Allow the zsh prompt context to be shown.";
+ };
+ };
+
+ git.submoduleIgnore = mkOption {
+ type = types.nullOr (types.enum [ "dirty" "untracked" "all" "none" ]);
+ default = null;
+ example = "all";
+ description =
+ "Ignore submodules when they are 'dirty', 'untracked', 'all', or 'none'.";
+ };
+
+ gnuUtility.prefix = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "g";
+ description = "Set the command prefix on non-GNU systems.";
+ };
+
+ historySubstring = {
+ foundColor = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "fg=blue";
+ description = "Set the query found color.";
+ };
+
+ notFoundColor = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "fg=red";
+ description = "Set the query not found color.";
+ };
+
+ globbingFlags = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "Set the search globbing flags.";
+ };
+ };
+
+ macOS.dashKeyword = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "manpages";
+ description =
+ "Set the keyword used by `mand` to open man pages in Dash.app";
+ };
+
+ prompt = {
+ theme = mkOption {
+ type = types.nullOr types.str;
+ default = "sorin";
+ example = "pure";
+ description = ''
+ Set the prompt theme to load. Setting it to 'random'
+ loads a random theme. Auto set to 'off' on dumb terminals.'';
+ };
+
+ pwdLength = mkOption {
+ type = types.nullOr (types.enum [ "short" "long" "full" ]);
+ default = null;
+ example = "short";
+ description = ''
+ Set the working directory prompt display length. By
+ default, it is set to 'short'. Set it to 'long' (without '~' expansion) for
+ longer or 'full' (with '~' expansion) for even longer prompt display.'';
+ };
+
+ showReturnVal = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ example = true;
+ description = ''
+ Set the prompt to display the return code along with an
+ indicator for non-zero return codes. This is not supported by all prompts.'';
+ };
+ };
+
+ python = {
+ virtualenvAutoSwitch = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ example = true;
+ description = "Auto switch to Python virtualenv on directory change.";
+ };
+
+ virtualenvInitialize = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ example = true;
+ description =
+ "Automatically initialize virtualenvwrapper if pre-requisites are met.";
+ };
+ };
+
+ ruby.chrubyAutoSwitch = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ example = true;
+ description = "Auto switch the Ruby version on directory change.";
+ };
+
+ screen = {
+ autoStartLocal = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ example = true;
+ description =
+ "Auto start a session when Zsh is launched in a local terminal.";
+ };
+
+ autoStartRemote = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ example = true;
+ description =
+ "Auto start a session when Zsh is launched in a SSH connection.";
+ };
+ };
+
+ ssh.identities = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "id_rsa" "id_rsa2" "id_github" ];
+ description = "Set the SSH identities to load into the agent.";
+ };
+
+ syntaxHighlighting = {
+ highlighters = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "main" "brackets" "pattern" "line" "cursor" "root" ];
+ description = ''
+ Set syntax highlighters. By default, only the main
+ highlighter is enabled.'';
+ };
+
+ styles = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ example = {
+ builtin = "bg=blue";
+ command = "bg=blue";
+ function = "bg=blue";
+ };
+ description = "Set syntax highlighting styles.";
+ };
+
+ pattern = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ example = { "rm*-rf*" = "fg=white,bold,bg=red"; };
+ description = "Set syntax pattern styles.";
+ };
+ };
+
+ terminal = {
+ autoTitle = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ example = true;
+ description = "Auto set the tab and window titles.";
+ };
+
+ windowTitleFormat = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "%n@%m: %s";
+ description = "Set the window title format.";
+ };
+
+ tabTitleFormat = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "%m: %s";
+ description = "Set the tab title format.";
+ };
+
+ multiplexerTitleFormat = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "%s";
+ description = "Set the multiplexer title format.";
+ };
+ };
+
+ tmux = {
+ autoStartLocal = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ example = true;
+ description =
+ "Auto start a session when Zsh is launched in a local terminal.";
+ };
+
+ autoStartRemote = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ example = true;
+ description =
+ "Auto start a session when Zsh is launched in a SSH connection.";
+ };
+
+ itermIntegration = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ example = true;
+ description = "Integrate with iTerm2.";
+ };
+
+ defaultSessionName = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "YOUR DEFAULT SESSION NAME";
+ description = "Set the default session name.";
+ };
+ };
+
+ utility.safeOps = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ example = true;
+ description = ''
+ Enabled safe options. This aliases cp, ln, mv and rm so
+ that they prompt before deleting or overwriting files. Set to 'no' to disable
+ this safer behavior.'';
+ };
+ };
+ };
+
+in {
+ options = {
+ programs.zsh = {
+ prezto = mkOption {
+ type = preztoModule;
+ default = { };
+ description = "Options to configure prezto.";
+ };
+ };
+ };
+ config = mkIf cfg.enable (mkMerge [{
+ home.file."${relToDotDir ".zprofile"}".text =
+ builtins.readFile "${pkgs.zsh-prezto}/runcoms/zprofile";
+ home.file."${relToDotDir ".zlogin"}".text =
+ builtins.readFile "${pkgs.zsh-prezto}/runcoms/zlogin";
+ home.file."${relToDotDir ".zlogout"}".text =
+ builtins.readFile "${pkgs.zsh-prezto}/runcoms/zlogout";
+ home.packages = with pkgs; [ zsh-prezto ];
+
+ home.file."${relToDotDir ".zshenv"}".text =
+ (builtins.readFile "${pkgs.zsh-prezto}/runcoms/zshenv");
+ home.file."${relToDotDir ".zpreztorc"}".text = ''
+ # Generated by Nix
+ ${optionalString (cfg.caseSensitive != null) ''
+ zstyle ':prezto:*:*' case-sensitive '${
+ if cfg.caseSensitive then "yes" else "no"
+ }'
+ ''}
+ ${optionalString (cfg.color != null) ''
+ zstyle ':prezto:*:*' color '${if cfg.color then "yes" else "no"}'
+ ''}
+ ${optionalString (cfg.pmoduleDirs != [ ]) ''
+ zstyle ':prezto:load' pmodule-dirs ${
+ builtins.concatStringsSep " " cfg.pmoduleDirs
+ }
+ ''}
+ ${optionalString (cfg.extraModules != [ ]) ''
+ zstyle ':prezto:load' zmodule ${
+ strings.concatMapStringsSep " " strings.escapeShellArg
+ cfg.extraModules
+ }
+ ''}
+ ${optionalString (cfg.extraFunctions != [ ]) ''
+ zstyle ':prezto:load' zfunction ${
+ strings.concatMapStringsSep " " strings.escapeShellArg
+ cfg.extraFunctions
+ }
+ ''}
+ ${optionalString (cfg.pmodules != [ ]) ''
+ zstyle ':prezto:load' pmodule \
+ ${
+ strings.concatMapStringsSep " \\\n " strings.escapeShellArg
+ cfg.pmodules
+ }
+ ''}
+ ${optionalString (cfg.autosuggestions.color != null) ''
+ zstyle ':prezto:module:autosuggestions:color' found '${cfg.autosuggestions.color}'
+ ''}
+ ${optionalString (cfg.completions.ignoredHosts != [ ]) ''
+ zstyle ':prezto:module:completion:*:hosts' etc-host-ignores \
+ ${
+ strings.concatMapStringsSep " " strings.escapeShellArg
+ cfg.completions.ignoredHosts
+ }
+ ''}
+ ${optionalString (cfg.editor.keymap != null) ''
+ zstyle ':prezto:module:editor' key-bindings '${cfg.editor.keymap}'
+ ''}
+ ${optionalString (cfg.editor.dotExpansion != null) ''
+ zstyle ':prezto:module:editor' dot-expansion '${
+ if cfg.editor.dotExpansion then "yes" else "no"
+ }'
+ ''}
+ ${optionalString (cfg.editor.promptContext != null) ''
+ zstyle ':prezto:module:editor' ps-context '${
+ if cfg.editor.promptContext then "yes" else "no"
+ }'
+ ''}
+ ${optionalString (cfg.git.submoduleIgnore != null) ''
+ zstyle ':prezto:module:git:status:ignore' submodules '${cfg.git.submoduleIgnore}'
+ ''}
+ ${optionalString (cfg.gnuUtility.prefix != null) ''
+ zstyle ':prezto:module:gnu-utility' prefix '${cfg.gnuUtility.prefix}'
+ ''}
+ ${optionalString (cfg.historySubstring.foundColor != null) ''
+ zstyle ':prezto:module:history-substring-search:color' found '${cfg.historySubstring.foundColor}'
+ ''}
+ ${optionalString (cfg.historySubstring.notFoundColor != null) ''
+ zstyle ':prezto:module:history-substring-search:color' not-found '${cfg.historySubstring.notFoundColor}'
+ ''}
+ ${optionalString (cfg.historySubstring.globbingFlags != null) ''
+ zstyle ':prezto:module:history-substring-search:color' globbing-flags '${cfg.historySubstring.globbingFlags}'
+ ''}
+ ${optionalString (cfg.macOS.dashKeyword != null) ''
+ zstyle ':prezto:module:osx:man' dash-keyword '${cfg.macOS.dashKeyword}'
+ ''}
+ ${optionalString (cfg.prompt.theme != null) ''
+ zstyle ':prezto:module:prompt' theme '${cfg.prompt.theme}'
+ ''}
+ ${optionalString (cfg.prompt.pwdLength != null) ''
+ zstyle ':prezto:module:prompt' pwd-length '${cfg.prompt.pwdLength}'
+ ''}
+ ${optionalString (cfg.prompt.showReturnVal != null) ''
+ zstyle ':prezto:module:prompt' show-return-val '${cfg.prompt.showReturnVal}'
+ ''}
+ ${optionalString (cfg.python.virtualenvAutoSwitch != null) ''
+ zstyle ':prezto:module:python:virtualenv' auto-switch '${
+ if cfg.python.virtualenvAutoSwitch then "yes" else "no"
+ }'
+ ''}
+ ${optionalString (cfg.python.virtualenvInitialize != null) ''
+ zstyle ':prezto:module:python:virtualenv' initialize '${
+ if cfg.python.virtualenvInitialize then "yes" else "no"
+ }'
+ ''}
+ ${optionalString (cfg.ruby.chrubyAutoSwitch != null) ''
+ zstyle ':prezto:module:ruby:chruby' auto-switch '${
+ if cfg.ruby.chrubyAutoSwitch then "yes" else "no"
+ }'
+ ''}
+ ${optionalString (cfg.screen.autoStartLocal != null) ''
+ zstyle ':prezto:module:screen:auto-start' local '${
+ if cfg.screen.autoStartLocal then "yes" else "no"
+ }'
+ ''}
+ ${optionalString (cfg.screen.autoStartRemote != null) ''
+ zstyle ':prezto:module:screen:auto-start' remote '${
+ if cfg.screen.autoStartRemote then "yes" else "no"
+ }'
+ ''}
+ ${optionalString (cfg.ssh.identities != [ ]) ''
+ zstyle ':prezto:module:ssh:load' identities \
+ ${
+ strings.concatMapStringsSep " " strings.escapeShellArg
+ cfg.ssh.identities
+ }
+ ''}
+ ${optionalString (cfg.syntaxHighlighting.highlighters != [ ]) ''
+ zstyle ':prezto:module:syntax-highlighting' highlighters \
+ ${
+ strings.concatMapStringsSep " \\\n " strings.escapeShellArg
+ cfg.syntaxHighlighting.highlighters
+ }
+ ''}
+ ${optionalString (cfg.syntaxHighlighting.styles != { }) ''
+ zstyle ':prezto:module:syntax-highlighting' styles \
+ ${
+ builtins.concatStringsSep " \\\n" (attrsets.mapAttrsToList
+ (k: v: strings.escapeShellArg k + " " + strings.escapeShellArg v)
+ cfg.syntaxHighlighting.styles)
+ }
+ ''}
+ ${optionalString (cfg.syntaxHighlighting.pattern != { }) ''
+ zstyle ':prezto:module:syntax-highlighting' pattern \
+ ${
+ builtins.concatStringsSep " \\\n" (attrsets.mapAttrsToList
+ (k: v: strings.escapeShellArg k + " " + strings.escapeShellArg v)
+ cfg.syntaxHighlighting.pattern)
+ }
+ ''}
+ ${optionalString (cfg.terminal.autoTitle != null) ''
+ zstyle ':prezto:module:terminal' auto-title '${
+ if cfg.terminal.autoTitle then "yes" else "no"
+ }'
+ ''}
+ ${optionalString (cfg.terminal.windowTitleFormat != null) ''
+ zstyle ':prezto:module:terminal:window-title' format '${cfg.terminal.windowTitleFormat}'
+ ''}
+ ${optionalString (cfg.terminal.tabTitleFormat != null) ''
+ zstyle ':prezto:module:terminal:tab-title' format '${cfg.terminal.tabTitleFormat}'
+ ''}
+ ${optionalString (cfg.terminal.multiplexerTitleFormat != null) ''
+ zstyle ':prezto:module:terminal:multiplexer-title' format '${cfg.terminal.multiplexerTitleFormat}'
+ ''}
+ ${optionalString (cfg.tmux.autoStartLocal != null) ''
+ zstyle ':prezto:module:tmux:auto-start' local '${
+ if cfg.tmux.autoStartLocal then "yes" else "no"
+ }'
+ ''}
+ ${optionalString (cfg.tmux.autoStartRemote != null) ''
+ zstyle ':prezto:module:tmux:auto-start' remote '${
+ if cfg.tmux.autoStartRemote then "yes" else "no"
+ }'
+ ''}
+ ${optionalString (cfg.tmux.itermIntegration != null) ''
+ zstyle ':prezto:module:tmux:iterm' integrate '${
+ if cfg.tmux.itermIntegration then "yes" else "no"
+ }'
+ ''}
+ ${optionalString (cfg.tmux.defaultSessionName != null) ''
+ zstyle ':prezto:module:tmux:session' name '${cfg.tmux.defaultSessionName}'
+ ''}
+ ${optionalString (cfg.utility.safeOps != null) ''
+ zstyle ':prezto:module:utility' safe-ops '${
+ if cfg.utility.safeOps then "yes" else "no"
+ }'
+ ''}
+ ${cfg.extraConfig}
+ '';
+ }]);
+}
diff --git a/infra/libkookie/home-manager/modules/services/blueman-applet.nix b/infra/libkookie/home-manager/modules/services/blueman-applet.nix
new file mode 100644
index 000000000000..5a57acccc276
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/blueman-applet.nix
@@ -0,0 +1,36 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ options = {
+ services.blueman-applet = {
+ enable = mkEnableOption "" // {
+ description = ''
+ Whether to enable the Blueman applet.
+ </para><para>
+ Note, for the applet to work, the 'blueman' service should
+ be enabled system-wide. You can enable it in the system
+ configuration using
+ <programlisting language="nix">
+ services.blueman.enable = true;
+ </programlisting>
+ '';
+ };
+ };
+ };
+
+ config = mkIf config.services.blueman-applet.enable {
+ systemd.user.services.blueman-applet = {
+ Unit = {
+ Description = "Blueman applet";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = { ExecStart = "${pkgs.blueman}/bin/blueman-applet"; };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/caffeine.nix b/infra/libkookie/home-manager/modules/services/caffeine.nix
new file mode 100644
index 000000000000..bb24a0e05273
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/caffeine.nix
@@ -0,0 +1,33 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.caffeine;
+
+in {
+ meta.maintainers = [ maintainers.uvnikita ];
+
+ options = {
+ services.caffeine = { enable = mkEnableOption "Caffeine service"; };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.caffeine = {
+ Unit = { Description = "caffeine"; };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ Restart = "on-failure";
+ PrivateTmp = true;
+ ProtectSystem = "full";
+ ProtectHome = "yes";
+ Type = "exec";
+ Slice = "session.slice";
+ ExecStart = "${pkgs.caffeine-ng}/bin/caffeine";
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/cbatticon.nix b/infra/libkookie/home-manager/modules/services/cbatticon.nix
new file mode 100644
index 000000000000..0de69c5f9ec2
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/cbatticon.nix
@@ -0,0 +1,118 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.cbatticon;
+
+ package = pkgs.cbatticon;
+
+ makeCommand = commandName: commandArg:
+ optional (commandArg != null)
+ (let cmd = pkgs.writeShellScript commandName commandArg;
+ in "--${commandName} ${cmd}");
+
+ commandLine = concatStringsSep " " ([ "${package}/bin/cbatticon" ]
+ ++ makeCommand "command-critical-level" cfg.commandCriticalLevel
+ ++ makeCommand "command-left-click" cfg.commandLeftClick
+ ++ optional (cfg.iconType != null) "--icon-type ${cfg.iconType}"
+ ++ optional (cfg.lowLevelPercent != null)
+ "--low-level ${toString cfg.lowLevelPercent}"
+ ++ optional (cfg.criticalLevelPercent != null)
+ "--critical-level ${toString cfg.criticalLevelPercent}"
+ ++ optional (cfg.updateIntervalSeconds != null)
+ "--update-interval ${toString cfg.updateIntervalSeconds}"
+ ++ optional (cfg.hideNotification != null && cfg.hideNotification)
+ "--hide-notification");
+
+in {
+ meta.maintainers = [ maintainers.pmiddend ];
+
+ options = {
+ services.cbatticon = {
+ enable = mkEnableOption "cbatticon";
+
+ commandCriticalLevel = mkOption {
+ type = types.nullOr types.lines;
+ default = null;
+ example = ''
+ notify-send "battery critical!"
+ '';
+ description = ''
+ Command to execute when the critical battery level is reached.
+ '';
+ };
+
+ commandLeftClick = mkOption {
+ type = types.nullOr types.lines;
+ default = null;
+ description = ''
+ Command to execute when left clicking on the tray icon.
+ '';
+ };
+
+ iconType = mkOption {
+ type =
+ types.nullOr (types.enum [ "standard" "notification" "symbolic" ]);
+ default = null;
+ example = "symbolic";
+ description = "Icon type to display in the system tray.";
+ };
+
+ lowLevelPercent = mkOption {
+ type = types.nullOr (types.ints.between 0 100);
+ default = null;
+ example = 20;
+ description = ''
+ Low level percentage of the battery in percent (without the
+ percent symbol).
+ '';
+ };
+
+ criticalLevelPercent = mkOption {
+ type = types.nullOr (types.ints.between 0 100);
+ default = null;
+ example = 5;
+ description = ''
+ Critical level percentage of the battery in percent (without
+ the percent symbol).
+ '';
+ };
+
+ updateIntervalSeconds = mkOption {
+ type = types.nullOr types.ints.positive;
+ default = null;
+ example = 5;
+ description = ''
+ Number of seconds between updates of the battery information.
+ '';
+ };
+
+ hideNotification = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = "Hide the notification popups.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ package ];
+
+ systemd.user.services.cbatticon = {
+ Unit = {
+ Description = "cbatticon system tray battery icon";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ ExecStart = commandLine;
+ Restart = "on-abort";
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/clipmenu.nix b/infra/libkookie/home-manager/modules/services/clipmenu.nix
new file mode 100644
index 000000000000..2e1c10e43d86
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/clipmenu.nix
@@ -0,0 +1,43 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.clipmenu;
+
+in {
+ meta.maintainers = [ maintainers.DamienCassou ];
+
+ options.services.clipmenu = {
+ enable = mkEnableOption "clipmenu, the clipboard management daemon";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.clipmenu;
+ defaultText = "pkgs.clipmenu";
+ description = "clipmenu derivation to use.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+
+ systemd.user.services.clipmenu = {
+ Unit = {
+ Description = "Clipboard management daemon";
+ After = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ ExecStart = "${cfg.package}/bin/clipmenud";
+ Environment = "PATH=${
+ makeBinPath
+ (with pkgs; [ coreutils findutils gnugrep gnused systemd ])
+ }";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/compton.nix b/infra/libkookie/home-manager/modules/services/compton.nix
new file mode 100644
index 000000000000..0b8e7232b45f
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/compton.nix
@@ -0,0 +1,43 @@
+{ config, lib, pkgs, ... }:
+
+with lib; {
+ imports = let
+ old = n: [ "services" "compton" n ];
+ new = n: [ "services" "picom" n ];
+ in [
+ (mkRenamedOptionModule (old "activeOpacity") (new "activeOpacity"))
+ (mkRenamedOptionModule (old "backend") (new "backend"))
+ (mkRenamedOptionModule (old "blur") (new "blur"))
+ (mkRenamedOptionModule (old "blurExclude") (new "blurExclude"))
+ (mkRenamedOptionModule (old "extraOptions") (new "extraOptions"))
+ (mkRenamedOptionModule (old "fade") (new "fade"))
+ (mkRenamedOptionModule (old "fadeDelta") (new "fadeDelta"))
+ (mkRenamedOptionModule (old "fadeExclude") (new "fadeExclude"))
+ (mkRenamedOptionModule (old "fadeSteps") (new "fadeSteps"))
+ (mkRenamedOptionModule (old "inactiveDim") (new "inactiveDim"))
+ (mkRenamedOptionModule (old "inactiveOpacity") (new "inactiveOpacity"))
+ (mkRenamedOptionModule (old "menuOpacity") (new "menuOpacity"))
+ (mkRenamedOptionModule (old "noDNDShadow") (new "noDNDShadow"))
+ (mkRenamedOptionModule (old "noDockShadow") (new "noDockShadow"))
+ (mkRenamedOptionModule (old "opacityRule") (new "opacityRule"))
+ (mkRenamedOptionModule (old "package") (new "package"))
+ (mkRenamedOptionModule (old "refreshRate") (new "refreshRate"))
+ (mkRenamedOptionModule (old "shadow") (new "shadow"))
+ (mkRenamedOptionModule (old "shadowExclude") (new "shadowExclude"))
+ (mkRenamedOptionModule (old "shadowOffsets") (new "shadowOffsets"))
+ (mkRenamedOptionModule (old "shadowOpacity") (new "shadowOpacity"))
+ (mkChangedOptionModule (old "vSync") (new "vSync") (v: v != "none"))
+ ];
+
+ options.services.compton.enable = mkEnableOption "Compton X11 compositor" // {
+ visible = false;
+ };
+
+ config = mkIf config.services.compton.enable {
+ warnings = [
+ "Obsolete option `services.compton.enable' is used. It was renamed to `services.picom.enable'."
+ ];
+
+ services.picom.enable = true;
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/dropbox.nix b/infra/libkookie/home-manager/modules/services/dropbox.nix
new file mode 100644
index 000000000000..bcf3ba2b457c
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/dropbox.nix
@@ -0,0 +1,77 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.dropbox;
+ baseDir = ".dropbox-hm";
+ dropboxCmd = "${pkgs.dropbox-cli}/bin/dropbox";
+ homeBaseDir = "${config.home.homeDirectory}/${baseDir}";
+
+in {
+ meta.maintainers = [ maintainers.eyjhb ];
+
+ options = {
+ services.dropbox = {
+ enable = mkEnableOption "Dropbox daemon";
+
+ path = mkOption {
+ type = types.path;
+ default = "${config.home.homeDirectory}/Dropbox";
+ defaultText =
+ literalExample ''"''${config.home.homeDirectory}/Dropbox"'';
+ apply = toString; # Prevent copies to Nix store.
+ description = "Where to put the Dropbox directory.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.dropbox-cli ];
+
+ systemd.user.services.dropbox = {
+ Unit = { Description = "dropbox"; };
+
+ Install = { WantedBy = [ "default.target" ]; };
+
+ Service = {
+ Environment = [ "HOME=${homeBaseDir}" "DISPLAY=" ];
+
+ Type = "forking";
+ PIDFile = "${homeBaseDir}/.dropbox/dropbox.pid";
+
+ Restart = "on-failure";
+ PrivateTmp = true;
+ ProtectSystem = "full";
+ Nice = 10;
+
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ ExecStop = "${dropboxCmd} stop";
+ ExecStart = toString (pkgs.writeShellScript "dropbox-start" ''
+ # ensure we have the dirs we need
+ $DRY_RUN_CMD ${pkgs.coreutils}/bin/mkdir $VERBOSE_ARG -p \
+ ${homeBaseDir}/{.dropbox,.dropbox-dist,Dropbox}
+
+ # symlink them as needed
+ if [[ ! -d ${config.home.homeDirectory}/.dropbox ]]; then
+ $DRY_RUN_CMD ${pkgs.coreutils}/bin/ln $VERBOSE_ARG -s \
+ ${homeBaseDir}/.dropbox ${config.home.homeDirectory}/.dropbox
+ fi
+
+ if [[ ! -d ${escapeShellArg cfg.path} ]]; then
+ $DRY_RUN_CMD ${pkgs.coreutils}/bin/ln $VERBOSE_ARG -s \
+ ${homeBaseDir}/Dropbox ${escapeShellArg cfg.path}
+ fi
+
+ # get the dropbox bins if needed
+ if [[ ! -f $HOME/.dropbox-dist/VERSION ]]; then
+ ${pkgs.coreutils}/bin/yes | ${dropboxCmd} update
+ fi
+
+ ${dropboxCmd} start
+ '');
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/dunst.nix b/infra/libkookie/home-manager/modules/services/dunst.nix
new file mode 100644
index 000000000000..76fee146e6d1
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/dunst.nix
@@ -0,0 +1,162 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.dunst;
+
+ eitherStrBoolIntList = with types;
+ either str (either bool (either int (listOf str)));
+
+ toDunstIni = generators.toINI {
+ mkKeyValue = key: value:
+ let
+ value' = if isBool value then
+ (if value then "yes" else "no")
+ else if isString value then
+ ''"${value}"''
+ else
+ toString value;
+ in "${key}=${value'}";
+ };
+
+ themeType = types.submodule {
+ options = {
+ package = mkOption {
+ type = types.package;
+ example = literalExample "pkgs.gnome3.adwaita-icon-theme";
+ description = "Package providing the theme.";
+ };
+
+ name = mkOption {
+ type = types.str;
+ example = "Adwaita";
+ description = "The name of the theme within the package.";
+ };
+
+ size = mkOption {
+ type = types.str;
+ default = "32x32";
+ example = "16x16";
+ description = "The desired icon size.";
+ };
+ };
+ };
+
+ hicolorTheme = {
+ package = pkgs.hicolor-icon-theme;
+ name = "hicolor";
+ size = "32x32";
+ };
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ services.dunst = {
+ enable = mkEnableOption "the dunst notification daemon";
+
+ iconTheme = mkOption {
+ type = themeType;
+ default = hicolorTheme;
+ description = "Set the icon theme.";
+ };
+
+ settings = mkOption {
+ type = with types; attrsOf (attrsOf eitherStrBoolIntList);
+ default = { };
+ description = "Configuration written to ~/.config/dunstrc";
+ example = literalExample ''
+ {
+ global = {
+ geometry = "300x5-30+50";
+ transparency = 10;
+ frame_color = "#eceff1";
+ font = "Droid Sans 9";
+ };
+
+ urgency_normal = {
+ background = "#37474f";
+ foreground = "#eceff1";
+ timeout = 10;
+ };
+ };
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+ home.packages = [ (getOutput "man" pkgs.dunst) ];
+
+ xdg.dataFile."dbus-1/services/org.knopwob.dunst.service".source =
+ "${pkgs.dunst}/share/dbus-1/services/org.knopwob.dunst.service";
+
+ services.dunst.settings.global.icon_path = let
+ useCustomTheme = cfg.iconTheme.package != hicolorTheme.package
+ || cfg.iconTheme.name != hicolorTheme.name || cfg.iconTheme.size
+ != hicolorTheme.size;
+
+ basePaths = [
+ "/run/current-system/sw"
+ config.home.profileDirectory
+ cfg.iconTheme.package
+ ] ++ optional useCustomTheme hicolorTheme.package;
+
+ themes = [ cfg.iconTheme ] ++ optional useCustomTheme
+ (hicolorTheme // { size = cfg.iconTheme.size; });
+
+ categories = [
+ "actions"
+ "animations"
+ "apps"
+ "categories"
+ "devices"
+ "emblems"
+ "emotes"
+ "filesystem"
+ "intl"
+ "legacy"
+ "mimetypes"
+ "places"
+ "status"
+ "stock"
+ ];
+ in concatStringsSep ":" (concatMap (theme:
+ concatMap (basePath:
+ map (category:
+ "${basePath}/share/icons/${theme.name}/${theme.size}/${category}")
+ categories) basePaths) themes);
+
+ systemd.user.services.dunst = {
+ Unit = {
+ Description = "Dunst notification daemon";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ Type = "dbus";
+ BusName = "org.freedesktop.Notifications";
+ ExecStart = "${pkgs.dunst}/bin/dunst";
+ };
+ };
+ }
+
+ (mkIf (cfg.settings != { }) {
+ xdg.configFile."dunst/dunstrc" = {
+ text = toDunstIni cfg.settings;
+ onChange = ''
+ pkillVerbose=""
+ if [[ -v VERBOSE ]]; then
+ pkillVerbose="-e"
+ fi
+ $DRY_RUN_CMD ${pkgs.procps}/bin/pkill -u $USER $pkillVerbose dunst || true
+ unset pkillVerbose
+ '';
+ };
+ })
+ ]);
+}
diff --git a/infra/libkookie/home-manager/modules/services/dwm-status.nix b/infra/libkookie/home-manager/modules/services/dwm-status.nix
new file mode 100644
index 000000000000..a0c2a7243698
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/dwm-status.nix
@@ -0,0 +1,67 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.dwm-status;
+
+ jsonFormat = pkgs.formats.json { };
+
+ features = [ "audio" "backlight" "battery" "cpu_load" "network" "time" ];
+
+ finalConfig = { inherit (cfg) order; } // cfg.extraConfig;
+
+ configFile = jsonFormat.generate "dwm-status.json" finalConfig;
+
+in {
+ options = {
+ services.dwm-status = {
+ enable = mkEnableOption "dwm-status user service";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.dwm-status;
+ defaultText = literalExample "pkgs.dwm-status";
+ example = "pkgs.dwm-status.override { enableAlsaUtils = false; }";
+ description = "Which dwm-status package to use.";
+ };
+
+ order = mkOption {
+ type = types.listOf (types.enum features);
+ description = "List of enabled features in order.";
+ };
+
+ extraConfig = mkOption {
+ type = jsonFormat.type;
+ default = { };
+ example = literalExample ''
+ {
+ separator = "#";
+
+ battery = {
+ notifier_levels = [ 2 5 10 15 20 ];
+ };
+
+ time = {
+ format = "%H:%M";
+ };
+ }
+ '';
+ description = "Extra config of dwm-status.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.dwm-status = {
+ Unit = {
+ Description = "DWM status service";
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = { ExecStart = "${cfg.package}/bin/dwm-status ${configFile}"; };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/emacs.nix b/infra/libkookie/home-manager/modules/services/emacs.nix
new file mode 100644
index 000000000000..560d9a6c4b8b
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/emacs.nix
@@ -0,0 +1,147 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.emacs;
+ emacsCfg = config.programs.emacs;
+ emacsBinPath = "${cfg.package}/bin";
+ emacsVersion = getVersion cfg.package;
+
+ # Adapted from upstream emacs.desktop
+ clientDesktopItem = pkgs.writeTextDir "share/applications/emacsclient.desktop"
+ (generators.toINI { } {
+ "Desktop Entry" = {
+ Type = "Application";
+ Exec = "${emacsBinPath}/emacsclient ${
+ concatStringsSep " " cfg.client.arguments
+ } %F";
+ Terminal = false;
+ Name = "Emacs Client";
+ Icon = "emacs";
+ Comment = "Edit text";
+ GenericName = "Text Editor";
+ 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++;";
+ Categories = "Utility;TextEditor;";
+ StartupWMClass = "Emacs";
+ };
+ });
+
+ # Match the default socket path for the Emacs version so emacsclient continues
+ # to work without wrapping it. It might be worthwhile to allow customizing the
+ # socket path, but we would want to wrap emacsclient in the user profile to
+ # connect to the alternative socket by default for Emacs 26, and set
+ # EMACS_SOCKET_NAME for Emacs 27.
+ #
+ # As systemd doesn't perform variable expansion for the ListenStream param, we
+ # would also have to solve the problem of matching the shell path to the path
+ # used in the socket unit, which would likely involve templating. It seems of
+ # little value for the most common use case of one Emacs daemon per user
+ # session.
+ socketPath = if versionAtLeast emacsVersion "27" then
+ "%t/emacs/server"
+ else
+ "%T/emacs%U/server";
+
+in {
+ meta.maintainers = [ maintainers.tadfisher ];
+
+ options.services.emacs = {
+ enable = mkEnableOption "the Emacs daemon";
+
+ package = mkOption {
+ type = types.package;
+ default = if emacsCfg.enable then emacsCfg.finalPackage else pkgs.emacs;
+ defaultText = literalExample ''
+ if config.programs.emacs.enable then config.programs.emacs.finalPackage
+ else pkgs.emacs
+ '';
+ description = "The Emacs package to use.";
+ };
+
+ client = {
+ enable = mkEnableOption "generation of Emacs client desktop file";
+ arguments = mkOption {
+ type = with types; listOf str;
+ default = [ "-c" ];
+ description = ''
+ Command-line arguments to pass to <command>emacsclient</command>.
+ '';
+ };
+ };
+
+ # Attrset for forward-compatibility; there may be a need to customize the
+ # socket path, though allowing for such is not easy to do as systemd socket
+ # units don't perform variable expansion for 'ListenStream'.
+ socketActivation = {
+ enable = mkEnableOption "systemd socket activation for the Emacs service";
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+ assertions = [{
+ assertion = cfg.socketActivation.enable
+ -> versionAtLeast emacsVersion "26";
+ message = "Socket activation requires Emacs 26 or newer.";
+ }];
+
+ systemd.user.services.emacs = {
+ Unit = {
+ Description = "Emacs: the extensible, self-documenting text editor";
+ Documentation =
+ "info:emacs man:emacs(1) https://gnu.org/software/emacs/";
+
+ # Avoid killing the Emacs session, which may be full of
+ # unsaved buffers.
+ X-RestartIfChanged = false;
+ };
+
+ Service = {
+ # We wrap ExecStart in a login shell so Emacs starts with the user's
+ # environment, most importantly $PATH and $NIX_PROFILES. It may be
+ # worth investigating a more targeted approach for user services to
+ # import the user environment.
+ ExecStart = ''
+ ${pkgs.runtimeShell} -l -c "${emacsBinPath}/emacs --fg-daemon${
+ # In case the user sets 'server-directory' or 'server-name' in
+ # their Emacs config, we want to specify the socket path explicitly
+ # so launching 'emacs.service' manually doesn't break emacsclient
+ # when using socket activation.
+ optionalString cfg.socketActivation.enable
+ "=${escapeShellArg socketPath}"
+ }"'';
+ # We use '(kill-emacs 0)' to avoid exiting with a failure code, which
+ # would restart the service immediately.
+ ExecStop = "${emacsBinPath}/emacsclient --eval '(kill-emacs 0)'";
+ Restart = "on-failure";
+ };
+ } // optionalAttrs (!cfg.socketActivation.enable) {
+ Install = { WantedBy = [ "default.target" ]; };
+ };
+
+ home.packages = optional cfg.client.enable (hiPrio clientDesktopItem);
+ }
+
+ (mkIf cfg.socketActivation.enable {
+ systemd.user.sockets.emacs = {
+ Unit = {
+ Description = "Emacs: the extensible, self-documenting text editor";
+ Documentation =
+ "info:emacs man:emacs(1) https://gnu.org/software/emacs/";
+ };
+
+ Socket = {
+ ListenStream = socketPath;
+ FileDescriptorName = "server";
+ SocketMode = "0600";
+ DirectoryMode = "0700";
+ };
+
+ Install = { WantedBy = [ "sockets.target" ]; };
+ };
+ })
+ ]);
+}
diff --git a/infra/libkookie/home-manager/modules/services/flameshot.nix b/infra/libkookie/home-manager/modules/services/flameshot.nix
new file mode 100644
index 000000000000..c8659d51d1e5
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/flameshot.nix
@@ -0,0 +1,39 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.flameshot;
+ package = pkgs.flameshot;
+
+in {
+ meta.maintainers = [ maintainers.hamhut1066 ];
+
+ options = { services.flameshot = { enable = mkEnableOption "Flameshot"; }; };
+
+ config = mkIf cfg.enable {
+ home.packages = [ package ];
+
+ systemd.user.services.flameshot = {
+ Unit = {
+ Description = "Flameshot screenshot tool";
+ After = [
+ "graphical-session-pre.target"
+ "polybar.service"
+ "stalonetray.service"
+ "taffybar.service"
+ ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ Environment = "PATH=${config.home.profileDirectory}/bin";
+ ExecStart = "${package}/bin/flameshot";
+ Restart = "on-abort";
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/fluidsynth.nix b/infra/libkookie/home-manager/modules/services/fluidsynth.nix
new file mode 100644
index 000000000000..18913fe54262
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/fluidsynth.nix
@@ -0,0 +1,57 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.fluidsynth;
+
+in {
+ meta.maintainers = [ maintainers.valodim ];
+
+ options = {
+ services.fluidsynth = {
+ enable = mkEnableOption "fluidsynth midi synthesizer";
+
+ soundFont = mkOption {
+ type = types.path;
+ default = "${pkgs.soundfont-fluid}/share/soundfonts/FluidR3_GM2-2.sf2";
+ description = ''
+ The soundfont file to use, in SoundFont 2 format.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "--sample-rate 96000" ];
+ description = ''
+ Extra arguments, added verbatim to the fluidsynth command. See
+ <citerefentry>
+ <refentrytitle>fluidsynth.conf</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.fluidsynth = {
+ Unit = {
+ Description = "FluidSynth Daemon";
+ Documentation = "man:fluidsynth(1)";
+ BindsTo = [ "pulseaudio.service" ];
+ After = [ "pulseaudio.service" ];
+ };
+
+ Install = { WantedBy = [ "default.target" ]; };
+
+ Service = {
+ ExecStart = "${pkgs.fluidsynth}/bin/fluidsynth -a pulseaudio -si ${
+ lib.concatStringsSep " " cfg.extraOptions
+ } ${cfg.soundFont}";
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/gammastep.nix b/infra/libkookie/home-manager/modules/services/gammastep.nix
new file mode 100644
index 000000000000..7740c462cb0b
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/gammastep.nix
@@ -0,0 +1,166 @@
+# Adapted from Nixpkgs.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.gammastep;
+
+in {
+ meta.maintainers = [ maintainers.petabyteboy ];
+
+ options.services.gammastep = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ example = true;
+ description = ''
+ Enable Gammastep to change your screen's colour temperature depending on
+ the time of day.
+ '';
+ };
+
+ latitude = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Your current latitude, between <literal>-90.0</literal> and
+ <literal>90.0</literal>. Must be provided along with
+ longitude.
+ '';
+ };
+
+ longitude = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Your current longitude, between <literal>-180.0</literal> and
+ <literal>180.0</literal>. Must be provided along with
+ latitude.
+ '';
+ };
+
+ provider = mkOption {
+ type = types.enum [ "manual" "geoclue2" ];
+ default = "manual";
+ description = ''
+ The location provider to use for determining your location. If set to
+ <literal>manual</literal> you must also provide latitude/longitude.
+ If set to <literal>geoclue2</literal>, you must also enable the global
+ geoclue2 service.
+ '';
+ };
+
+ temperature = {
+ day = mkOption {
+ type = types.int;
+ default = 5500;
+ description = ''
+ Colour temperature to use during the day, between
+ <literal>1000</literal> and <literal>25000</literal> K.
+ '';
+ };
+
+ night = mkOption {
+ type = types.int;
+ default = 3700;
+ description = ''
+ Colour temperature to use at night, between
+ <literal>1000</literal> and <literal>25000</literal> K.
+ '';
+ };
+ };
+
+ brightness = {
+ day = mkOption {
+ type = types.str;
+ default = "1";
+ description = ''
+ Screen brightness to apply during the day,
+ between <literal>0.1</literal> and <literal>1.0</literal>.
+ '';
+ };
+
+ night = mkOption {
+ type = types.str;
+ default = "1";
+ description = ''
+ Screen brightness to apply during the night,
+ between <literal>0.1</literal> and <literal>1.0</literal>.
+ '';
+ };
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.gammastep;
+ defaultText = literalExample "pkgs.gammastep";
+ description = ''
+ gammastep derivation to use.
+ '';
+ };
+
+ tray = mkOption {
+ type = types.bool;
+ default = false;
+ example = true;
+ description = ''
+ Start the gammastep-indicator tray applet.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "-v" "-m randr" ];
+ description = ''
+ Additional command-line arguments to pass to
+ <command>gammastep</command>.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [{
+ assertion = cfg.provider == "manual" -> cfg.latitude != null
+ && cfg.longitude != null;
+ message = "Must provide services.gammastep.latitude and"
+ + " services.gammastep.latitude when"
+ + " services.gammastep.provider is set to \"manual\".";
+ }];
+
+ systemd.user.services.gammastep = {
+ Unit = {
+ Description = "Gammastep colour temperature adjuster";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ ExecStart = let
+ providerString = if cfg.provider == "manual" then
+ "${cfg.latitude}:${cfg.longitude}"
+ else
+ cfg.provider;
+
+ args = [
+ "-l ${providerString}"
+ "-t ${toString cfg.temperature.day}:${
+ toString cfg.temperature.night
+ }"
+ "-b ${toString cfg.brightness.day}:${toString cfg.brightness.night}"
+ ] ++ cfg.extraOptions;
+
+ command = if cfg.tray then "gammastep-indicator" else "gammastep";
+ in "${cfg.package}/bin/${command} ${concatStringsSep " " args}";
+ RestartSec = 3;
+ Restart = "always";
+ };
+ };
+ };
+
+}
diff --git a/infra/libkookie/home-manager/modules/services/getmail.nix b/infra/libkookie/home-manager/modules/services/getmail.nix
new file mode 100644
index 000000000000..e7a1b1a46277
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/getmail.nix
@@ -0,0 +1,55 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.getmail;
+
+ accounts =
+ filter (a: a.getmail.enable) (attrValues config.accounts.email.accounts);
+
+ # Note: The getmail service does not expect a path, but just the filename!
+ renderConfigFilepath = a:
+ if a.primary then "getmailrc" else "getmail${a.name}";
+ configFiles =
+ concatMapStringsSep " " (a: " --rcfile ${renderConfigFilepath a}") accounts;
+in {
+ options = {
+ services.getmail = {
+ enable = mkEnableOption
+ "the getmail systemd service to automatically retrieve mail";
+
+ frequency = mkOption {
+ type = types.str;
+ default = "*:0/5";
+ example = "hourly";
+ description = ''
+ The refresh frequency. Check <literal>man systemd.time</literal> for
+ more information on the syntax. If you use a gpg-agent in
+ combination with the passwordCommand, keep the poll
+ frequency below the cache-ttl value (as set by the
+ <literal>default</literal>) to avoid pinentry asking
+ permanently for a password.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.getmail = {
+ Unit = { Description = "getmail email fetcher"; };
+ Service = { ExecStart = "${pkgs.getmail}/bin/getmail ${configFiles}"; };
+ };
+
+ systemd.user.timers.getmail = {
+ Unit = { Description = "getmail email fetcher"; };
+ Timer = {
+ OnCalendar = "${cfg.frequency}";
+ Unit = "getmail.service";
+ };
+ Install = { WantedBy = [ "timers.target" ]; };
+ };
+
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/gnome-keyring.nix b/infra/libkookie/home-manager/modules/services/gnome-keyring.nix
new file mode 100644
index 000000000000..ce39cea93f95
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/gnome-keyring.nix
@@ -0,0 +1,46 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.gnome-keyring;
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ services.gnome-keyring = {
+ enable = mkEnableOption "GNOME Keyring";
+
+ components = mkOption {
+ type = types.listOf (types.enum [ "pkcs11" "secrets" "ssh" ]);
+ default = [ ];
+ description = ''
+ The GNOME keyring components to start. If empty then the
+ default set of components will be started.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.gnome-keyring = {
+ Unit = {
+ Description = "GNOME Keyring";
+ PartOf = [ "graphical-session-pre.target" ];
+ };
+
+ Service = {
+ ExecStart = let
+ args = concatStringsSep " " ([ "--start" "--foreground" ]
+ ++ optional (cfg.components != [ ])
+ ("--components=" + concatStringsSep "," cfg.components));
+ in "${pkgs.gnome3.gnome-keyring}/bin/gnome-keyring-daemon ${args}";
+ Restart = "on-abort";
+ };
+
+ Install = { WantedBy = [ "graphical-session-pre.target" ]; };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/gpg-agent.nix b/infra/libkookie/home-manager/modules/services/gpg-agent.nix
new file mode 100644
index 000000000000..16a4723fea7d
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/gpg-agent.nix
@@ -0,0 +1,281 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.gpg-agent;
+
+ gpgInitStr = ''
+ GPG_TTY="$(tty)"
+ export GPG_TTY
+ ''
+ + optionalString cfg.enableSshSupport
+ "${pkgs.gnupg}/bin/gpg-connect-agent updatestartuptty /bye > /dev/null";
+
+in
+
+{
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ services.gpg-agent = {
+ enable = mkEnableOption "GnuPG private key agent";
+
+ defaultCacheTtl = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ Set the time a cache entry is valid to the given number of
+ seconds.
+ '';
+ };
+
+ defaultCacheTtlSsh = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ Set the time a cache entry used for SSH keys is valid to the
+ given number of seconds.
+ '';
+ };
+
+ maxCacheTtl = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ Set the maximum time a cache entry is valid to n seconds. After this
+ time a cache entry will be expired even if it has been accessed
+ recently or has been set using gpg-preset-passphrase. The default is
+ 2 hours (7200 seconds).
+ '';
+ };
+
+ maxCacheTtlSsh = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = ''
+ Set the maximum time a cache entry used for SSH keys is valid to n
+ seconds. After this time a cache entry will be expired even if it has
+ been accessed recently or has been set using gpg-preset-passphrase.
+ The default is 2 hours (7200 seconds).
+ '';
+ };
+
+ enableSshSupport = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to use the GnuPG key agent for SSH keys.
+ '';
+ };
+
+ sshKeys = mkOption {
+ type = types.nullOr (types.listOf types.str);
+ default = null;
+ description = ''
+ Which GPG keys (by keygrip) to expose as SSH keys.
+ '';
+ };
+
+ enableExtraSocket = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable extra socket of the GnuPG key agent (useful for GPG
+ Agent forwarding).
+ '';
+ };
+
+ verbose = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to produce verbose output.
+ '';
+ };
+
+ grabKeyboardAndMouse = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Tell the pinentry to grab the keyboard and mouse. This
+ option should in general be used to avoid X-sniffing
+ attacks. When disabled, this option passes
+ <option>no-grab</option> setting to gpg-agent.
+ '';
+ };
+
+ enableScDaemon = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Make use of the scdaemon tool. This option has the effect of
+ enabling the ability to do smartcard operations. When
+ disabled, this option passes
+ <option>disable-scdaemon</option> setting to gpg-agent.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ allow-emacs-pinentry
+ allow-loopback-pinentry
+ '';
+ description = ''
+ Extra configuration lines to append to the gpg-agent
+ configuration file.
+ '';
+ };
+
+ pinentryFlavor = mkOption {
+ type = types.nullOr (types.enum pkgs.pinentry.flavors);
+ example = "gnome3";
+ default = "gtk2";
+ description = ''
+ Which pinentry interface to use. If not
+ <literal>null</literal>, it sets
+ <option>pinentry-program</option> in
+ <filename>gpg-agent.conf</filename>. Beware that
+ <literal>pinentry-gnome3</literal> may not work on non-Gnome
+ systems. You can fix it by adding the following to your
+ system configuration:
+ <programlisting language="nix">
+ services.dbus.packages = [ pkgs.gcr ];
+ </programlisting>
+ For this reason, the default is <literal>gtk2</literal> for
+ now.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+ home.file.".gnupg/gpg-agent.conf".text = concatStringsSep "\n" (
+ optional (cfg.enableSshSupport) "enable-ssh-support"
+ ++
+ optional (!cfg.grabKeyboardAndMouse) "no-grab"
+ ++
+ optional (!cfg.enableScDaemon) "disable-scdaemon"
+ ++
+ optional (cfg.defaultCacheTtl != null)
+ "default-cache-ttl ${toString cfg.defaultCacheTtl}"
+ ++
+ optional (cfg.defaultCacheTtlSsh != null)
+ "default-cache-ttl-ssh ${toString cfg.defaultCacheTtlSsh}"
+ ++
+ optional (cfg.maxCacheTtl != null)
+ "max-cache-ttl ${toString cfg.maxCacheTtl}"
+ ++
+ optional (cfg.maxCacheTtlSsh != null)
+ "max-cache-ttl-ssh ${toString cfg.maxCacheTtlSsh}"
+ ++
+ optional (cfg.pinentryFlavor != null)
+ "pinentry-program ${pkgs.pinentry.${cfg.pinentryFlavor}}/bin/pinentry"
+ ++
+ [ cfg.extraConfig ]
+ );
+
+ home.sessionVariables =
+ optionalAttrs cfg.enableSshSupport {
+ SSH_AUTH_SOCK = "$(${pkgs.gnupg}/bin/gpgconf --list-dirs agent-ssh-socket)";
+ };
+
+ programs.bash.initExtra = gpgInitStr;
+ programs.zsh.initExtra = gpgInitStr;
+ }
+
+ (mkIf (cfg.sshKeys != null) {
+ # Trailing newlines are important
+ home.file.".gnupg/sshcontrol".text = concatMapStrings (s: "${s}\n") cfg.sshKeys;
+ })
+
+ # The systemd units below are direct translations of the
+ # descriptions in the
+ #
+ # ${pkgs.gnupg}/share/doc/gnupg/examples/systemd-user
+ #
+ # directory.
+ {
+ systemd.user.services.gpg-agent = {
+ Unit = {
+ Description = "GnuPG cryptographic agent and passphrase cache";
+ Documentation = "man:gpg-agent(1)";
+ Requires = "gpg-agent.socket";
+ After = "gpg-agent.socket";
+ # This is a socket-activated service:
+ RefuseManualStart = true;
+ };
+
+ Service = {
+ ExecStart = "${pkgs.gnupg}/bin/gpg-agent --supervised"
+ + optionalString cfg.verbose " --verbose";
+ ExecReload = "${pkgs.gnupg}/bin/gpgconf --reload gpg-agent";
+ };
+ };
+
+ systemd.user.sockets.gpg-agent = {
+ Unit = {
+ Description = "GnuPG cryptographic agent and passphrase cache";
+ Documentation = "man:gpg-agent(1)";
+ };
+
+ Socket = {
+ ListenStream = "%t/gnupg/S.gpg-agent";
+ FileDescriptorName = "std";
+ SocketMode = "0600";
+ DirectoryMode = "0700";
+ };
+
+ Install = {
+ WantedBy = [ "sockets.target" ];
+ };
+ };
+ }
+
+ (mkIf cfg.enableSshSupport {
+ systemd.user.sockets.gpg-agent-ssh = {
+ Unit = {
+ Description = "GnuPG cryptographic agent (ssh-agent emulation)";
+ Documentation = "man:gpg-agent(1) man:ssh-add(1) man:ssh-agent(1) man:ssh(1)";
+ };
+
+ Socket = {
+ ListenStream = "%t/gnupg/S.gpg-agent.ssh";
+ FileDescriptorName = "ssh";
+ Service = "gpg-agent.service";
+ SocketMode = "0600";
+ DirectoryMode = "0700";
+ };
+
+ Install = {
+ WantedBy = [ "sockets.target" ];
+ };
+ };
+ })
+
+ (mkIf cfg.enableExtraSocket {
+ systemd.user.sockets.gpg-agent-extra = {
+ Unit = {
+ Description = "GnuPG cryptographic agent and passphrase cache (restricted)";
+ Documentation = "man:gpg-agent(1) man:ssh(1)";
+ };
+
+ Socket = {
+ ListenStream = "%t/gnupg/S.gpg-agent.extra";
+ FileDescriptorName = "extra";
+ Service = "gpg-agent.service";
+ SocketMode = "0600";
+ DirectoryMode = "0700";
+ };
+
+ Install = {
+ WantedBy = [ "sockets.target" ];
+ };
+ };
+ })
+ ]);
+}
diff --git a/infra/libkookie/home-manager/modules/services/grobi.nix b/infra/libkookie/home-manager/modules/services/grobi.nix
new file mode 100644
index 000000000000..4dfc5d6331f1
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/grobi.nix
@@ -0,0 +1,97 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.grobi;
+
+ eitherStrBoolIntList = with types;
+ either str (either bool (either int (listOf str)));
+
+in {
+ meta.maintainers = [ maintainers.mbrgm ];
+
+ options = {
+ services.grobi = {
+ enable = mkEnableOption "the grobi display setup daemon";
+
+ executeAfter = mkOption {
+ type = with types; listOf str;
+ default = [ ];
+ example = [ "setxkbmap dvorak" ];
+ description = ''
+ Commands to be run after an output configuration was
+ changed. The Nix value declared here will be translated to
+ JSON and written to the <option>execute_after</option> key
+ in <filename>~/.config/grobi.conf</filename>.
+ '';
+ };
+
+ rules = mkOption {
+ type = with types; listOf (attrsOf eitherStrBoolIntList);
+ default = [ ];
+ example = literalExample ''
+ [
+ {
+ name = "Home";
+ outputs_connected = [ "DP-2" ];
+ configure_single = "DP-2";
+ primary = true;
+ atomic = true;
+ execute_after = [
+ "${pkgs.xorg.xrandr}/bin/xrandr --dpi 96"
+ "${pkgs.xmonad-with-packages}/bin/xmonad --restart";
+ ];
+ }
+ {
+ name = "Mobile";
+ outputs_disconnected = [ "DP-2" ];
+ configure_single = "eDP-1";
+ primary = true;
+ atomic = true;
+ execute_after = [
+ "${pkgs.xorg.xrandr}/bin/xrandr --dpi 120"
+ "${pkgs.xmonad-with-packages}/bin/xmonad --restart";
+ ];
+ }
+ ]
+ '';
+ description = ''
+ These are the rules grobi tries to match to the current
+ output configuration. The rules are evaluated top to bottom,
+ the first matching rule is applied and processing stops. See
+ <link xlink:href="https://github.com/fd0/grobi/blob/master/doc/grobi.conf"/>
+ for more information. The Nix value declared here will be
+ translated to JSON and written to the <option>rules</option>
+ key in <filename>~/.config/grobi.conf</filename>.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.grobi = {
+ Unit = {
+ Description = "grobi display auto config daemon";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ Type = "simple";
+ ExecStart = "${pkgs.grobi}/bin/grobi watch -v";
+ Restart = "always";
+ RestartSec = "2s";
+ Environment = "PATH=${pkgs.xorg.xrandr}/bin:${pkgs.bash}/bin";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+
+ xdg.configFile."grobi.conf".text = builtins.toJSON {
+ execute_after = cfg.executeAfter;
+ rules = cfg.rules;
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/hound.nix b/infra/libkookie/home-manager/modules/services/hound.nix
new file mode 100644
index 000000000000..07b5d4765388
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/hound.nix
@@ -0,0 +1,76 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.hound;
+
+ jsonFormat = pkgs.formats.json { };
+
+ configFile = jsonFormat.generate "hound-config.json" {
+ max-concurrent-indexers = cfg.maxConcurrentIndexers;
+ dbpath = cfg.databasePath;
+ repos = cfg.repositories;
+ health-check-url = "/healthz";
+ };
+
+ houndOptions = [ "--addr ${cfg.listenAddress}" "--conf ${configFile}" ];
+
+in {
+ meta.maintainers = [ maintainers.adisbladis ];
+
+ options.services.hound = {
+ enable = mkEnableOption "hound";
+
+ maxConcurrentIndexers = mkOption {
+ type = types.ints.positive;
+ default = 2;
+ description = "Limit the amount of concurrent indexers.";
+ };
+
+ databasePath = mkOption {
+ type = types.path;
+ default = "${config.xdg.dataHome}/hound";
+ defaultText = "$XDG_DATA_HOME/hound";
+ description = "The Hound database path.";
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "localhost:6080";
+ description = "Listen address of the Hound daemon.";
+ };
+
+ repositories = mkOption {
+ type = types.attrsOf jsonFormat.type;
+ default = { };
+ example = literalExample ''
+ {
+ SomeGitRepo = {
+ url = "https://www.github.com/YourOrganization/RepoOne.git";
+ ms-between-poll = 10000;
+ exclude-dot-files = true;
+ };
+ }
+ '';
+ description = "The repository configuration.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.hound ];
+
+ systemd.user.services.hound = {
+ Unit = { Description = "Hound source code search engine"; };
+
+ Install = { WantedBy = [ "default.target" ]; };
+
+ Service = {
+ Environment = "PATH=${makeBinPath [ pkgs.mercurial pkgs.git ]}";
+ ExecStart =
+ "${pkgs.hound}/bin/houndd ${concatStringsSep " " houndOptions}";
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/imapnotify-accounts.nix b/infra/libkookie/home-manager/modules/services/imapnotify-accounts.nix
new file mode 100644
index 000000000000..94bdce5dfb46
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/imapnotify-accounts.nix
@@ -0,0 +1,33 @@
+{ lib, ... }:
+
+with lib;
+
+{
+ options.imapnotify = {
+ enable = mkEnableOption "imapnotify";
+
+ onNotify = mkOption {
+ type = with types; either str (attrsOf str);
+ default = "";
+ example = "\${pkgs.isync}/bin/mbsync test-%s";
+ description = "Shell commands to run on any event.";
+ };
+
+ onNotifyPost = mkOption {
+ type = with types; either str (attrsOf str);
+ default = "";
+ example = {
+ mail =
+ "\${pkgs.notmuch}/bin/notmuch new && \${pkgs.libnotify}/bin/notify-send 'New mail arrived'";
+ };
+ description = "Shell commands to run after onNotify event.";
+ };
+
+ boxes = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "Inbox" "[Gmail]/MyLabel" ];
+ description = "IMAP folders to watch.";
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/imapnotify.nix b/infra/libkookie/home-manager/modules/services/imapnotify.nix
new file mode 100644
index 000000000000..b59b006e335f
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/imapnotify.nix
@@ -0,0 +1,90 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.imapnotify;
+
+ safeName = lib.replaceChars [ "@" ":" "\\" "[" "]" ] [ "-" "-" "-" "" "" ];
+
+ imapnotifyAccounts =
+ filter (a: a.imapnotify.enable) (attrValues config.accounts.email.accounts);
+
+ genAccountUnit = account:
+ let name = safeName account.name;
+ in {
+ name = "imapnotify-${name}";
+ value = {
+ Unit = { Description = "imapnotify for ${name}"; };
+
+ Service = {
+ ExecStart =
+ "${pkgs.imapnotify}/bin/imapnotify -c ${genAccountConfig account}";
+ } // optionalAttrs account.notmuch.enable {
+ Environment =
+ "NOTMUCH_CONFIG=${config.xdg.configHome}/notmuch/notmuchrc";
+ };
+
+ Install = { WantedBy = [ "default.target" ]; };
+ };
+ };
+
+ genAccountConfig = account:
+ pkgs.writeText "imapnotify-${safeName account.name}-config.js" (let
+ port = if account.imap.port != null then
+ account.imap.port
+ else if account.imap.tls.enable then
+ 993
+ else
+ 143;
+
+ toJSON = builtins.toJSON;
+ in ''
+ var child_process = require('child_process');
+
+ function getStdout(cmd) {
+ var stdout = child_process.execSync(cmd);
+ return stdout.toString().trim();
+ }
+
+ exports.host = ${toJSON account.imap.host}
+ exports.port = ${toJSON port};
+ exports.tls = ${toJSON account.imap.tls.enable};
+ exports.username = ${toJSON account.userName};
+ exports.password = getStdout("${toString account.passwordCommand}");
+ exports.onNotify = ${toJSON account.imapnotify.onNotify};
+ exports.onNotifyPost = ${toJSON account.imapnotify.onNotifyPost};
+ exports.boxes = ${toJSON account.imapnotify.boxes};
+ '');
+
+in {
+ meta.maintainers = [ maintainers.nickhu ];
+
+ options = {
+ services.imapnotify = { enable = mkEnableOption "imapnotify"; };
+
+ accounts.email.accounts = mkOption {
+ type = with types; attrsOf (submodule (import ./imapnotify-accounts.nix));
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = let
+ checkAccounts = pred: msg:
+ let badAccounts = filter pred imapnotifyAccounts;
+ in {
+ assertion = badAccounts == [ ];
+ message = "imapnotify: Missing ${msg} for accounts: "
+ + concatMapStringsSep ", " (a: a.name) badAccounts;
+ };
+ in [
+ (checkAccounts (a: a.maildir == null) "maildir configuration")
+ (checkAccounts (a: a.imap == null) "IMAP configuration")
+ (checkAccounts (a: a.passwordCommand == null) "password command")
+ (checkAccounts (a: a.userName == null) "username")
+ ];
+
+ systemd.user.services = listToAttrs (map genAccountUnit imapnotifyAccounts);
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/kanshi.nix b/infra/libkookie/home-manager/modules/services/kanshi.nix
new file mode 100644
index 000000000000..4e5e5f104e65
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/kanshi.nix
@@ -0,0 +1,194 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.kanshi;
+
+ outputModule = types.submodule {
+ options = {
+
+ criteria = mkOption {
+ type = types.str;
+ description = ''
+ The criteria can either be an output name, an output description or "*".
+ The latter can be used to match any output.
+
+ On
+ <citerefentry>
+ <refentrytitle>sway</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>,
+ output names and descriptions can be obtained via
+ <literal>swaymsg -t get_outputs</literal>.
+ '';
+ };
+
+ status = mkOption {
+ type = types.nullOr (types.enum [ "enable" "disable" ]);
+ default = null;
+ description = ''
+ Enables or disables the specified output.
+ '';
+ };
+
+ mode = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "1920x1080@60Hz";
+ description = ''
+ &lt;width&gt;x&lt;height&gt;[@&lt;rate&gt;[Hz]]
+ </para><para>
+ Configures the specified output to use the specified mode.
+ Modes are a combination of width and height (in pixels) and
+ a refresh rate (in Hz) that your display can be configured to use.
+ '';
+ };
+
+ position = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "1600,0";
+ description = ''
+ &lt;x&gt;,&lt;y&gt;
+ </para><para>
+ Places the output at the specified position in the global coordinates
+ space.
+ '';
+ };
+
+ scale = mkOption {
+ type = types.nullOr types.float;
+ default = null;
+ example = 2;
+ description = ''
+ Scales the output by the specified scale factor.
+ '';
+ };
+
+ transform = mkOption {
+ type = types.nullOr (types.enum [
+ "normal"
+ "90"
+ "180"
+ "270"
+ "flipped"
+ "flipped-90"
+ "flipped-180"
+ "flipped-270"
+ ]);
+ default = null;
+ description = ''
+ Sets the output transform.
+ '';
+ };
+ };
+ };
+
+ outputStr = { criteria, status, mode, position, scale, transform, ... }:
+ ''output "${criteria}"'' + optionalString (status != null) " ${status}"
+ + optionalString (mode != null) " mode ${mode}"
+ + optionalString (position != null) " position ${position}"
+ + optionalString (scale != null) " scale ${toString scale}"
+ + optionalString (transform != null) " transform ${transform}";
+
+ profileModule = types.submodule {
+ options = {
+ outputs = mkOption {
+ type = types.listOf outputModule;
+ default = [ ];
+ description = ''
+ Outputs configuration.
+ '';
+ };
+
+ exec = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example =
+ "\${pkg.sway}/bin/swaymsg workspace 1, move workspace to eDP-1";
+ description = ''
+ Command executed after the profile is succesfully applied.
+ '';
+ };
+ };
+ };
+
+ profileStr = name:
+ { outputs, exec, ... }:
+ ''
+ profile ${name} {
+ ${concatStringsSep "\n " (map outputStr outputs)}
+ '' + optionalString (exec != null) " exec ${exec}\n" + ''
+ }
+ '';
+in {
+
+ meta.maintainers = [ maintainers.nurelin ];
+
+ options.services.kanshi = {
+ enable = mkEnableOption
+ "kanshi, a Wayland daemon that automatically configures outputs";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.kanshi;
+ defaultText = literalExample "pkgs.kanshi";
+ description = ''
+ kanshi derivation to use.
+ '';
+ };
+
+ profiles = mkOption {
+ type = types.attrsOf profileModule;
+ default = { };
+ description = ''
+ List of profiles.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration lines to append to the kanshi
+ configuration file.
+ '';
+ };
+
+ systemdTarget = mkOption {
+ type = types.str;
+ default = "sway-session.target";
+ description = ''
+ Systemd target to bind to.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ xdg.configFile."kanshi/config".text = ''
+ ${concatStringsSep "\n" (mapAttrsToList profileStr cfg.profiles)}
+ ${cfg.extraConfig}
+ '';
+
+ systemd.user.services.kanshi = {
+ Unit = {
+ Description = "Dynamic output configuration";
+ Documentation = "man:kanshi(1)";
+ PartOf = cfg.systemdTarget;
+ Requires = cfg.systemdTarget;
+ After = cfg.systemdTarget;
+ };
+
+ Service = {
+ Type = "simple";
+ ExecStart = "${cfg.package}/bin/kanshi";
+ Restart = "always";
+ };
+
+ Install = { WantedBy = [ cfg.systemdTarget ]; };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/kbfs.nix b/infra/libkookie/home-manager/modules/services/kbfs.nix
new file mode 100644
index 000000000000..863f4feea3ba
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/kbfs.nix
@@ -0,0 +1,67 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.kbfs;
+
+in
+
+{
+ options = {
+ services.kbfs = {
+ enable = mkEnableOption "Keybase File System";
+
+ mountPoint = mkOption {
+ type = types.str;
+ default = "keybase";
+ description = ''
+ Mount point for the Keybase filesystem, relative to
+ <envar>HOME</envar>.
+ '';
+ };
+
+ extraFlags = mkOption {
+ type = types.listOf types.str;
+ default = [];
+ example = [
+ "-label kbfs"
+ "-mount-type normal"
+ ];
+ description = ''
+ Additional flags to pass to the Keybase filesystem on launch.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.kbfs = {
+ Unit = {
+ Description = "Keybase File System";
+ Requires = [ "keybase.service" ];
+ After = [ "keybase.service" ];
+ };
+
+ Service =
+ let
+ mountPoint = "\"%h/${cfg.mountPoint}\"";
+ in {
+ Environment = "PATH=/run/wrappers/bin KEYBASE_SYSTEMD=1";
+ ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p ${mountPoint}";
+ ExecStart ="${pkgs.kbfs}/bin/kbfsfuse ${toString cfg.extraFlags} ${mountPoint}";
+ ExecStopPost = "/run/wrappers/bin/fusermount -u ${mountPoint}";
+ Restart = "on-failure";
+ PrivateTmp = true;
+ };
+
+ Install = {
+ WantedBy = [ "default.target" ];
+ };
+ };
+
+ home.packages = [ pkgs.kbfs ];
+ services.keybase.enable = true;
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/kdeconnect.nix b/infra/libkookie/home-manager/modules/services/kdeconnect.nix
new file mode 100644
index 000000000000..82de1f0eb7cb
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/kdeconnect.nix
@@ -0,0 +1,72 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.kdeconnect;
+ package = pkgs.kdeconnect;
+
+in {
+ meta.maintainers = [ maintainers.adisbladis ];
+
+ options = {
+ services.kdeconnect = {
+ enable = mkEnableOption "KDE connect";
+
+ indicator = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable kdeconnect-indicator service.";
+ };
+
+ };
+ };
+
+ config = mkMerge [
+ (mkIf cfg.enable {
+ home.packages = [ package ];
+
+ systemd.user.services.kdeconnect = {
+ Unit = {
+ Description =
+ "Adds communication between your desktop and your smartphone";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ Environment = "PATH=${config.home.profileDirectory}/bin";
+ ExecStart = "${package}/libexec/kdeconnectd";
+ Restart = "on-abort";
+ };
+ };
+ })
+
+ (mkIf cfg.indicator {
+ systemd.user.services.kdeconnect-indicator = {
+ Unit = {
+ Description = "kdeconnect-indicator";
+ After = [
+ "graphical-session-pre.target"
+ "polybar.service"
+ "taffybar.service"
+ "stalonetray.service"
+ ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ Environment = "PATH=${config.home.profileDirectory}/bin";
+ ExecStart = "${package}/bin/kdeconnect-indicator";
+ Restart = "on-abort";
+ };
+ };
+ })
+
+ ];
+}
diff --git a/infra/libkookie/home-manager/modules/services/keepassx.nix b/infra/libkookie/home-manager/modules/services/keepassx.nix
new file mode 100644
index 000000000000..dc37066e20ce
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/keepassx.nix
@@ -0,0 +1,27 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ services.keepassx = {
+ enable = mkEnableOption "the KeePassX password manager";
+ };
+ };
+
+ config = mkIf config.services.keepassx.enable {
+ systemd.user.services.keepassx = {
+ Unit = {
+ Description = "KeePassX password manager";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = { ExecStart = "${pkgs.keepassx}/bin/keepassx -min -lock"; };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/keybase.nix b/infra/libkookie/home-manager/modules/services/keybase.nix
new file mode 100644
index 000000000000..2d0a06b06a71
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/keybase.nix
@@ -0,0 +1,37 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.keybase;
+
+in
+
+{
+ options = {
+ services.keybase = {
+ enable = mkEnableOption "Keybase";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.keybase ];
+
+ systemd.user.services.keybase = {
+ Unit = {
+ Description = "Keybase service";
+ };
+
+ Service = {
+ ExecStart = "${pkgs.keybase}/bin/keybase service --auto-forked";
+ Restart = "on-failure";
+ PrivateTmp = true;
+ };
+
+ Install = {
+ WantedBy = [ "default.target" ];
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/keynav.nix b/infra/libkookie/home-manager/modules/services/keynav.nix
new file mode 100644
index 000000000000..c7f1df373b89
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/keynav.nix
@@ -0,0 +1,29 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.keynav;
+
+in {
+ options.services.keynav = { enable = mkEnableOption "keynav"; };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.keynav = {
+ Unit = {
+ Description = "keynav";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ ExecStart = "${pkgs.keynav}/bin/keynav";
+ RestartSec = 3;
+ Restart = "always";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/lieer-accounts.nix b/infra/libkookie/home-manager/modules/services/lieer-accounts.nix
new file mode 100644
index 000000000000..187f7dff9805
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/lieer-accounts.nix
@@ -0,0 +1,25 @@
+{ lib, ... }:
+
+with lib;
+
+{
+ options.lieer.sync = {
+ enable = mkEnableOption "lieer synchronization service";
+
+ frequency = mkOption {
+ type = types.str;
+ default = "*:0/5";
+ description = ''
+ How often to synchronize the account.
+ </para><para>
+ This value is passed to the systemd timer configuration as the
+ onCalendar option. See
+ <citerefentry>
+ <refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum>
+ </citerefentry>
+ for more information about the format.
+ '';
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/lieer.nix b/infra/libkookie/home-manager/modules/services/lieer.nix
new file mode 100644
index 000000000000..571e2af75c84
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/lieer.nix
@@ -0,0 +1,66 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.lieer;
+
+ syncAccounts = filter (a: a.lieer.enable && a.lieer.sync.enable)
+ (attrValues config.accounts.email.accounts);
+
+ escapeUnitName = name:
+ let
+ good = upperChars ++ lowerChars ++ stringToCharacters "0123456789-_";
+ subst = c: if any (x: x == c) good then c else "-";
+ in stringAsChars subst name;
+
+ serviceUnit = account: {
+ name = escapeUnitName "lieer-${account.name}";
+ value = {
+ Unit = {
+ Description = "lieer Gmail synchronization for ${account.name}";
+ ConditionPathExists = "${account.maildir.absPath}/.gmailieer.json";
+ };
+
+ Service = {
+ Type = "oneshot";
+ ExecStart = "${pkgs.gmailieer}/bin/gmi sync";
+ WorkingDirectory = account.maildir.absPath;
+ };
+ };
+ };
+
+ timerUnit = account: {
+ name = escapeUnitName "lieer-${account.name}";
+ value = {
+ Unit = {
+ Description = "lieer Gmail synchronization for ${account.name}";
+ };
+
+ Timer = {
+ OnCalendar = account.lieer.sync.frequency;
+ RandomizedDelaySec = 30;
+ };
+
+ Install = { WantedBy = [ "timers.target" ]; };
+ };
+ };
+
+in {
+ meta.maintainers = [ maintainers.tadfisher ];
+
+ options = {
+ services.lieer.enable =
+ mkEnableOption "lieer Gmail synchronization service";
+
+ accounts.email.accounts = mkOption {
+ type = with types; attrsOf (submodule (import ./lieer-accounts.nix));
+ };
+ };
+
+ config = mkIf cfg.enable {
+ programs.lieer.enable = true;
+ systemd.user.services = listToAttrs (map serviceUnit syncAccounts);
+ systemd.user.timers = listToAttrs (map timerUnit syncAccounts);
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/lorri.nix b/infra/libkookie/home-manager/modules/services/lorri.nix
new file mode 100644
index 000000000000..6183699088b3
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/lorri.nix
@@ -0,0 +1,60 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.lorri;
+
+in {
+ meta.maintainers = [ maintainers.gerschtli ];
+
+ options.services.lorri = {
+ enable = mkEnableOption "lorri build daemon";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.lorri;
+ defaultText = literalExample "pkgs.lorri";
+ description = "Which lorri package to install.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+
+ systemd.user = {
+ services.lorri = {
+ Unit = {
+ Description = "lorri build daemon";
+ Requires = "lorri.socket";
+ After = "lorri.socket";
+ RefuseManualStart = true;
+ };
+
+ Service = {
+ ExecStart = "${cfg.package}/bin/lorri daemon";
+ PrivateTmp = true;
+ ProtectSystem = "strict";
+ ProtectHome = "read-only";
+ Restart = "on-failure";
+ Environment = let
+ path = with pkgs;
+ makeSearchPath "bin" [ nix gitMinimal gnutar gzip ];
+ in [ "PATH=${path}" ];
+ };
+ };
+
+ sockets.lorri = {
+ Unit = { Description = "Socket for lorri build daemon"; };
+
+ Socket = {
+ ListenStream = "%t/lorri/daemon.socket";
+ RuntimeDirectory = "lorri";
+ };
+
+ Install = { WantedBy = [ "sockets.target" ]; };
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/mako.nix b/infra/libkookie/home-manager/modules/services/mako.nix
new file mode 100644
index 000000000000..77ea30116784
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/mako.nix
@@ -0,0 +1,316 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.mako;
+
+in {
+ meta.maintainers = [ maintainers.onny ];
+
+ options = {
+ programs.mako = {
+ enable = mkEnableOption ''
+ Mako, lightweight notification daemon for Wayland
+ '';
+
+ maxVisible = mkOption {
+ default = 5;
+ type = types.nullOr types.int;
+ description = ''
+ Set maximum number of visible notifications. Set -1 to show all.
+ '';
+ };
+
+ sort = mkOption {
+ default = "-time";
+ type =
+ types.nullOr (types.enum [ "+time" "-time" "+priority" "-priority" ]);
+ description = ''
+ Sorts incoming notifications by time and/or priority in ascending(+)
+ or descending(-) order.
+ '';
+ };
+
+ output = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ Show notifications on the specified output. If empty, notifications
+ will appear on the focused output. Requires the compositor to support
+ the Wayland protocol xdg-output-unstable-v1 version 2.
+ '';
+ };
+
+ layer = mkOption {
+ default = "top";
+ type =
+ types.nullOr (types.enum [ "background" "bottom" "top" "overlay" ]);
+ description = ''
+ Arrange mako at the specified layer, relative to normal windows.
+ Supported values are background, bottom, top, and overlay. Using
+ overlay will cause notifications to be displayed above fullscreen
+ windows, though this may also occur at top depending on your
+ compositor.
+ '';
+ };
+
+ anchor = mkOption {
+ default = "top-right";
+ type = types.nullOr (types.enum [
+ "top-right"
+ "top-center"
+ "top-left"
+ "bottom-right"
+ "bottom-center"
+ "bottom-left"
+ "center"
+ ]);
+ description = ''
+ Show notifications at the specified position on the output.
+ Supported values are top-right, top-center, top-left, bottom-right,
+ bottom-center, bottom-left, and center.
+ '';
+ };
+
+ font = mkOption {
+ default = "monospace 10";
+ type = types.nullOr types.str;
+ description = ''
+ Font to use, in Pango format.
+ '';
+ };
+
+ backgroundColor = mkOption {
+ default = "#285577FF";
+ type = types.nullOr types.str;
+ description = ''
+ Set popup background color to a specific color, represented in hex
+ color code.
+ '';
+ };
+
+ textColor = mkOption {
+ default = "#FFFFFFFF";
+ type = types.nullOr types.str;
+ description = ''
+ Set popup text color to a specific color, represented in hex color
+ code.
+ '';
+ };
+
+ width = mkOption {
+ default = 300;
+ type = types.nullOr types.int;
+ description = ''
+ Set width of notification popups in specified number of pixels.
+ '';
+ };
+
+ height = mkOption {
+ default = 100;
+ type = types.nullOr types.int;
+ description = ''
+ Set maximum height of notification popups. Notifications whose text
+ takes up less space are shrunk to fit.
+ '';
+ };
+
+ margin = mkOption {
+ default = "10";
+ type = types.nullOr types.str;
+ description = ''
+ Set margin of each edge specified in pixels. Specify single value to
+ apply margin on all sides. Two comma-seperated values will set
+ vertical and horizontal edges seperately. Four comma-seperated will
+ give each edge a seperate value.
+ For example: 10,20,5 will set top margin to 10, left and right to 20
+ and bottom to five.
+ '';
+ };
+
+ padding = mkOption {
+ default = "5";
+ type = types.nullOr types.str;
+ description = ''
+ Set padding of each edge specified in pixels. Specify single value to
+ apply margin on all sides. Two comma-seperated values will set
+ vertical and horizontal edges seperately. Four comma-seperated will
+ give each edge a seperate value.
+ For example: 10,20,5 will set top margin to 10, left and right to 20
+ and bottom to five.
+ '';
+ };
+
+ borderSize = mkOption {
+ default = 1;
+ type = types.nullOr types.int;
+ description = ''
+ Set popup border size to the specified number of pixels.
+ '';
+ };
+
+ borderColor = mkOption {
+ default = "#4C7899FF";
+ type = types.nullOr types.str;
+ description = ''
+ Set popup border color to a specific color, represented in hex color
+ code.
+ '';
+ };
+
+ borderRadius = mkOption {
+ default = 0;
+ type = types.nullOr types.int;
+ description = ''
+ Set popup corner radius to the specified number of pixels.
+ '';
+ };
+
+ progressColor = mkOption {
+ default = "over #5588AAFF";
+ type = types.nullOr types.str;
+ description = ''
+ Set popup progress indicator color to a specific color,
+ represented in hex color code. To draw the progress
+ indicator on top of the background color, use the
+ <literal>over</literal> attribute. To replace the background
+ color, use the <literal>source</literal> attribute (this can
+ be useful when the notification is semi-transparent).
+ '';
+ };
+
+ icons = mkOption {
+ default = true;
+ type = types.nullOr types.bool;
+ description = ''
+ Whether or not to show icons in notifications.
+ '';
+ };
+
+ maxIconSize = mkOption {
+ default = 64;
+ type = types.nullOr types.int;
+ description = ''
+ Set maximum icon size to the specified number of pixels.
+ '';
+ };
+
+ iconPath = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ Paths to search for icons when a notification specifies a name
+ instead of a full path. Colon-delimited. This approximates the search
+ algorithm used by the XDG Icon Theme Specification, but does not
+ support any of the theme metadata. Therefore, if you want to search
+ parent themes, you'll need to add them to the path manually.
+ </para><para>
+ The <filename>/usr/share/icons/hicolor</filename> and
+ <filename>/usr/share/pixmaps</filename> directories are
+ always searched.
+ '';
+ };
+
+ markup = mkOption {
+ default = true;
+ type = types.nullOr types.bool;
+ description = ''
+ If 1, enable Pango markup. If 0, disable Pango markup. If enabled,
+ Pango markup will be interpreted in your format specifier and in the
+ body of notifications.
+ '';
+ };
+
+ actions = mkOption {
+ default = true;
+ type = types.nullOr types.bool;
+ description = ''
+ Applications may request an action to be associated with activating a
+ notification. Disabling this will cause mako to ignore these requests.
+ '';
+ };
+
+ format = mkOption {
+ default = "<b>%s</b>\\n%b";
+ type = types.nullOr types.str;
+ description = ''
+ Set notification format string to format. See FORMAT SPECIFIERS for
+ more information. To change this for grouped notifications, set it
+ within a grouped criteria.
+ '';
+ };
+
+ defaultTimeout = mkOption {
+ default = 0;
+ type = types.nullOr types.int;
+ description = ''
+ Set the default timeout to timeout in milliseconds. To disable the
+ timeout, set it to zero.
+ '';
+ };
+
+ ignoreTimeout = mkOption {
+ default = false;
+ type = types.nullOr types.bool;
+ description = ''
+ If set, mako will ignore the expire timeout sent by notifications
+ and use the one provided by default-timeout instead.
+ '';
+ };
+
+ groupBy = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ description = ''
+ A comma-separated list of criteria fields that will be compared to
+ other visible notifications to determine if this one should form a
+ group with them. All listed criteria must be exactly equal for two
+ notifications to group.
+ '';
+ };
+
+ };
+ };
+
+ config = let
+ boolToString = v: if v then "true" else "false";
+ optionalBoolean = name: val:
+ lib.optionalString (val != null) "${name}=${boolToString val}";
+ optionalInteger = name: val:
+ lib.optionalString (val != null) "${name}=${toString val}";
+ optionalString = name: val:
+ lib.optionalString (val != null) "${name}=${val}";
+ in mkIf cfg.enable {
+ home.packages = [ pkgs.mako ];
+ xdg.configFile."mako/config".text = ''
+ ${optionalInteger "max-visible" cfg.maxVisible}
+ ${optionalString "sort" cfg.sort}
+ ${optionalString "output" cfg.output}
+ ${optionalString "layer" cfg.layer}
+ ${optionalString "anchor" cfg.anchor}
+
+ ${optionalString "font" cfg.font}
+ ${optionalString "background-color" cfg.backgroundColor}
+ ${optionalString "text-color" cfg.textColor}
+ ${optionalInteger "width" cfg.width}
+ ${optionalInteger "height" cfg.height}
+ ${optionalString "margin" cfg.margin}
+ ${optionalString "padding" cfg.padding}
+ ${optionalInteger "border-size" cfg.borderSize}
+ ${optionalString "border-color" cfg.borderColor}
+ ${optionalInteger "border-radius" cfg.borderRadius}
+ ${optionalString "progress-color" cfg.progressColor}
+ ${optionalBoolean "icons" cfg.icons}
+ ${optionalInteger "max-icon-size" cfg.maxIconSize}
+ ${optionalString "icon-path" cfg.iconPath}
+ ${optionalBoolean "markup" cfg.markup}
+ ${optionalBoolean "actions" cfg.actions}
+ ${optionalString "format" cfg.format}
+ ${optionalInteger "default-timeout" cfg.defaultTimeout}
+ ${optionalBoolean "ignore-timeout" cfg.ignoreTimeout}
+ ${optionalString "group-by" cfg.groupBy}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/mbsync.nix b/infra/libkookie/home-manager/modules/services/mbsync.nix
new file mode 100644
index 000000000000..ac6ac1ef78aa
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/mbsync.nix
@@ -0,0 +1,104 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.mbsync;
+
+ mbsyncOptions = [ "--all" ] ++ optional (cfg.verbose) "--verbose"
+ ++ optional (cfg.configFile != null) "--config ${cfg.configFile}";
+
+in {
+ meta.maintainers = [ maintainers.pjones ];
+
+ options.services.mbsync = {
+ enable = mkEnableOption "mbsync";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.isync;
+ defaultText = literalExample "pkgs.isync";
+ example = literalExample "pkgs.isync";
+ description = "The package to use for the mbsync binary.";
+ };
+
+ frequency = mkOption {
+ type = types.str;
+ default = "*:0/5";
+ description = ''
+ How often to run mbsync. This value is passed to the systemd
+ timer configuration as the onCalendar option. See
+ <citerefentry>
+ <refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum>
+ </citerefentry>
+ for more information about the format.
+ '';
+ };
+
+ verbose = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether mbsync should produce verbose output.
+ '';
+ };
+
+ configFile = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ description = ''
+ Optional configuration file to link to use instead of
+ the default file (<filename>~/.mbsyncrc</filename>).
+ '';
+ };
+
+ preExec = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "mkdir -p %h/mail";
+ description = ''
+ An optional command to run before mbsync executes. This is
+ useful for creating the directories mbsync is going to use.
+ '';
+ };
+
+ postExec = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "\${pkgs.mu}/bin/mu index";
+ description = ''
+ An optional command to run after mbsync executes successfully.
+ This is useful for running mailbox indexing tools.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.mbsync = {
+ Unit = { Description = "mbsync mailbox synchronization"; };
+
+ Service = {
+ Type = "oneshot";
+ ExecStart =
+ "${cfg.package}/bin/mbsync ${concatStringsSep " " mbsyncOptions}";
+ } // (optionalAttrs (cfg.postExec != null) {
+ ExecStartPost = cfg.postExec;
+ }) // (optionalAttrs (cfg.preExec != null) {
+ ExecStartPre = cfg.preExec;
+ });
+ };
+
+ systemd.user.timers.mbsync = {
+ Unit = { Description = "mbsync mailbox synchronization"; };
+
+ Timer = {
+ OnCalendar = cfg.frequency;
+ Unit = "mbsync.service";
+ };
+
+ Install = { WantedBy = [ "timers.target" ]; };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/mpd.nix b/infra/libkookie/home-manager/modules/services/mpd.nix
new file mode 100644
index 000000000000..a6ed0a483120
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/mpd.nix
@@ -0,0 +1,183 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ name = "mpd";
+
+ cfg = config.services.mpd;
+
+ mpdConf = pkgs.writeText "mpd.conf" ''
+ music_directory "${cfg.musicDirectory}"
+ playlist_directory "${cfg.playlistDirectory}"
+ ${lib.optionalString (cfg.dbFile != null) ''
+ db_file "${cfg.dbFile}"
+ ''}
+ state_file "${cfg.dataDir}/state"
+ sticker_file "${cfg.dataDir}/sticker.sql"
+
+ ${optionalString (cfg.network.listenAddress != "any")
+ ''bind_to_address "${cfg.network.listenAddress}"''}
+ ${optionalString (cfg.network.port != 6600)
+ ''port "${toString cfg.network.port}"''}
+
+ ${cfg.extraConfig}
+ '';
+
+in {
+
+ ###### interface
+
+ options = {
+
+ services.mpd = {
+
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable MPD, the music player daemon.
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.mpd;
+ defaultText = "pkgs.mpd";
+ description = ''
+ The MPD package to run.
+ '';
+ };
+
+ musicDirectory = mkOption {
+ type = with types; either path str;
+ default = "${config.home.homeDirectory}/music";
+ defaultText = "$HOME/music";
+ apply = toString; # Prevent copies to Nix store.
+ description = ''
+ The directory where mpd reads music from.
+ '';
+ };
+
+ playlistDirectory = mkOption {
+ type = types.path;
+ default = "${cfg.dataDir}/playlists";
+ defaultText = ''''${dataDir}/playlists'';
+ apply = toString; # Prevent copies to Nix store.
+ description = ''
+ The directory where mpd stores playlists.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra directives added to to the end of MPD's configuration
+ file, <filename>mpd.conf</filename>. Basic configuration
+ like file location and uid/gid is added automatically to the
+ beginning of the file. For available options see
+ <citerefentry>
+ <refentrytitle>mpd.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry>.
+ '';
+ };
+
+ dataDir = mkOption {
+ type = types.path;
+ default = "${config.xdg.dataHome}/${name}";
+ defaultText = "$XDG_DATA_HOME/mpd";
+ apply = toString; # Prevent copies to Nix store.
+ description = ''
+ The directory where MPD stores its state, tag cache,
+ playlists etc.
+ '';
+ };
+
+ network = {
+ startWhenNeeded = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable systemd socket activation.
+ '';
+ };
+
+ listenAddress = mkOption {
+ type = types.str;
+ default = "127.0.0.1";
+ example = "any";
+ description = ''
+ The address for the daemon to listen on.
+ Use <literal>any</literal> to listen on all addresses.
+ '';
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = 6600;
+ description = ''
+ The TCP port on which the the daemon will listen.
+ '';
+ };
+
+ };
+
+ dbFile = mkOption {
+ type = types.nullOr types.str;
+ default = "${cfg.dataDir}/tag_cache";
+ defaultText = ''''${dataDir}/tag_cache'';
+ description = ''
+ The path to MPD's database. If set to
+ <literal>null</literal> the parameter is omitted from the
+ configuration.
+ '';
+ };
+ };
+
+ };
+
+
+ ###### implementation
+
+ config = mkIf cfg.enable {
+
+ systemd.user.services.mpd = {
+ Unit = {
+ After = [ "network.target" "sound.target" ];
+ Description = "Music Player Daemon";
+ };
+
+ Install = mkIf (!cfg.network.startWhenNeeded) {
+ WantedBy = [ "default.target" ];
+ };
+
+ Service = {
+ Environment = "PATH=${config.home.profileDirectory}/bin";
+ ExecStart = "${cfg.package}/bin/mpd --no-daemon ${mpdConf}";
+ Type = "notify";
+ ExecStartPre = ''${pkgs.bash}/bin/bash -c "${pkgs.coreutils}/bin/mkdir -p '${cfg.dataDir}' '${cfg.playlistDirectory}'"'';
+ };
+ };
+ systemd.user.sockets.mpd = mkIf cfg.network.startWhenNeeded {
+ Socket = {
+ ListenStream = let
+ listen =
+ if cfg.network.listenAddress == "any"
+ then toString cfg.network.port
+ else "${cfg.network.listenAddress}:${toString cfg.network.port}";
+ in [ listen "%t/mpd/socket" ];
+
+ Backlog = 5;
+ KeepAlive = true;
+ };
+
+ Install = {
+ WantedBy = [ "sockets.target" ];
+ };
+ };
+ };
+
+}
diff --git a/infra/libkookie/home-manager/modules/services/mpdris2.nix b/infra/libkookie/home-manager/modules/services/mpdris2.nix
new file mode 100644
index 000000000000..cb8cefba6bd7
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/mpdris2.nix
@@ -0,0 +1,101 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.mpdris2;
+
+ toIni = generators.toINI {
+ mkKeyValue = key: value:
+ let
+ value' = if isBool value then
+ (if value then "True" else "False")
+ else
+ toString value;
+ in "${key} = ${value'}";
+ };
+
+ mpdris2Conf = {
+ Connection = {
+ host = cfg.mpd.host;
+ port = cfg.mpd.port;
+ music_dir = cfg.mpd.musicDirectory;
+ };
+
+ Bling = {
+ notify = cfg.notifications;
+ mmkeys = cfg.multimediaKeys;
+ };
+ };
+
+in {
+ meta.maintainers = [ maintainers.pjones ];
+
+ options.services.mpdris2 = {
+ enable = mkEnableOption "mpDris2 the MPD to MPRIS2 bridge";
+ notifications = mkEnableOption "song change notifications";
+ multimediaKeys = mkEnableOption "multimedia key support";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.mpdris2;
+ defaultText = literalExample "pkgs.mpdris2";
+ description = "The mpDris2 package to use.";
+ };
+
+ mpd = {
+ host = mkOption {
+ type = types.str;
+ default = config.services.mpd.network.listenAddress;
+ defaultText = "config.services.mpd.network.listenAddress";
+ example = "192.168.1.1";
+ description = "The address where MPD is listening for connections.";
+ };
+
+ port = mkOption {
+ type = types.port;
+ default = config.services.mpd.network.port;
+ defaultText = "config.services.mpd.network.port";
+ description = ''
+ The port number where MPD is listening for connections.
+ '';
+ };
+
+ musicDirectory = mkOption {
+ type = types.nullOr types.path;
+ default = config.services.mpd.musicDirectory;
+ defaultText = "config.services.mpd.musicDirectory";
+ description = ''
+ If set, mpDris2 will use this directory to access music artwork.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [{
+ assertion = config.services.mpd.enable;
+ message = "The mpdris2 module requires 'services.mpd.enable = true'.";
+ }];
+
+ xdg.configFile."mpDris2/mpDris2.conf".text = toIni mpdris2Conf;
+
+ systemd.user.services.mpdris2 = {
+ Install = { WantedBy = [ "default.target" ]; };
+
+ Unit = {
+ Description = "MPRIS 2 support for MPD";
+ After = [ "mpd.service" ];
+ };
+
+ Service = {
+ Type = "simple";
+ Restart = "on-failure";
+ RestartSec = "5s";
+ ExecStart = "${cfg.package}/bin/mpDris2";
+ BusName = "org.mpris.MediaPlayer2.mpd";
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/muchsync.nix b/infra/libkookie/home-manager/modules/services/muchsync.nix
new file mode 100644
index 000000000000..b7004418d35e
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/muchsync.nix
@@ -0,0 +1,203 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+# Documentation was partially copied from the muchsync manual.
+# See http://www.muchsync.org/muchsync.html
+
+let
+ cfg = config.services.muchsync;
+ syncOptions = {
+ options = {
+ frequency = mkOption {
+ type = types.str;
+ default = "*:0/5";
+ description = ''
+ How often to run <command>muchsync</command>. This
+ value is passed to the systemd timer configuration as the
+ <literal>OnCalendar</literal> option. See
+ <citerefentry>
+ <refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum>
+ </citerefentry>
+ for more information about the format.
+ '';
+ };
+
+ sshCommand = mkOption {
+ type = types.str;
+ default = "${pkgs.openssh}/bin/ssh -CTaxq";
+ defaultText = "ssh -CTaxq";
+ description = ''
+ Specifies a command line to pass to <command>/bin/sh</command>
+ to execute a command on another machine.
+ </para><para>
+ Note that because this string is passed to the shell,
+ special characters including spaces may need to be escaped.
+ '';
+ };
+
+ upload = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to propagate local changes to the remote.
+ '';
+ };
+
+ local = {
+ checkForModifiedFiles = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Check for locally modified files.
+ Without this option, muchsync assumes that files in a maildir are
+ never edited.
+ </para><para>
+ <option>checkForModifiedFiles</option> disables certain
+ optimizations so as to make muchsync at least check the timestamp on
+ every file, which will detect modified files at the cost of a longer
+ startup time.
+ </para><para>
+ This option is useful if your software regularly modifies the
+ contents of mail files (e.g., because you are running offlineimap
+ with "synclabels = yes").
+ '';
+ };
+
+ importNew = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to begin the synchronisation by running
+ <command>notmuch new</command> locally.
+ '';
+ };
+ };
+
+ remote = {
+ host = mkOption {
+ type = types.str;
+ description = ''
+ Remote SSH host to synchronize with.
+ '';
+ };
+
+ muchsyncPath = mkOption {
+ type = types.str;
+ default = "";
+ defaultText = "$PATH/muchsync";
+ description = ''
+ Specifies the path to muchsync on the server.
+ Ordinarily, muchsync should be in the default PATH on the server
+ so this option is not required.
+ However, this option is useful if you have to install muchsync in
+ a non-standard place or wish to test development versions of the
+ code.
+ '';
+ };
+
+ checkForModifiedFiles = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Check for modified files on the remote side.
+ Without this option, muchsync assumes that files in a maildir are
+ never edited.
+ </para><para>
+ <option>checkForModifiedFiles</option> disables certain
+ optimizations so as to make muchsync at least check the timestamp on
+ every file, which will detect modified files at the cost of a longer
+ startup time.
+ </para><para>
+ This option is useful if your software regularly modifies the
+ contents of mail files (e.g., because you are running offlineimap
+ with "synclabels = yes").
+ '';
+ };
+
+ importNew = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to begin the synchronisation by running
+ <command>notmuch new</command> on the remote side.
+ '';
+ };
+ };
+ };
+ };
+
+in {
+ meta.maintainers = with maintainers; [ pacien ];
+
+ options.services.muchsync = {
+ remotes = mkOption {
+ type = with types; attrsOf (submodule syncOptions);
+ default = { };
+ example = literalExample ''
+ {
+ server = {
+ frequency = "*:0/10";
+ remote.host = "server.tld";
+ };
+ }
+ '';
+ description = ''
+ Muchsync remotes to synchronise with.
+ '';
+ };
+ };
+
+ config = let
+ mapRemotes = gen:
+ with attrsets;
+ mapAttrs'
+ (name: remoteCfg: nameValuePair "muchsync-${name}" (gen name remoteCfg))
+ cfg.remotes;
+ in mkIf (cfg.remotes != { }) {
+ assertions = [{
+ assertion = config.programs.notmuch.enable;
+ message = ''
+ The muchsync module requires 'programs.notmuch.enable = true'.
+ '';
+ }];
+
+ systemd.user.services = mapRemotes (name: remoteCfg: {
+ Unit = { Description = "muchsync sync service (${name})"; };
+ Service = {
+ CPUSchedulingPolicy = "idle";
+ IOSchedulingClass = "idle";
+ Environment = [
+ ''"PATH=${pkgs.notmuch}/bin"''
+ ''"NOTMUCH_CONFIG=${config.home.sessionVariables.NOTMUCH_CONFIG}"''
+ ''"NMBGIT=${config.home.sessionVariables.NMBGIT}"''
+ ];
+ ExecStart = concatStringsSep " " ([ "${pkgs.muchsync}/bin/muchsync" ]
+ ++ [ "-s ${escapeShellArg remoteCfg.sshCommand}" ]
+ ++ optional (!remoteCfg.upload) "--noup"
+
+ # local configuration
+ ++ optional remoteCfg.local.checkForModifiedFiles "-F"
+ ++ optional (!remoteCfg.local.importNew) "--nonew"
+
+ # remote configuration
+ ++ [ (escapeShellArg remoteCfg.remote.host) ]
+ ++ optional (remoteCfg.remote.muchsyncPath != "")
+ "-r ${escapeShellArg remoteCfg.remote.muchsyncPath}"
+ ++ optional remoteCfg.remote.checkForModifiedFiles "-F"
+ ++ optional (!remoteCfg.remote.importNew) "--nonew");
+ };
+ });
+
+ systemd.user.timers = mapRemotes (name: remoteCfg: {
+ Unit = { Description = "muchsync periodic sync (${name})"; };
+ Timer = {
+ Unit = "muchsync-${name}.service";
+ OnCalendar = remoteCfg.frequency;
+ Persistent = true;
+ };
+ Install = { WantedBy = [ "timers.target" ]; };
+ });
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/network-manager-applet.nix b/infra/libkookie/home-manager/modules/services/network-manager-applet.nix
new file mode 100644
index 000000000000..bf57ed650918
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/network-manager-applet.nix
@@ -0,0 +1,36 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.network-manager-applet;
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ services.network-manager-applet = {
+ enable = mkEnableOption "the Network Manager applet";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.network-manager-applet = {
+ Unit = {
+ Description = "Network Manager applet";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ ExecStart = toString
+ ([ "${pkgs.networkmanagerapplet}/bin/nm-applet" "--sm-disable" ]
+ ++ optional config.xsession.preferStatusNotifierItems
+ "--indicator");
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/nextcloud-client.nix b/infra/libkookie/home-manager/modules/services/nextcloud-client.nix
new file mode 100644
index 000000000000..555ca11ad645
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/nextcloud-client.nix
@@ -0,0 +1,26 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ options = {
+ services.nextcloud-client = { enable = mkEnableOption "Nextcloud Client"; };
+ };
+
+ config = mkIf config.services.nextcloud-client.enable {
+ systemd.user.services.nextcloud-client = {
+ Unit = {
+ Description = "Nextcloud Client";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ Environment = "PATH=${config.home.profileDirectory}/bin";
+ ExecStart = "${pkgs.nextcloud-client}/bin/nextcloud";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/owncloud-client.nix b/infra/libkookie/home-manager/modules/services/owncloud-client.nix
new file mode 100644
index 000000000000..d55d8ffa2a41
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/owncloud-client.nix
@@ -0,0 +1,26 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ options = {
+ services.owncloud-client = { enable = mkEnableOption "Owncloud Client"; };
+ };
+
+ config = mkIf config.services.owncloud-client.enable {
+ systemd.user.services.owncloud-client = {
+ Unit = {
+ Description = "Owncloud Client";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ Environment = "PATH=${config.home.profileDirectory}/bin";
+ ExecStart = "${pkgs.owncloud-client}/bin/owncloud";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/parcellite.nix b/infra/libkookie/home-manager/modules/services/parcellite.nix
new file mode 100644
index 000000000000..cf1a7fe890f8
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/parcellite.nix
@@ -0,0 +1,42 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.parcellite;
+
+in {
+ meta.maintainers = [ maintainers.gleber ];
+
+ options.services.parcellite = {
+ enable = mkEnableOption "Parcellite";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.parcellite;
+ defaultText = literalExample "pkgs.parcellite";
+ example = literalExample "pkgs.clipit";
+ description = "Parcellite derivation to use.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+
+ systemd.user.services.parcellite = {
+ Unit = {
+ Description = "Lightweight GTK+ clipboard manager";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ ExecStart = "${cfg.package}/bin/${cfg.package.pname}";
+ Restart = "on-abort";
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/password-store-sync.nix b/infra/libkookie/home-manager/modules/services/password-store-sync.nix
new file mode 100644
index 000000000000..819339149800
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/password-store-sync.nix
@@ -0,0 +1,71 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ serviceCfg = config.services.password-store-sync;
+ programCfg = config.programs.password-store;
+
+in {
+ meta.maintainers = with maintainers; [ pacien ];
+
+ options.services.password-store-sync = {
+ enable = mkEnableOption "Password store periodic sync";
+
+ frequency = mkOption {
+ type = types.str;
+ default = "*:0/5";
+ description = ''
+ How often to synchronise the password store git repository with its
+ default upstream.
+ </para><para>
+ This value is passed to the systemd timer configuration as the
+ <literal>onCalendar</literal> option.
+ See
+ <citerefentry>
+ <refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum>
+ </citerefentry>
+ for more information about the format.
+ '';
+ };
+ };
+
+ config = mkIf serviceCfg.enable {
+ assertions = [{
+ assertion = programCfg.enable;
+ message = "The 'services.password-store-sync' module requires"
+ + " 'programs.password-store.enable = true'.";
+ }];
+
+ systemd.user.services.password-store-sync = {
+ Unit = { Description = "Password store sync"; };
+
+ Service = {
+ CPUSchedulingPolicy = "idle";
+ IOSchedulingClass = "idle";
+ Environment = let
+ makeEnvironmentPairs =
+ mapAttrsToList (key: value: "${key}=${builtins.toJSON value}");
+ in makeEnvironmentPairs programCfg.settings;
+ ExecStart = toString (pkgs.writeShellScript "password-store-sync" ''
+ ${pkgs.pass}/bin/pass git pull --rebase && \
+ ${pkgs.pass}/bin/pass git push
+ '');
+ };
+ };
+
+ systemd.user.timers.password-store-sync = {
+ Unit = { Description = "Password store periodic sync"; };
+
+ Timer = {
+ Unit = "password-store-sync.service";
+ OnCalendar = serviceCfg.frequency;
+ Persistent = true;
+ };
+
+ Install = { WantedBy = [ "timers.target" ]; };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/pasystray.nix b/infra/libkookie/home-manager/modules/services/pasystray.nix
new file mode 100644
index 000000000000..7c6651d9499c
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/pasystray.nix
@@ -0,0 +1,30 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ meta.maintainers = [ maintainers.pltanton ];
+
+ options = {
+ services.pasystray = { enable = mkEnableOption "PulseAudio system tray"; };
+ };
+
+ config = mkIf config.services.pasystray.enable {
+ systemd.user.services.pasystray = {
+ Unit = {
+ Description = "PulseAudio system tray";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ Environment =
+ let toolPaths = makeBinPath [ pkgs.paprefs pkgs.pavucontrol ];
+ in [ "PATH=${toolPaths}" ];
+ ExecStart = "${pkgs.pasystray}/bin/pasystray";
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/pbgopy.nix b/infra/libkookie/home-manager/modules/services/pbgopy.nix
new file mode 100644
index 000000000000..a95ad959f008
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/pbgopy.nix
@@ -0,0 +1,40 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.pbgopy;
+ package = pkgs.pbgopy;
+in {
+ meta.maintainers = [ maintainers.ivar ];
+
+ options.services.pbgopy = {
+ enable = mkEnableOption "pbgopy";
+
+ cache.ttl = mkOption {
+ type = types.str;
+ default = "24h";
+ example = "10m";
+ description = ''
+ The TTL for the cache. Use <literal>"0s"</literal> to disable it.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ package ];
+
+ systemd.user.services.pbgopy = {
+ Unit = {
+ Description = "pbgopy server for sharing the clipboard between devices";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+ Service = {
+ ExecStart = "${package}/bin/pbgopy serve --ttl ${cfg.cache.ttl}";
+ Restart = "on-abort";
+ };
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/picom.nix b/infra/libkookie/home-manager/modules/services/picom.nix
new file mode 100644
index 000000000000..4c4da8de6972
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/picom.nix
@@ -0,0 +1,311 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+with builtins;
+
+let
+
+ cfg = config.services.picom;
+
+ configFile = pkgs.writeText "picom.conf" (optionalString cfg.fade ''
+ # fading
+ fading = true;
+ fade-delta = ${toString cfg.fadeDelta};
+ fade-in-step = ${elemAt cfg.fadeSteps 0};
+ fade-out-step = ${elemAt cfg.fadeSteps 1};
+ fade-exclude = ${toJSON cfg.fadeExclude};
+ '' + optionalString cfg.shadow ''
+
+ # shadows
+ shadow = true;
+ shadow-offset-x = ${toString (elemAt cfg.shadowOffsets 0)};
+ shadow-offset-y = ${toString (elemAt cfg.shadowOffsets 1)};
+ shadow-opacity = ${cfg.shadowOpacity};
+ shadow-exclude = ${toJSON cfg.shadowExclude};
+ '' + optionalString cfg.blur ''
+
+ # blur
+ blur-background = true;
+ blur-background-exclude = ${toJSON cfg.blurExclude};
+ '' + ''
+
+ # opacity
+ active-opacity = ${cfg.activeOpacity};
+ inactive-opacity = ${cfg.inactiveOpacity};
+ inactive-dim = ${cfg.inactiveDim};
+ opacity-rule = ${toJSON cfg.opacityRule};
+
+ wintypes:
+ {
+ dock = { shadow = ${toJSON (!cfg.noDockShadow)}; };
+ dnd = { shadow = ${toJSON (!cfg.noDNDShadow)}; };
+ popup_menu = { opacity = ${cfg.menuOpacity}; };
+ dropdown_menu = { opacity = ${cfg.menuOpacity}; };
+ };
+
+ # other options
+ backend = ${toJSON cfg.backend};
+ vsync = ${toJSON cfg.vSync};
+ refresh-rate = ${toString cfg.refreshRate};
+ '' + cfg.extraOptions);
+
+in {
+
+ options.services.picom = {
+ enable = mkEnableOption "Picom X11 compositor";
+
+ blur = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable background blur on transparent windows.
+ '';
+ };
+
+ blurExclude = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "class_g = 'slop'" "class_i = 'polybar'" ];
+ description = ''
+ List of windows to exclude background blur.
+ See the
+ <citerefentry>
+ <refentrytitle>picom</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ man page for more examples.
+ '';
+ };
+
+ experimentalBackends = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to use the new experimental backends.
+ '';
+ };
+
+ fade = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Fade windows in and out.
+ '';
+ };
+
+ fadeDelta = mkOption {
+ type = types.int;
+ default = 10;
+ example = 5;
+ description = ''
+ Time between fade animation step (in ms).
+ '';
+ };
+
+ fadeSteps = mkOption {
+ type = types.listOf types.str;
+ default = [ "0.028" "0.03" ];
+ example = [ "0.04" "0.04" ];
+ description = ''
+ Opacity change between fade steps (in and out).
+ '';
+ };
+
+ fadeExclude = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "window_type *= 'menu'" "name ~= 'Firefox$'" "focused = 1" ];
+ description = ''
+ List of conditions of windows that should not be faded.
+ See the
+ <citerefentry>
+ <refentrytitle>picom</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ man page for more examples.
+ '';
+ };
+
+ shadow = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Draw window shadows.
+ '';
+ };
+
+ shadowOffsets = mkOption {
+ type = types.listOf types.int;
+ default = [ (-15) (-15) ];
+ example = [ (-10) (-15) ];
+ description = ''
+ Horizontal and vertical offsets for shadows (in pixels).
+ '';
+ };
+
+ shadowOpacity = mkOption {
+ type = types.str;
+ default = "0.75";
+ example = "0.8";
+ description = ''
+ Window shadows opacity (number in range 0 - 1).
+ '';
+ };
+
+ shadowExclude = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "window_type *= 'menu'" "name ~= 'Firefox$'" "focused = 1" ];
+ description = ''
+ List of conditions of windows that should have no shadow.
+ See the
+ <citerefentry>
+ <refentrytitle>picom</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ man page for more examples.
+ '';
+ };
+
+ noDockShadow = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Avoid shadow on docks.
+ '';
+ };
+
+ noDNDShadow = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Avoid shadow on drag-and-drop windows.
+ '';
+ };
+
+ activeOpacity = mkOption {
+ type = types.str;
+ default = "1.0";
+ example = "0.8";
+ description = ''
+ Opacity of active windows.
+ '';
+ };
+
+ inactiveDim = mkOption {
+ type = types.str;
+ default = "0.0";
+ example = "0.2";
+ description = ''
+ Dim inactive windows.
+ '';
+ };
+
+ inactiveOpacity = mkOption {
+ type = types.str;
+ default = "1.0";
+ example = "0.8";
+ description = ''
+ Opacity of inactive windows.
+ '';
+ };
+
+ menuOpacity = mkOption {
+ type = types.str;
+ default = "1.0";
+ example = "0.8";
+ description = ''
+ Opacity of dropdown and popup menu.
+ '';
+ };
+
+ opacityRule = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "87:class_i ?= 'scratchpad'" "91:class_i ?= 'xterm'" ];
+ description = ''
+ List of opacity rules.
+ See the
+ <citerefentry>
+ <refentrytitle>picom</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ man page for more examples.
+ '';
+ };
+
+ backend = mkOption {
+ type = types.str;
+ default = "glx";
+ description = ''
+ Backend to use: <literal>glx</literal> or <literal>xrender</literal>.
+ '';
+ };
+
+ vSync = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Enable vertical synchronization.
+ '';
+ };
+
+ refreshRate = mkOption {
+ type = types.int;
+ default = 0;
+ example = 60;
+ description = ''
+ Screen refresh rate (0 = automatically detect).
+ '';
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.picom;
+ defaultText = literalExample "pkgs.picom";
+ example = literalExample "pkgs.picom";
+ description = ''
+ picom derivation to use.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = types.str;
+ default = "";
+ example = ''
+ unredir-if-possible = true;
+ dbe = true;
+ '';
+ description = ''
+ Additional Picom configuration.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+
+ systemd.user.services.picom = {
+ Unit = {
+ Description = "Picom X11 compositor";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = let
+ experimentalBackendsFlag =
+ if cfg.experimentalBackends then " --experimental-backends" else "";
+ in {
+ ExecStart = "${cfg.package}/bin/picom --config ${configFile}"
+ + experimentalBackendsFlag;
+ Restart = "always";
+ RestartSec = 3;
+ } // optionalAttrs (cfg.backend == "glx") {
+ # Temporarily fixes corrupt colours with Mesa 18.
+ Environment = [ "allow_rgb10_configs=false" ];
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/polybar.nix b/infra/libkookie/home-manager/modules/services/polybar.nix
new file mode 100644
index 000000000000..934a990638f9
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/polybar.nix
@@ -0,0 +1,135 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.polybar;
+
+ eitherStrBoolIntList = with types;
+ either str (either bool (either int (listOf str)));
+
+ toPolybarIni = generators.toINI {
+ mkKeyValue = key: value:
+ let
+ quoted = v:
+ if hasPrefix " " v || hasSuffix " " v then ''"${v}"'' else v;
+
+ value' = if isBool value then
+ (if value then "true" else "false")
+ else if (isString value && key != "include-file") then
+ quoted value
+ else
+ toString value;
+ in "${key}=${value'}";
+ };
+
+ configFile = pkgs.writeText "polybar.conf"
+ (toPolybarIni cfg.config + "\n" + cfg.extraConfig);
+
+in {
+ options = {
+ services.polybar = {
+ enable = mkEnableOption "Polybar status bar";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.polybar;
+ defaultText = literalExample "pkgs.polybar";
+ description = "Polybar package to install.";
+ example = literalExample ''
+ pkgs.polybar.override {
+ i3GapsSupport = true;
+ alsaSupport = true;
+ iwSupport = true;
+ githubSupport = true;
+ }
+ '';
+ };
+
+ config = mkOption {
+ type = types.coercedTo types.path
+ (p: { "section/base" = { include-file = "${p}"; }; })
+ (types.attrsOf (types.attrsOf eitherStrBoolIntList));
+ description = ''
+ Polybar configuration. Can be either path to a file, or set of attributes
+ that will be used to create the final configuration.
+ '';
+ default = { };
+ example = literalExample ''
+ {
+ "bar/top" = {
+ monitor = "\''${env:MONITOR:eDP1}";
+ width = "100%";
+ height = "3%";
+ radius = 0;
+ modules-center = "date";
+ };
+
+ "module/date" = {
+ type = "internal/date";
+ internal = 5;
+ date = "%d.%m.%y";
+ time = "%H:%M";
+ label = "%time% %date%";
+ };
+ }
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ description = "Additional configuration to add.";
+ default = "";
+ example = ''
+ [module/date]
+ type = internal/date
+ interval = 5
+ date = "%d.%m.%y"
+ time = %H:%M
+ format-prefix-foreground = \''${colors.foreground-alt}
+ label = %time% %date%
+ '';
+ };
+
+ script = mkOption {
+ type = types.lines;
+ description = ''
+ This script will be used to start the polybars.
+ Set all necessary environment variables here and start all bars.
+ It can be assumed that <command>polybar</command> executable is in the <envar>PATH</envar>.
+
+ Note, this script must start all bars in the background and then terminate.
+ '';
+ example = "polybar bar &";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+ xdg.configFile."polybar/config".source = configFile;
+
+ systemd.user.services.polybar = {
+ Unit = {
+ Description = "Polybar status bar";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ X-Restart-Triggers =
+ [ "${config.xdg.configFile."polybar/config".source}" ];
+ };
+
+ Service = {
+ Type = "forking";
+ Environment = "PATH=${cfg.package}/bin:/run/wrappers/bin";
+ ExecStart =
+ let scriptPkg = pkgs.writeShellScriptBin "polybar-start" cfg.script;
+ in "${scriptPkg}/bin/polybar-start";
+ Restart = "on-failure";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+
+}
diff --git a/infra/libkookie/home-manager/modules/services/pulseeffects.nix b/infra/libkookie/home-manager/modules/services/pulseeffects.nix
new file mode 100644
index 000000000000..445b1c0a1f87
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/pulseeffects.nix
@@ -0,0 +1,55 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.pulseeffects;
+
+ presetOpts = optionalString (cfg.preset != "") "--load-preset ${cfg.preset}";
+
+in {
+ meta.maintainers = [ maintainers.jonringer ];
+
+ options.services.pulseeffects = {
+ enable = mkEnableOption "Pulseeffects daemon";
+
+ preset = mkOption {
+ type = types.str;
+ default = "";
+ description = ''
+ Which preset to use when starting pulseeffects.
+ Will likely need to launch pulseeffects to initially create preset.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ # running pulseeffects will just attach itself to gapplication service
+ # at-spi2-core is to minimize journalctl noise of:
+ # "AT-SPI: Error retrieving accessibility bus address: org.freedesktop.DBus.Error.ServiceUnknown: The name org.a11y.Bus was not provided by any .service files"
+ home.packages = [ pkgs.pulseeffects pkgs.at-spi2-core ];
+
+ # Will need to add `services.dbus.packages = with pkgs; [ gnome3.dconf ];`
+ # to /etc/nixos/configuration.nix for daemon to work correctly
+
+ systemd.user.services.pulseeffects = {
+ Unit = {
+ Description = "Pulseeffects daemon";
+ Requires = [ "dbus.service" ];
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" "pulseaudio.service" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ ExecStart =
+ "${pkgs.pulseeffects}/bin/pulseeffects --gapplication-service ${presetOpts}";
+ ExecStop = "${pkgs.pulseeffects}/bin/pulseeffects --quit";
+ Restart = "on-failure";
+ RestartSec = 5;
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/random-background.nix b/infra/libkookie/home-manager/modules/services/random-background.nix
new file mode 100644
index 000000000000..9deee8deb5c1
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/random-background.nix
@@ -0,0 +1,97 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.random-background;
+
+ flags = lib.concatStringsSep " "
+ ([ "--bg-${cfg.display}" "--no-fehbg" "--randomize" ]
+ ++ lib.optional (!cfg.enableXinerama) "--no-xinerama");
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ services.random-background = {
+ enable = mkEnableOption "" // {
+ description = ''
+ Whether to enable random desktop background.
+ </para><para>
+ Note, if you are using NixOS and have set up a custom
+ desktop manager session for Home Manager, then the session
+ configuration must have the <option>bgSupport</option>
+ option set to <literal>true</literal> or the background
+ image set by this module may be overwritten.
+ '';
+ };
+
+ imageDirectory = mkOption {
+ type = types.str;
+ example = "%h/backgrounds";
+ description = ''
+ The directory of images from which a background should be
+ chosen. Should be formatted in a way understood by systemd,
+ e.g., '%h' is the home directory.
+ '';
+ };
+
+ display = mkOption {
+ type = types.enum [ "center" "fill" "max" "scale" "tile" ];
+ default = "fill";
+ description = "Display background images according to this option.";
+ };
+
+ interval = mkOption {
+ default = null;
+ type = types.nullOr types.str;
+ example = "1h";
+ description = ''
+ The duration between changing background image, set to null
+ to only set background when logging in. Should be formatted
+ as a duration understood by systemd.
+ '';
+ };
+
+ enableXinerama = mkOption {
+ default = true;
+ type = types.bool;
+ description = ''
+ Will place a separate image per screen when enabled,
+ otherwise a single image will be stretched across all
+ screens.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge ([
+ {
+ systemd.user.services.random-background = {
+ Unit = {
+ Description = "Set random desktop background using feh";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ Type = "oneshot";
+ ExecStart = "${pkgs.feh}/bin/feh ${flags} ${cfg.imageDirectory}";
+ IOSchedulingClass = "idle";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ }
+ (mkIf (cfg.interval != null) {
+ systemd.user.timers.random-background = {
+ Unit = { Description = "Set random desktop background using feh"; };
+
+ Timer = { OnUnitActiveSec = cfg.interval; };
+
+ Install = { WantedBy = [ "timers.target" ]; };
+ };
+ })
+ ]));
+}
diff --git a/infra/libkookie/home-manager/modules/services/redshift.nix b/infra/libkookie/home-manager/modules/services/redshift.nix
new file mode 100644
index 000000000000..86cbab205f68
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/redshift.nix
@@ -0,0 +1,164 @@
+# Adapted from Nixpkgs.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.redshift;
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options.services.redshift = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ example = true;
+ description = ''
+ Enable Redshift to change your screen's colour temperature depending on
+ the time of day.
+ '';
+ };
+
+ latitude = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Your current latitude, between <literal>-90.0</literal> and
+ <literal>90.0</literal>. Must be provided along with
+ longitude.
+ '';
+ };
+
+ longitude = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Your current longitude, between <literal>-180.0</literal> and
+ <literal>180.0</literal>. Must be provided along with
+ latitude.
+ '';
+ };
+
+ provider = mkOption {
+ type = types.enum [ "manual" "geoclue2" ];
+ default = "manual";
+ description = ''
+ The location provider to use for determining your location. If set to
+ <literal>manual</literal> you must also provide latitude/longitude.
+ If set to <literal>geoclue2</literal>, you must also enable the global
+ geoclue2 service.
+ '';
+ };
+
+ temperature = {
+ day = mkOption {
+ type = types.int;
+ default = 5500;
+ description = ''
+ Colour temperature to use during the day, between
+ <literal>1000</literal> and <literal>25000</literal> K.
+ '';
+ };
+ night = mkOption {
+ type = types.int;
+ default = 3700;
+ description = ''
+ Colour temperature to use at night, between
+ <literal>1000</literal> and <literal>25000</literal> K.
+ '';
+ };
+ };
+
+ brightness = {
+ day = mkOption {
+ type = types.str;
+ default = "1";
+ description = ''
+ Screen brightness to apply during the day,
+ between <literal>0.1</literal> and <literal>1.0</literal>.
+ '';
+ };
+ night = mkOption {
+ type = types.str;
+ default = "1";
+ description = ''
+ Screen brightness to apply during the night,
+ between <literal>0.1</literal> and <literal>1.0</literal>.
+ '';
+ };
+ };
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.redshift;
+ defaultText = literalExample "pkgs.redshift";
+ description = ''
+ redshift derivation to use.
+ '';
+ };
+
+ tray = mkOption {
+ type = types.bool;
+ default = false;
+ example = true;
+ description = ''
+ Start the redshift-gtk tray applet.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "-v" "-m randr" ];
+ description = ''
+ Additional command-line arguments to pass to
+ <command>redshift</command>.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [{
+ assertion = cfg.provider == "manual" -> cfg.latitude != null
+ && cfg.longitude != null;
+ message = "Must provide services.redshift.latitude and"
+ + " services.redshift.latitude when"
+ + " services.redshift.provider is set to \"manual\".";
+ }];
+
+ systemd.user.services.redshift = {
+ Unit = {
+ Description = "Redshift colour temperature adjuster";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ ExecStart = let
+ providerString = if cfg.provider == "manual" then
+ "${cfg.latitude}:${cfg.longitude}"
+ else
+ cfg.provider;
+
+ args = [
+ "-l ${providerString}"
+ "-t ${toString cfg.temperature.day}:${
+ toString cfg.temperature.night
+ }"
+ "-b ${toString cfg.brightness.day}:${toString cfg.brightness.night}"
+ ] ++ cfg.extraOptions;
+
+ command = if cfg.tray then "redshift-gtk" else "redshift";
+ in "${cfg.package}/bin/${command} ${concatStringsSep " " args}";
+ RestartSec = 3;
+ Restart = "always";
+ };
+ };
+ };
+
+}
diff --git a/infra/libkookie/home-manager/modules/services/rsibreak.nix b/infra/libkookie/home-manager/modules/services/rsibreak.nix
new file mode 100644
index 000000000000..77eaa71f958a
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/rsibreak.nix
@@ -0,0 +1,32 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.rsibreak;
+
+in {
+ options.services.rsibreak = {
+
+ enable = mkEnableOption "rsibreak";
+
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.rsibreak = {
+ Unit = {
+ Description = "RSI break timer";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ Environment = "PATH=${config.home.profileDirectory}/bin";
+ ExecStart = "${pkgs.rsibreak}/bin/rsibreak";
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/screen-locker.nix b/infra/libkookie/home-manager/modules/services/screen-locker.nix
new file mode 100644
index 000000000000..554d64f9abe8
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/screen-locker.nix
@@ -0,0 +1,92 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.screen-locker;
+
+in {
+
+ options.services.screen-locker = {
+ enable = mkEnableOption "screen locker for X session";
+
+ lockCmd = mkOption {
+ type = types.str;
+ description = "Locker command to run.";
+ example = "\${pkgs.i3lock}/bin/i3lock -n -c 000000";
+ };
+
+ enableDetectSleep = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to reset timers when awaking from sleep.
+ '';
+ };
+
+ inactiveInterval = mkOption {
+ type = types.int;
+ default = 10;
+ description = ''
+ Inactive time interval in minutes after which session will be locked.
+ The minimum is 1 minute, and the maximum is 1 hour.
+ See <link xlink:href="https://linux.die.net/man/1/xautolock"/>.
+ '';
+ };
+
+ xautolockExtraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = ''
+ Extra command-line arguments to pass to <command>xautolock</command>.
+ '';
+ };
+
+ xssLockExtraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = ''
+ Extra command-line arguments to pass to <command>xss-lock</command>.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.xautolock-session = {
+ Unit = {
+ Description = "xautolock, session locker service";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ ExecStart = concatStringsSep " " ([
+ "${pkgs.xautolock}/bin/xautolock"
+ "-time ${toString cfg.inactiveInterval}"
+ "-locker '${pkgs.systemd}/bin/loginctl lock-session $XDG_SESSION_ID'"
+ ] ++ optional cfg.enableDetectSleep "-detectsleep"
+ ++ cfg.xautolockExtraOptions);
+ };
+ };
+
+ systemd.user.services.xss-lock = {
+ Unit = {
+ Description = "xss-lock, session locker service";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ ExecStart = concatStringsSep " "
+ ([ "${pkgs.xss-lock}/bin/xss-lock" "-s \${XDG_SESSION_ID}" ]
+ ++ cfg.xssLockExtraOptions ++ [ "-- ${cfg.lockCmd}" ]);
+ };
+ };
+ };
+
+}
diff --git a/infra/libkookie/home-manager/modules/services/spotifyd.nix b/infra/libkookie/home-manager/modules/services/spotifyd.nix
new file mode 100644
index 000000000000..dfe0ecd318e3
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/spotifyd.nix
@@ -0,0 +1,64 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.spotifyd;
+
+ configFile = pkgs.writeText "spotifyd.conf" ''
+ ${generators.toINI { } cfg.settings}
+ '';
+
+in {
+ options.services.spotifyd = {
+ enable = mkEnableOption "SpotifyD connect";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.spotifyd;
+ defaultText = literalExample "pkgs.spotifyd";
+ example =
+ literalExample "(pkgs.spotifyd.override { withKeyring = true; })";
+ description = ''
+ The <literal>spotifyd</literal> package to use.
+ Can be used to specify extensions.
+ '';
+ };
+
+ settings = mkOption {
+ type = types.attrsOf (types.attrsOf types.str);
+ default = { };
+ description = "Configuration for spotifyd";
+ example = literalExample ''
+ {
+ global = {
+ username = "Alex";
+ password = "foo";
+ device_name = "nix";
+ };
+ }
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+
+ systemd.user.services.spotifyd = {
+ Unit = {
+ Description = "spotify daemon";
+ Documentation = "https://github.com/Spotifyd/spotifyd";
+ };
+
+ Install.WantedBy = [ "default.target" ];
+
+ Service = {
+ ExecStart =
+ "${cfg.package}/bin/spotifyd --no-daemon --config-path ${configFile}";
+ Restart = "always";
+ RestartSec = 12;
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/stalonetray.nix b/infra/libkookie/home-manager/modules/services/stalonetray.nix
new file mode 100644
index 000000000000..cca60498963a
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/stalonetray.nix
@@ -0,0 +1,90 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.stalonetray;
+
+in {
+ options = {
+ services.stalonetray = {
+ enable = mkEnableOption "Stalonetray system tray";
+
+ package = mkOption {
+ default = pkgs.stalonetray;
+ defaultText = literalExample "pkgs.stalonetray";
+ type = types.package;
+ example = literalExample "pkgs.stalonetray";
+ description = "The package to use for the Stalonetray binary.";
+ };
+
+ config = mkOption {
+ type = with types; attrsOf (nullOr (either str (either bool int)));
+ description = ''
+ Stalonetray configuration as a set of attributes.
+ '';
+ default = { };
+ example = {
+ geometry = "3x1-600+0";
+ decorations = null;
+ icon_size = 30;
+ sticky = true;
+ background = "#cccccc";
+ };
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ description = "Additional configuration lines for stalonetrayrc.";
+ default = "";
+ example = ''
+ geometry 3x1-600+0
+ decorations none
+ icon_size 30
+ sticky true
+ background "#cccccc"
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+ home.packages = [ cfg.package ];
+
+ systemd.user.services.stalonetray = {
+ Unit = {
+ Description = "Stalonetray system tray";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ ExecStart = "${cfg.package}/bin/stalonetray";
+ Restart = "on-failure";
+ };
+ };
+ }
+
+ (mkIf (cfg.config != { }) {
+ home.file.".stalonetrayrc".text = let
+ valueToString = v:
+ if isBool v then
+ (if v then "true" else "false")
+ else if (v == null) then
+ "none"
+ else
+ ''"${toString v}"'';
+ in concatStrings (mapAttrsToList (k: v: ''
+ ${k} ${valueToString v}
+ '') cfg.config);
+ })
+
+ (mkIf (cfg.extraConfig != "") {
+ home.file.".stalonetrayrc".text = cfg.extraConfig;
+ })
+ ]);
+}
diff --git a/infra/libkookie/home-manager/modules/services/status-notifier-watcher.nix b/infra/libkookie/home-manager/modules/services/status-notifier-watcher.nix
new file mode 100644
index 000000000000..ed0537e22e1e
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/status-notifier-watcher.nix
@@ -0,0 +1,51 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.status-notifier-watcher;
+
+in {
+ meta.maintainers = [ maintainers.pltanton ];
+
+ options = {
+ services.status-notifier-watcher = {
+ enable = mkEnableOption "Status Notifier Watcher";
+
+ package = mkOption {
+ default = pkgs.haskellPackages.status-notifier-item;
+ defaultText =
+ literalExample "pkgs.haskellPackages.status-notifier-item";
+ type = types.package;
+ example = literalExample "pkgs.haskellPackages.status-notifier-item";
+ description =
+ "The package to use for the status notifier watcher binary.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.status-notifier-watcher = {
+ Unit = {
+ Description = "SNI watcher";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ Before = [ "taffybar.service" ];
+ };
+
+ Service = {
+ ExecStart = "${cfg.package}/bin/status-notifier-watcher";
+ # Delay the unit start a bit to allow the program to get fully
+ # set up before letting dependent services start. This is
+ # brittle and a better solution using, e.g., `BusName=` might
+ # be possible.
+ ExecStartPost = "${pkgs.coreutils}/bin/sleep 1";
+ };
+
+ Install = {
+ WantedBy = [ "graphical-session.target" "taffybar.service" ];
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/sxhkd.nix b/infra/libkookie/home-manager/modules/services/sxhkd.nix
new file mode 100644
index 000000000000..d9f0a968515d
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/sxhkd.nix
@@ -0,0 +1,86 @@
+{config, lib, pkgs, ...}:
+
+with lib;
+
+let
+
+ cfg = config.services.sxhkd;
+
+ keybindingsStr = concatStringsSep "\n" (
+ mapAttrsToList (hotkey: command:
+ optionalString (command != null) ''
+ ${hotkey}
+ ${command}
+ ''
+ )
+ cfg.keybindings
+ );
+
+in
+
+{
+ options.services.sxhkd = {
+ enable = mkEnableOption "simple X hotkey daemon";
+
+ keybindings = mkOption {
+ type = types.attrsOf (types.nullOr types.str);
+ default = {};
+ description = "An attribute set that assigns hotkeys to commands.";
+ example = literalExample ''
+ {
+ "super + shift + {r,c}" = "i3-msg {restart,reload}";
+ "super + {s,w}" = "i3-msg {stacking,tabbed}";
+ }
+ '';
+ };
+
+ extraConfig = mkOption {
+ default = "";
+ type = types.lines;
+ description = "Additional configuration to add.";
+ example = literalExample ''
+ super + {_,shift +} {1-9,0}
+ i3-msg {workspace,move container to workspace} {1-10}
+ '';
+ };
+
+ extraPath = mkOption {
+ default = "";
+ type = types.envVar;
+ description = ''
+ Additional <envar>PATH</envar> entries to search for commands.
+ '';
+ example = "/home/some-user/bin:/extra/path/bin";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.sxhkd ];
+
+ xdg.configFile."sxhkd/sxhkdrc".text = concatStringsSep "\n" [
+ keybindingsStr
+ cfg.extraConfig
+ ];
+
+ systemd.user.services.sxhkd = {
+ Unit = {
+ Description = "simple X hotkey daemon";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ Environment =
+ "PATH="
+ + "${config.home.profileDirectory}/bin"
+ + optionalString (cfg.extraPath != "") ":"
+ + cfg.extraPath;
+ ExecStart = "${pkgs.sxhkd}/bin/sxhkd";
+ };
+
+ Install = {
+ WantedBy = [ "graphical-session.target" ];
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/syncthing.nix b/infra/libkookie/home-manager/modules/services/syncthing.nix
new file mode 100644
index 000000000000..4622ac2e9416
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/syncthing.nix
@@ -0,0 +1,70 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ services.syncthing = {
+ enable = mkEnableOption "Syncthing continuous file synchronization";
+
+ tray = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to enable QSyncthingTray service.";
+ };
+ };
+ };
+
+ config = mkMerge [
+ (mkIf config.services.syncthing.enable {
+ home.packages = [ (getOutput "man" pkgs.syncthing) ];
+
+ systemd.user.services = {
+ syncthing = {
+ Unit = {
+ Description =
+ "Syncthing - Open Source Continuous File Synchronization";
+ Documentation = "man:syncthing(1)";
+ After = [ "network.target" ];
+ };
+
+ Service = {
+ ExecStart =
+ "${pkgs.syncthing}/bin/syncthing -no-browser -no-restart -logflags=0";
+ Restart = "on-failure";
+ SuccessExitStatus = [ 3 4 ];
+ RestartForceExitStatus = [ 3 4 ];
+ };
+
+ Install = { WantedBy = [ "default.target" ]; };
+ };
+ };
+ })
+
+ (mkIf config.services.syncthing.tray {
+ systemd.user.services = {
+ qsyncthingtray = {
+ Unit = {
+ Description = "QSyncthingTray";
+ After = [
+ "graphical-session-pre.target"
+ "polybar.service"
+ "taffybar.service"
+ "stalonetray.service"
+ ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ Environment = "PATH=${config.home.profileDirectory}/bin";
+ ExecStart = "${pkgs.qsyncthingtray}/bin/QSyncthingTray";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+ })
+ ];
+}
diff --git a/infra/libkookie/home-manager/modules/services/taffybar.nix b/infra/libkookie/home-manager/modules/services/taffybar.nix
new file mode 100644
index 000000000000..5392755423d4
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/taffybar.nix
@@ -0,0 +1,44 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.taffybar;
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ services.taffybar = {
+ enable = mkEnableOption "Taffybar";
+
+ package = mkOption {
+ default = pkgs.taffybar;
+ defaultText = literalExample "pkgs.taffybar";
+ type = types.package;
+ example = literalExample "pkgs.taffybar";
+ description = "The package to use for the Taffybar binary.";
+ };
+ };
+ };
+
+ config = mkIf config.services.taffybar.enable {
+ systemd.user.services.taffybar = {
+ Unit = {
+ Description = "Taffybar desktop bar";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ ExecStart = "${cfg.package}/bin/taffybar";
+ Restart = "on-failure";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+
+ xsession.importedVariables = [ "GDK_PIXBUF_MODULE_FILE" ];
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/tahoe-lafs.nix b/infra/libkookie/home-manager/modules/services/tahoe-lafs.nix
new file mode 100644
index 000000000000..742b779b270f
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/tahoe-lafs.nix
@@ -0,0 +1,19 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ services.tahoe-lafs = { enable = mkEnableOption "Tahoe-LAFS"; };
+ };
+
+ config = mkIf config.services.tahoe-lafs.enable {
+ systemd.user.services.tahoe-lafs = {
+ Unit = { Description = "Tahoe-LAFS"; };
+
+ Service = { ExecStart = "${pkgs.tahoelafs}/bin/tahoe run -C %h/.tahoe"; };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/taskwarrior-sync.nix b/infra/libkookie/home-manager/modules/services/taskwarrior-sync.nix
new file mode 100644
index 000000000000..d16c0681beef
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/taskwarrior-sync.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.taskwarrior-sync;
+
+in {
+ meta.maintainers = with maintainers; [ minijackson pacien ];
+
+ options.services.taskwarrior-sync = {
+ enable = mkEnableOption "Taskwarrior periodic sync";
+
+ frequency = mkOption {
+ type = types.str;
+ default = "*:0/5";
+ description = ''
+ How often to run <literal>taskwarrior sync</literal>. This
+ value is passed to the systemd timer configuration as the
+ <literal>OnCalendar</literal> option. See
+ <citerefentry>
+ <refentrytitle>systemd.time</refentrytitle>
+ <manvolnum>7</manvolnum>
+ </citerefentry>
+ for more information about the format.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.taskwarrior-sync = {
+ Unit = { Description = "Taskwarrior sync"; };
+ Service = {
+ CPUSchedulingPolicy = "idle";
+ IOSchedulingClass = "idle";
+ ExecStart = "${pkgs.taskwarrior}/bin/task synchronize";
+ };
+ };
+
+ systemd.user.timers.taskwarrior-sync = {
+ Unit = { Description = "Taskwarrior periodic sync"; };
+ Timer = {
+ Unit = "taskwarrior-sync.service";
+ OnCalendar = cfg.frequency;
+ };
+ Install = { WantedBy = [ "timers.target" ]; };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/udiskie.nix b/infra/libkookie/home-manager/modules/services/udiskie.nix
new file mode 100644
index 000000000000..ca31021cb5c8
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/udiskie.nix
@@ -0,0 +1,89 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.udiskie;
+
+ commandArgs = concatStringsSep " " (map (opt: "-" + opt) [
+ (if cfg.automount then "a" else "A")
+ (if cfg.notify then "n" else "N")
+ ({
+ always = "t";
+ auto = "s";
+ never = "T";
+ }.${cfg.tray})
+ ] ++ optional config.xsession.preferStatusNotifierItems "--appindicator");
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ imports = [
+ (mkRemovedOptionModule [ "services" "udiskie" "sni" ] ''
+ Support for Status Notifier Items is now configured globally through the
+
+ xsession.preferStatusNotifierItems
+
+ option. Please change to use that instead.
+ '')
+ ];
+
+ options = {
+ services.udiskie = {
+ enable = mkEnableOption "udiskie mount daemon";
+
+ automount = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to automatically mount new devices.";
+ };
+
+ notify = mkOption {
+ type = types.bool;
+ default = true;
+ description = "Whether to show pop-up notifications.";
+ };
+
+ tray = mkOption {
+ type = types.enum [ "always" "auto" "never" ];
+ default = "auto";
+ description = ''
+ Whether to display tray icon.
+ </para><para>
+ The options are
+ <variablelist>
+ <varlistentry>
+ <term><literal>always</literal></term>
+ <listitem><para>Always show tray icon.</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>auto</literal></term>
+ <listitem><para>
+ Show tray icon only when there is a device available.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>never</literal></term>
+ <listitem><para>Never show tray icon.</para></listitem>
+ </varlistentry>
+ </variablelist>
+ '';
+ };
+ };
+ };
+
+ config = mkIf config.services.udiskie.enable {
+ systemd.user.services.udiskie = {
+ Unit = {
+ Description = "udiskie mount daemon";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = { ExecStart = "${pkgs.udiskie}/bin/udiskie ${commandArgs}"; };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/unclutter.nix b/infra/libkookie/home-manager/modules/services/unclutter.nix
new file mode 100644
index 000000000000..5e7606395915
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/unclutter.nix
@@ -0,0 +1,61 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.unclutter;
+
+in {
+ options.services.unclutter = {
+
+ enable = mkEnableOption "unclutter";
+
+ package = mkOption {
+ description = "unclutter derivation to use.";
+ type = types.package;
+ default = pkgs.unclutter-xfixes;
+ defaultText = literalExample "pkgs.unclutter-xfixes";
+ };
+
+ timeout = mkOption {
+ description = "Number of seconds before the cursor is marked inactive.";
+ type = types.int;
+ default = 1;
+ };
+
+ threshold = mkOption {
+ description = "Minimum number of pixels considered cursor movement.";
+ type = types.int;
+ default = 1;
+ };
+
+ extraOptions = mkOption {
+ description = "More arguments to pass to the unclutter command.";
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "exclude-root" "ignore-scrolling" ];
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.unclutter = {
+ Unit = {
+ Description = "unclutter";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ ExecStart = ''
+ ${cfg.package}/bin/unclutter \
+ --timeout ${toString cfg.timeout} \
+ --jitter ${toString (cfg.threshold - 1)} \
+ ${concatMapStrings (x: " --${x}") cfg.extraOptions}
+ '';
+ RestartSec = 3;
+ Restart = "always";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/unison.nix b/infra/libkookie/home-manager/modules/services/unison.nix
new file mode 100644
index 000000000000..a9cf23fb66e9
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/unison.nix
@@ -0,0 +1,121 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.unison;
+
+ pairOf = t:
+ let list = types.addCheck (types.listOf t) (l: length l == 2);
+ in list // { description = list.description + " of length 2"; };
+
+ pairOptions = {
+ options = {
+ stateDirectory = mkOption {
+ type = types.path;
+ default = "${config.xdg.dataHome}/unison";
+ defaultText = "$XDG_DATA_HOME/unison";
+ description = ''
+ Unison state directory to use.
+ '';
+ };
+
+ commandOptions = mkOption rec {
+ type = with types; attrsOf str;
+ apply = mergeAttrs default;
+ default = {
+ repeat = "watch";
+ sshcmd = "${pkgs.openssh}/bin/ssh";
+ ui = "text";
+ auto = "true";
+ batch = "true";
+ log = "false"; # don't log to file, handled by systemd
+ };
+ description = ''
+ Additional command line options as a dictionary to pass to the
+ <literal>unison</literal> program.
+ </para><para>
+ See
+ <citerefentry>
+ <refentrytitle>unison</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </citerefentry>
+ for a list of available options.
+ '';
+ };
+
+ roots = mkOption {
+ type = pairOf types.str;
+ example = literalExample ''
+ [
+ "/home/user/documents"
+ "ssh://remote/documents"
+ ]
+ '';
+ description = ''
+ Pair of roots to synchronise.
+ '';
+ };
+ };
+ };
+
+ serialiseArg = key: val: escapeShellArg "-${key}=${escape [ "=" ] val}";
+
+ serialiseArgs = args: concatStringsSep " " (mapAttrsToList serialiseArg args);
+
+ makeDefs = gen:
+ mapAttrs'
+ (name: pairCfg: nameValuePair "unison-pair-${name}" (gen name pairCfg))
+ cfg.pairs;
+
+in {
+ meta.maintainers = with maintainers; [ pacien ];
+
+ options.services.unison = {
+ enable = mkEnableOption "Unison synchronisation";
+
+ pairs = mkOption {
+ type = with types; attrsOf (submodule pairOptions);
+ default = { };
+ example = literalExample ''
+ {
+ roots = [
+ "/home/user/documents"
+ "ssh://remote/documents"
+ ];
+ }
+ '';
+ description = ''
+ Unison root pairs to keep synchronised.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services = makeDefs (name: pairCfg: {
+ Unit = {
+ Description = "Unison pair sync (${name})";
+ # Retry forever, useful in case of network disruption.
+ StartLimitIntervalSec = 0;
+ };
+
+ Service = {
+ Restart = "always";
+ RestartSec = 60;
+
+ CPUSchedulingPolicy = "idle";
+ IOSchedulingClass = "idle";
+
+ Environment = [ "UNISON='${toString pairCfg.stateDirectory}'" ];
+ ExecStart = ''
+ ${pkgs.unison}/bin/unison \
+ ${serialiseArgs pairCfg.commandOptions} \
+ ${strings.concatMapStringsSep " " escapeShellArg pairCfg.roots}
+ '';
+ };
+
+ Install = { WantedBy = [ "default.target" ]; };
+ });
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/window-managers/awesome.nix b/infra/libkookie/home-manager/modules/services/window-managers/awesome.nix
new file mode 100644
index 000000000000..d2e2903f83b8
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/window-managers/awesome.nix
@@ -0,0 +1,52 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.xsession.windowManager.awesome;
+ awesome = cfg.package;
+ getLuaPath = lib: dir: "${lib}/${dir}/lua/${pkgs.luaPackages.lua.luaversion}";
+ makeSearchPath = lib.concatMapStrings (path:
+ " --search ${getLuaPath path "share"}"
+ + " --search ${getLuaPath path "lib"}");
+
+in {
+ options = {
+ xsession.windowManager.awesome = {
+ enable = mkEnableOption "Awesome window manager.";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.awesome;
+ defaultText = literalExample "pkgs.awesome";
+ description = "Package to use for running the Awesome WM.";
+ };
+
+ luaModules = mkOption {
+ default = [ ];
+ type = types.listOf types.package;
+ description = ''
+ List of lua packages available for being
+ used in the Awesome configuration.
+ '';
+ example = literalExample "[ luaPackages.oocairo ]";
+ };
+
+ noArgb = mkOption {
+ default = false;
+ type = types.bool;
+ description = ''
+ Disable client transparency support, which can be greatly
+ detrimental to performance in some setups
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ awesome ];
+ xsession.windowManager.command = "${awesome}/bin/awesome "
+ + optionalString cfg.noArgb "--no-argb " + makeSearchPath cfg.luaModules;
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/window-managers/bspwm/default.nix b/infra/libkookie/home-manager/modules/services/window-managers/bspwm/default.nix
new file mode 100644
index 000000000000..9ea5adbc8805
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/window-managers/bspwm/default.nix
@@ -0,0 +1,74 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.xsession.windowManager.bspwm;
+ bspwm = cfg.package;
+
+ camelToSnake = s:
+ builtins.replaceStrings lib.upperChars (map (c: "_${c}") lib.lowerChars) s;
+
+ formatConfig = n: v:
+ let
+ formatList = x:
+ if isList x then
+ throw "can not convert 2-dimensional lists to bspwm format"
+ else
+ formatValue x;
+
+ formatValue = v:
+ if isBool v then
+ (if v then "true" else "false")
+ else if isList v then
+ concatMapStringsSep ", " formatList v
+ else if isString v then
+ "${lib.strings.escapeShellArg v}"
+ else
+ toString v;
+ in "bspc config ${n} ${formatValue v}";
+
+ formatMonitors = n: v: "bspc monitor ${n} -d ${concatStringsSep " " v}";
+
+ formatRules = target: directiveOptions:
+ let
+ formatDirective = n: v:
+ if isBool v then
+ (if v then "${camelToSnake n}=on" else "${camelToSnake n}=off")
+ else if (n == "desktop" || n == "node") then
+ "${camelToSnake n}='${v}'"
+ else
+ "${camelToSnake n}=${lib.strings.escapeShellArg v}";
+
+ directives =
+ filterAttrs (n: v: v != null && !(lib.strings.hasPrefix "_" n))
+ directiveOptions;
+ directivesStr = builtins.concatStringsSep " "
+ (mapAttrsToList formatDirective directives);
+ in "bspc rule -a ${target} ${directivesStr}";
+
+ formatStartupPrograms = map (s: "${s} &");
+
+in {
+ options = import ./options.nix {
+ inherit pkgs;
+ inherit lib;
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ bspwm ];
+ xsession.windowManager.command = let
+ configFile = pkgs.writeShellScript "bspwmrc" (concatStringsSep "\n"
+ ((mapAttrsToList formatMonitors cfg.monitors)
+ ++ (mapAttrsToList formatConfig cfg.settings)
+ ++ (mapAttrsToList formatRules cfg.rules) ++ [''
+ # java gui fixes
+ export _JAVA_AWT_WM_NONREPARENTING=1
+ bspc rule -a sun-awt-X11-XDialogPeer state=floating
+ ''] ++ [ cfg.extraConfig ]
+ ++ (formatStartupPrograms cfg.startupPrograms)));
+ configCmdOpt = optionalString (cfg.settings != null) "-c ${configFile}";
+ in "${cfg.package}/bin/bspwm ${configCmdOpt}";
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/window-managers/bspwm/options.nix b/infra/libkookie/home-manager/modules/services/window-managers/bspwm/options.nix
new file mode 100644
index 000000000000..58a58a1a782a
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/window-managers/bspwm/options.nix
@@ -0,0 +1,214 @@
+{ pkgs, lib }:
+
+with lib;
+
+let
+
+ rule = types.submodule {
+ options = {
+ monitor = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "The monitor where the rule should be applied.";
+ example = "HDMI-0";
+ };
+
+ desktop = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "The desktop where the rule should be applied.";
+ example = "^8";
+ };
+
+ node = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = "The node where the rule should be applied.";
+ example = "1";
+ };
+
+ state = mkOption {
+ type = types.nullOr
+ (types.enum [ "tiled" "pseudo_tiled" "floating" "fullscreen" ]);
+ default = null;
+ description = "The state in which a new window should spawn.";
+ example = "floating";
+ };
+
+ layer = mkOption {
+ type = types.nullOr (types.enum [ "below" "normal" "above" ]);
+ default = null;
+ description = "The layer where a new window should spawn.";
+ example = "above";
+ };
+
+ splitDir = mkOption {
+ type = types.nullOr (types.enum [ "north" "west" "south" "east" ]);
+ default = null;
+ description = "The direction where the container is going to be split.";
+ example = "south";
+ };
+
+ splitRatio = mkOption {
+ type = types.nullOr types.float;
+ default = null;
+ description = ''
+ The ratio between the new window and the previous existing window in
+ the desktop.
+ '';
+ example = 0.65;
+ };
+
+ hidden = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = "Whether the node should occupy any space.";
+ example = true;
+ };
+
+ sticky = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = "Whether the node should stay on the focused desktop.";
+ example = true;
+ };
+
+ private = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = ''
+ Whether the node should stay in the same tiling position and size.
+ '';
+ example = true;
+ };
+
+ locked = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = ''
+ Whether the node should ignore <command>node --close</command>
+ messages.
+ '';
+ example = true;
+ };
+
+ marked = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = "Whether the node will be marked for deferred actions.";
+ example = true;
+ };
+
+ center = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = ''
+ Whether the node will be put in the center, in floating mode.
+ '';
+ example = true;
+ };
+
+ follow = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = "Whether focus should follow the node when it is moved.";
+ example = true;
+ };
+
+ manage = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = ''
+ Whether the window should be managed by bspwm. If false, the window
+ will be ignored by bspwm entirely. This is useful for overlay apps,
+ e.g. screenshot tools.
+ '';
+ example = true;
+ };
+
+ focus = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = "Whether the node should gain focus on creation.";
+ example = true;
+ };
+
+ border = mkOption {
+ type = types.nullOr types.bool;
+ default = null;
+ description = "Whether the node should have border.";
+ example = true;
+ };
+ };
+ };
+
+in {
+ xsession.windowManager.bspwm = {
+ enable = mkEnableOption "bspwm window manager.";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.bspwm;
+ defaultText = literalExample "pkgs.bspwm";
+ description = "bspwm package to use.";
+ example = literalExample "pkgs.bspwm-unstable";
+ };
+
+ settings = mkOption {
+ type = with types;
+ let primitive = either bool (either int (either float str));
+ in attrsOf (either primitive (listOf primitive));
+ default = { };
+ description = "bspwm configuration";
+ example = {
+ "border_width" = 2;
+ "split_ratio" = 0.52;
+ "gapless_monocle" = true;
+ };
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Additional configuration to add.";
+ example = ''
+ bspc subscribe all > ~/bspc-report.log &
+ '';
+ };
+
+ monitors = mkOption {
+ type = types.attrsOf (types.listOf types.str);
+ default = { };
+ description = "bspc monitor configurations";
+ example = { "HDMI-0" = [ "web" "terminal" "III" "IV" ]; };
+ };
+
+ rules = mkOption {
+ type = types.attrsOf rule;
+ default = { };
+ description = "bspc rules";
+ example = literalExample ''
+ {
+ "Gimp" = {
+ desktop = "^8";
+ state = "floating";
+ follow = true;
+ };
+ "Kupfer.py" = {
+ focus = true;
+ };
+ "Screenkey" = {
+ manage = false;
+ };
+ }
+ '';
+ };
+
+ startupPrograms = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ description = "Programs to be executed during startup.";
+ example = [ "numlockx on" "tilda" ];
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/window-managers/i3-sway/i3.nix b/infra/libkookie/home-manager/modules/services/window-managers/i3-sway/i3.nix
new file mode 100644
index 000000000000..37f6e8bd8302
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/window-managers/i3-sway/i3.nix
@@ -0,0 +1,272 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.xsession.windowManager.i3;
+
+ commonOptions = import ./lib/options.nix {
+ inherit config lib cfg pkgs;
+ moduleName = "i3";
+ isGaps = cfg.package == pkgs.i3-gaps;
+ };
+
+ configModule = types.submodule {
+ options = {
+ inherit (commonOptions)
+ fonts window floating focus assigns modifier workspaceLayout
+ workspaceAutoBackAndForth keycodebindings colors bars startup gaps menu
+ terminal;
+
+ keybindings = mkOption {
+ type = types.attrsOf (types.nullOr types.str);
+ default = mapAttrs (n: mkOptionDefault) {
+ "${cfg.config.modifier}+Return" = "exec ${cfg.config.terminal}";
+ "${cfg.config.modifier}+Shift+q" = "kill";
+ "${cfg.config.modifier}+d" = "exec ${cfg.config.menu}";
+
+ "${cfg.config.modifier}+Left" = "focus left";
+ "${cfg.config.modifier}+Down" = "focus down";
+ "${cfg.config.modifier}+Up" = "focus up";
+ "${cfg.config.modifier}+Right" = "focus right";
+
+ "${cfg.config.modifier}+Shift+Left" = "move left";
+ "${cfg.config.modifier}+Shift+Down" = "move down";
+ "${cfg.config.modifier}+Shift+Up" = "move up";
+ "${cfg.config.modifier}+Shift+Right" = "move right";
+
+ "${cfg.config.modifier}+h" = "split h";
+ "${cfg.config.modifier}+v" = "split v";
+ "${cfg.config.modifier}+f" = "fullscreen toggle";
+
+ "${cfg.config.modifier}+s" = "layout stacking";
+ "${cfg.config.modifier}+w" = "layout tabbed";
+ "${cfg.config.modifier}+e" = "layout toggle split";
+
+ "${cfg.config.modifier}+Shift+space" = "floating toggle";
+ "${cfg.config.modifier}+space" = "focus mode_toggle";
+
+ "${cfg.config.modifier}+a" = "focus parent";
+
+ "${cfg.config.modifier}+Shift+minus" = "move scratchpad";
+ "${cfg.config.modifier}+minus" = "scratchpad show";
+
+ "${cfg.config.modifier}+1" = "workspace number 1";
+ "${cfg.config.modifier}+2" = "workspace number 2";
+ "${cfg.config.modifier}+3" = "workspace number 3";
+ "${cfg.config.modifier}+4" = "workspace number 4";
+ "${cfg.config.modifier}+5" = "workspace number 5";
+ "${cfg.config.modifier}+6" = "workspace number 6";
+ "${cfg.config.modifier}+7" = "workspace number 7";
+ "${cfg.config.modifier}+8" = "workspace number 8";
+ "${cfg.config.modifier}+9" = "workspace number 9";
+ "${cfg.config.modifier}+0" = "workspace number 10";
+
+ "${cfg.config.modifier}+Shift+1" =
+ "move container to workspace number 1";
+ "${cfg.config.modifier}+Shift+2" =
+ "move container to workspace number 2";
+ "${cfg.config.modifier}+Shift+3" =
+ "move container to workspace number 3";
+ "${cfg.config.modifier}+Shift+4" =
+ "move container to workspace number 4";
+ "${cfg.config.modifier}+Shift+5" =
+ "move container to workspace number 5";
+ "${cfg.config.modifier}+Shift+6" =
+ "move container to workspace number 6";
+ "${cfg.config.modifier}+Shift+7" =
+ "move container to workspace number 7";
+ "${cfg.config.modifier}+Shift+8" =
+ "move container to workspace number 8";
+ "${cfg.config.modifier}+Shift+9" =
+ "move container to workspace number 9";
+ "${cfg.config.modifier}+Shift+0" =
+ "move container to workspace number 10";
+
+ "${cfg.config.modifier}+Shift+c" = "reload";
+ "${cfg.config.modifier}+Shift+r" = "restart";
+ "${cfg.config.modifier}+Shift+e" =
+ "exec i3-nagbar -t warning -m 'Do you want to exit i3?' -b 'Yes' 'i3-msg exit'";
+
+ "${cfg.config.modifier}+r" = "mode resize";
+ };
+ defaultText = "Default i3 keybindings.";
+ description = ''
+ An attribute set that assigns a key press to an action using a key symbol.
+ See <link xlink:href="https://i3wm.org/docs/userguide.html#keybindings"/>.
+ </para><para>
+ Consider to use <code>lib.mkOptionDefault</code> function to extend or override
+ default keybindings instead of specifying all of them from scratch.
+ '';
+ example = literalExample ''
+ let
+ modifier = config.xsession.windowManager.i3.config.modifier;
+ in lib.mkOptionDefault {
+ "''${modifier}+Return" = "exec i3-sensible-terminal";
+ "''${modifier}+Shift+q" = "kill";
+ "''${modifier}+d" = "exec \${pkgs.dmenu}/bin/dmenu_run";
+ }
+ '';
+ };
+
+ modes = mkOption {
+ type = types.attrsOf (types.attrsOf types.str);
+ default = {
+ resize = {
+ "Left" = "resize shrink width 10 px or 10 ppt";
+ "Down" = "resize grow height 10 px or 10 ppt";
+ "Up" = "resize shrink height 10 px or 10 ppt";
+ "Right" = "resize grow width 10 px or 10 ppt";
+ "Escape" = "mode default";
+ "Return" = "mode default";
+ };
+ };
+ description = ''
+ An attribute set that defines binding modes and keybindings
+ inside them
+
+ Only basic keybinding is supported (bindsym keycomb action),
+ for more advanced setup use 'i3.extraConfig'.
+ '';
+ };
+ };
+ };
+
+ commonFunctions = import ./lib/functions.nix {
+ inherit cfg lib;
+ moduleName = "i3";
+ };
+
+ inherit (commonFunctions)
+ keybindingsStr keycodebindingsStr modeStr assignStr barStr gapsStr
+ floatingCriteriaStr windowCommandsStr colorSetStr;
+
+ startupEntryStr = { command, always, notification, workspace, ... }: ''
+ ${if always then "exec_always" else "exec"} ${
+ if (notification && workspace == null) then "" else "--no-startup-id"
+ } ${
+ if (workspace == null) then
+ command
+ else
+ "i3-msg 'workspace ${workspace}; exec ${command}'"
+ }
+ '';
+
+ configFile = pkgs.writeText "i3.conf" ((if cfg.config != null then
+ with cfg.config; ''
+ font pango:${concatStringsSep ", " fonts}
+ floating_modifier ${floating.modifier}
+ new_window ${if window.titlebar then "normal" else "pixel"} ${
+ toString window.border
+ }
+ new_float ${if floating.titlebar then "normal" else "pixel"} ${
+ toString floating.border
+ }
+ hide_edge_borders ${window.hideEdgeBorders}
+ force_focus_wrapping ${if focus.forceWrapping then "yes" else "no"}
+ focus_follows_mouse ${if focus.followMouse then "yes" else "no"}
+ focus_on_window_activation ${focus.newWindow}
+ mouse_warping ${if focus.mouseWarping then "output" else "none"}
+ workspace_layout ${workspaceLayout}
+ workspace_auto_back_and_forth ${
+ if workspaceAutoBackAndForth then "yes" else "no"
+ }
+
+ client.focused ${colorSetStr colors.focused}
+ client.focused_inactive ${colorSetStr colors.focusedInactive}
+ client.unfocused ${colorSetStr colors.unfocused}
+ client.urgent ${colorSetStr colors.urgent}
+ client.placeholder ${colorSetStr colors.placeholder}
+ client.background ${colors.background}
+
+ ${keybindingsStr { inherit keybindings; }}
+ ${keycodebindingsStr keycodebindings}
+ ${concatStringsSep "\n" (mapAttrsToList modeStr modes)}
+ ${concatStringsSep "\n" (mapAttrsToList assignStr assigns)}
+ ${concatStringsSep "\n" (map barStr bars)}
+ ${optionalString (gaps != null) gapsStr}
+ ${concatStringsSep "\n" (map floatingCriteriaStr floating.criteria)}
+ ${concatStringsSep "\n" (map windowCommandsStr window.commands)}
+ ${concatStringsSep "\n" (map startupEntryStr startup)}
+ ''
+ else
+ "") + "\n" + cfg.extraConfig);
+
+ # Validates the i3 configuration
+ checkI3Config =
+ pkgs.runCommandLocal "i3-config" { buildInputs = [ cfg.package ]; } ''
+ # We have to make sure the wrapper does not start a dbus session
+ export DBUS_SESSION_BUS_ADDRESS=1
+
+ # A zero exit code means i3 succesfully validated the configuration
+ i3 -c ${configFile} -C -d all || {
+ echo "i3 configuration validation failed"
+ echo "For a verbose log of the failure, run 'i3 -c ${configFile} -C -d all'"
+ exit 1
+ };
+ cp ${configFile} $out
+ '';
+
+in {
+ options = {
+ xsession.windowManager.i3 = {
+ enable = mkEnableOption "i3 window manager.";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.i3;
+ defaultText = literalExample "pkgs.i3";
+ example = literalExample "pkgs.i3-gaps";
+ description = ''
+ i3 package to use.
+ If 'i3.config.gaps' settings are specified, 'pkgs.i3-gaps' will be set as a default package.
+ '';
+ };
+
+ config = mkOption {
+ type = types.nullOr configModule;
+ default = { };
+ description = "i3 configuration options.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description =
+ "Extra configuration lines to add to ~/.config/i3/config.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+ home.packages = [ cfg.package ];
+ xsession.windowManager.command = "${cfg.package}/bin/i3";
+ xdg.configFile."i3/config" = {
+ source = checkI3Config;
+ onChange = ''
+ i3Socket=''${XDG_RUNTIME_DIR:-/run/user/$UID}/i3/ipc-socket.*
+ if [ -S $i3Socket ]; then
+ echo "Reloading i3"
+ $DRY_RUN_CMD ${cfg.package}/bin/i3-msg -s $i3Socket reload 1>/dev/null
+ fi
+ '';
+ };
+ }
+
+ (mkIf (cfg.config != null) {
+ xsession.windowManager.i3.package =
+ mkDefault (if (cfg.config.gaps != null) then pkgs.i3-gaps else pkgs.i3);
+ })
+
+ (mkIf (cfg.config != null
+ && (any (s: s.workspace != null) cfg.config.startup)) {
+ warnings = [
+ ("'xsession.windowManager.i3.config.startup.*.workspace' is deprecated, "
+ + "use 'xsession.windowManager.i3.config.assigns' instead."
+ + "See https://github.com/nix-community/home-manager/issues/265.")
+ ];
+ })
+ ]);
+}
diff --git a/infra/libkookie/home-manager/modules/services/window-managers/i3-sway/lib/functions.nix b/infra/libkookie/home-manager/modules/services/window-managers/i3-sway/lib/functions.nix
new file mode 100644
index 000000000000..9391e6e92fc8
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/window-managers/i3-sway/lib/functions.nix
@@ -0,0 +1,127 @@
+{ cfg, lib, moduleName }:
+
+with lib;
+
+rec {
+ criteriaStr = criteria:
+ "[${
+ concatStringsSep " " (mapAttrsToList (k: v: ''${k}="${v}"'') criteria)
+ }]";
+
+ keybindingsStr = { keybindings, bindsymArgs ? "" }:
+ concatStringsSep "\n" (mapAttrsToList (keycomb: action:
+ optionalString (action != null) "bindsym ${
+ lib.optionalString (bindsymArgs != "") "${bindsymArgs} "
+ }${keycomb} ${action}") keybindings);
+
+ keycodebindingsStr = keycodebindings:
+ concatStringsSep "\n" (mapAttrsToList (keycomb: action:
+ optionalString (action != null) "bindcode ${keycomb} ${action}")
+ keycodebindings);
+
+ colorSetStr = c:
+ concatStringsSep " " [
+ c.border
+ c.background
+ c.text
+ c.indicator
+ c.childBorder
+ ];
+ barColorSetStr = c: concatStringsSep " " [ c.border c.background c.text ];
+
+ modeStr = name: keybindings: ''
+ mode "${name}" {
+ ${keybindingsStr { inherit keybindings; }}
+ }
+ '';
+
+ assignStr = workspace: criteria:
+ concatStringsSep "\n"
+ (map (c: "assign ${criteriaStr c} ${workspace}") criteria);
+
+ barStr = { id, fonts, mode, hiddenState, position, workspaceButtons
+ , workspaceNumbers, command, statusCommand, colors, trayOutput, extraConfig
+ , ... }:
+ let colorsNotNull = lib.filterAttrs (n: v: v != null) colors != { };
+ in ''
+ bar {
+ ${optionalString (id != null) "id ${id}"}
+ ${
+ optionalString (fonts != [ ])
+ "font pango:${concatStringsSep ", " fonts}"
+ }
+ ${optionalString (mode != null) "mode ${mode}"}
+ ${optionalString (hiddenState != null) "hidden_state ${hiddenState}"}
+ ${optionalString (position != null) "position ${position}"}
+ ${
+ optionalString (statusCommand != null)
+ "status_command ${statusCommand}"
+ }
+ ${moduleName}bar_command ${command}
+ ${
+ optionalString (workspaceButtons != null)
+ "workspace_buttons ${if workspaceButtons then "yes" else "no"}"
+ }
+ ${
+ optionalString (workspaceNumbers != null)
+ "strip_workspace_numbers ${if !workspaceNumbers then "yes" else "no"}"
+ }
+ ${optionalString (trayOutput != null) "tray_output ${trayOutput}"}
+ ${optionalString colorsNotNull "colors {"}
+ ${
+ optionalString (colors.background != null)
+ "background ${colors.background}"
+ }
+ ${
+ optionalString (colors.statusline != null)
+ "statusline ${colors.statusline}"
+ }
+ ${
+ optionalString (colors.separator != null)
+ "separator ${colors.separator}"
+ }
+ ${
+ optionalString (colors.focusedWorkspace != null)
+ "focused_workspace ${barColorSetStr colors.focusedWorkspace}"
+ }
+ ${
+ optionalString (colors.activeWorkspace != null)
+ "active_workspace ${barColorSetStr colors.activeWorkspace}"
+ }
+ ${
+ optionalString (colors.inactiveWorkspace != null)
+ "inactive_workspace ${barColorSetStr colors.inactiveWorkspace}"
+ }
+ ${
+ optionalString (colors.urgentWorkspace != null)
+ "urgent_workspace ${barColorSetStr colors.urgentWorkspace}"
+ }
+ ${
+ optionalString (colors.bindingMode != null)
+ "binding_mode ${barColorSetStr colors.bindingMode}"
+ }
+ ${optionalString colorsNotNull "}"}
+ ${extraConfig}
+ }
+ '';
+
+ gapsStr = with cfg.config.gaps; ''
+ ${optionalString (inner != null) "gaps inner ${toString inner}"}
+ ${optionalString (outer != null) "gaps outer ${toString outer}"}
+ ${optionalString (horizontal != null)
+ "gaps horizontal ${toString horizontal}"}
+ ${optionalString (vertical != null) "gaps vertical ${toString vertical}"}
+ ${optionalString (top != null) "gaps top ${toString top}"}
+ ${optionalString (bottom != null) "gaps bottom ${toString bottom}"}
+ ${optionalString (left != null) "gaps left ${toString left}"}
+ ${optionalString (right != null) "gaps right ${toString right}"}
+
+ ${optionalString smartGaps "smart_gaps on"}
+ ${optionalString (smartBorders != "off") "smart_borders ${smartBorders}"}
+ '';
+
+ floatingCriteriaStr = criteria:
+ "for_window ${criteriaStr criteria} floating enable";
+ windowCommandsStr = { command, criteria, ... }:
+ "for_window ${criteriaStr criteria} ${command}";
+}
diff --git a/infra/libkookie/home-manager/modules/services/window-managers/i3-sway/lib/options.nix b/infra/libkookie/home-manager/modules/services/window-managers/i3-sway/lib/options.nix
new file mode 100644
index 000000000000..f1d0436a5e25
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/window-managers/i3-sway/lib/options.nix
@@ -0,0 +1,772 @@
+{ config, lib, moduleName, cfg, pkgs, capitalModuleName ? moduleName
+, isGaps ? true }:
+
+with lib;
+
+let
+ fonts = mkOption {
+ type = types.listOf types.str;
+ default = [ "monospace 8" ];
+ description = ''
+ Font list used for window titles. Only FreeType fonts are supported.
+ The order here is important (e.g. icons font should go before the one used for text).
+ '';
+ example = [ "FontAwesome 10" "Terminus 10" ];
+ };
+
+ startupModule = types.submodule {
+ options = {
+ command = mkOption {
+ type = types.str;
+ description = "Command that will be executed on startup.";
+ };
+
+ always = mkOption {
+ type = types.bool;
+ default = false;
+ description = "Whether to run command on each ${moduleName} restart.";
+ };
+ } // optionalAttrs (moduleName == "i3") {
+ notification = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether to enable startup-notification support for the command.
+ See <option>--no-startup-id</option> option description in the i3 user guide.
+ '';
+ };
+
+ workspace = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Launch application on a particular workspace. DEPRECATED:
+ Use <varname><link linkend="opt-xsession.windowManager.i3.config.assigns">xsession.windowManager.i3.config.assigns</link></varname>
+ instead. See <link xlink:href="https://github.com/nix-community/home-manager/issues/265"/>.
+ '';
+ };
+ };
+
+ };
+
+ barModule = types.submodule {
+ options = let
+ versionAtLeast2009 = versionAtLeast config.home.stateVersion "20.09";
+ mkNullableOption = { type, default, ... }@args:
+ mkOption (args // optionalAttrs versionAtLeast2009 {
+ type = types.nullOr type;
+ default = null;
+ example = default;
+ } // {
+ defaultText = literalExample ''
+ ${
+ if isString default then default else "See code"
+ } for state version < 20.09,
+ null for state version ≥ 20.09
+ '';
+ });
+ in {
+ fonts = fonts // optionalAttrs versionAtLeast2009 { default = [ ]; };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Extra configuration lines for this bar.";
+ };
+
+ id = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Specifies the bar ID for the configured bar instance.
+ If this option is missing, the ID is set to bar-x, where x corresponds
+ to the position of the embedding bar block in the config file.
+ '';
+ };
+
+ mode = mkNullableOption {
+ type = types.enum [ "dock" "hide" "invisible" ];
+ default = "dock";
+ description = "Bar visibility mode.";
+ };
+
+ hiddenState = mkNullableOption {
+ type = types.enum [ "hide" "show" ];
+ default = "hide";
+ description = "The default bar mode when 'bar.mode' == 'hide'.";
+ };
+
+ position = mkNullableOption {
+ type = types.enum [ "top" "bottom" ];
+ default = "bottom";
+ description = "The edge of the screen ${moduleName}bar should show up.";
+ };
+
+ workspaceButtons = mkNullableOption {
+ type = types.bool;
+ default = true;
+ description = "Whether workspace buttons should be shown or not.";
+ };
+
+ workspaceNumbers = mkNullableOption {
+ type = types.bool;
+ default = true;
+ description =
+ "Whether workspace numbers should be displayed within the workspace buttons.";
+ };
+
+ command = mkOption {
+ type = types.str;
+ default = "${cfg.package}/bin/${moduleName}bar";
+ defaultText = "i3bar";
+ description = "Command that will be used to start a bar.";
+ example = if moduleName == "i3" then
+ "\${pkgs.i3-gaps}/bin/i3bar -t"
+ else
+ "\${pkgs.waybar}/bin/waybar";
+ };
+
+ statusCommand = mkOption {
+ type = types.nullOr types.str;
+ default =
+ if versionAtLeast2009 then null else "${pkgs.i3status}/bin/i3status";
+ example = "i3status";
+ description = "Command that will be used to get status lines.";
+ };
+
+ colors = mkOption {
+ type = types.submodule {
+ options = {
+ background = mkNullableOption {
+ type = types.str;
+ default = "#000000";
+ description = "Background color of the bar.";
+ };
+
+ statusline = mkNullableOption {
+ type = types.str;
+ default = "#ffffff";
+ description = "Text color to be used for the statusline.";
+ };
+
+ separator = mkNullableOption {
+ type = types.str;
+ default = "#666666";
+ description = "Text color to be used for the separator.";
+ };
+
+ focusedWorkspace = mkNullableOption {
+ type = barColorSetModule;
+ default = {
+ border = "#4c7899";
+ background = "#285577";
+ text = "#ffffff";
+ };
+ description = ''
+ Border, background and text color for a workspace button when the workspace has focus.
+ '';
+ };
+
+ activeWorkspace = mkNullableOption {
+ type = barColorSetModule;
+ default = {
+ border = "#333333";
+ background = "#5f676a";
+ text = "#ffffff";
+ };
+ description = ''
+ Border, background and text color for a workspace button when the workspace is active.
+ '';
+ };
+
+ inactiveWorkspace = mkNullableOption {
+ type = barColorSetModule;
+ default = {
+ border = "#333333";
+ background = "#222222";
+ text = "#888888";
+ };
+ description = ''
+ Border, background and text color for a workspace button when the workspace does not
+ have focus and is not active.
+ '';
+ };
+
+ urgentWorkspace = mkNullableOption {
+ type = barColorSetModule;
+ default = {
+ border = "#2f343a";
+ background = "#900000";
+ text = "#ffffff";
+ };
+ description = ''
+ Border, background and text color for a workspace button when the workspace contains
+ a window with the urgency hint set.
+ '';
+ };
+
+ bindingMode = mkNullableOption {
+ type = barColorSetModule;
+ default = {
+ border = "#2f343a";
+ background = "#900000";
+ text = "#ffffff";
+ };
+ description =
+ "Border, background and text color for the binding mode indicator";
+ };
+ };
+ };
+ default = { };
+ description = ''
+ Bar color settings. All color classes can be specified using submodules
+ with 'border', 'background', 'text', fields and RGB color hex-codes as values.
+ See default values for the reference.
+ Note that 'background', 'status', and 'separator' parameters take a single RGB value.
+
+ See <link xlink:href="https://i3wm.org/docs/userguide.html#_colors"/>.
+ '';
+ };
+
+ trayOutput = mkNullableOption {
+ type = types.str;
+ default = "primary";
+ description = "Where to output tray.";
+ };
+ };
+ };
+
+ barColorSetModule = types.submodule {
+ options = {
+ border = mkOption {
+ type = types.str;
+ visible = false;
+ };
+
+ background = mkOption {
+ type = types.str;
+ visible = false;
+ };
+
+ text = mkOption {
+ type = types.str;
+ visible = false;
+ };
+ };
+ };
+
+ colorSetModule = types.submodule {
+ options = {
+ border = mkOption {
+ type = types.str;
+ visible = false;
+ };
+
+ childBorder = mkOption {
+ type = types.str;
+ visible = false;
+ };
+
+ background = mkOption {
+ type = types.str;
+ visible = false;
+ };
+
+ text = mkOption {
+ type = types.str;
+ visible = false;
+ };
+
+ indicator = mkOption {
+ type = types.str;
+ visible = false;
+ };
+ };
+ };
+
+ windowCommandModule = types.submodule {
+ options = {
+ command = mkOption {
+ type = types.str;
+ description = "${capitalModuleName}wm command to execute.";
+ example = "border pixel 1";
+ };
+
+ criteria = mkOption {
+ type = criteriaModule;
+ description =
+ "Criteria of the windows on which command should be executed.";
+ example = { title = "x200: ~/work"; };
+ };
+ };
+ };
+
+ criteriaModule = types.attrsOf types.str;
+in {
+ inherit fonts;
+
+ window = mkOption {
+ type = types.submodule {
+ options = {
+ titlebar = mkOption {
+ type = types.bool;
+ default = !isGaps;
+ defaultText = if moduleName == "i3" then
+ "xsession.windowManager.i3.package != nixpkgs.i3-gaps (titlebar should be disabled for i3-gaps)"
+ else
+ "false";
+ description = "Whether to show window titlebars.";
+ };
+
+ border = mkOption {
+ type = types.int;
+ default = 2;
+ description = "Window border width.";
+ };
+
+ hideEdgeBorders = mkOption {
+ type = types.enum [ "none" "vertical" "horizontal" "both" "smart" ];
+ default = "none";
+ description = "Hide window borders adjacent to the screen edges.";
+ };
+
+ commands = mkOption {
+ type = types.listOf windowCommandModule;
+ default = [ ];
+ description = ''
+ List of commands that should be executed on specific windows.
+ See <option>for_window</option> ${moduleName}wm option documentation.
+ '';
+ example = [{
+ command = "border pixel 1";
+ criteria = { class = "XTerm"; };
+ }];
+ };
+ };
+ };
+ default = { };
+ description = "Window titlebar and border settings.";
+ };
+
+ floating = mkOption {
+ type = types.submodule {
+ options = {
+ titlebar = mkOption {
+ type = types.bool;
+ default = !isGaps;
+ defaultText = if moduleName == "i3" then
+ "xsession.windowManager.i3.package != nixpkgs.i3-gaps (titlebar should be disabled for i3-gaps)"
+ else
+ "false";
+ description = "Whether to show floating window titlebars.";
+ };
+
+ border = mkOption {
+ type = types.int;
+ default = 2;
+ description = "Floating windows border width.";
+ };
+
+ modifier = mkOption {
+ type =
+ types.enum [ "Shift" "Control" "Mod1" "Mod2" "Mod3" "Mod4" "Mod5" ];
+ default = cfg.config.modifier;
+ defaultText = "${moduleName}.config.modifier";
+ description =
+ "Modifier key that can be used to drag floating windows.";
+ example = "Mod4";
+ };
+
+ criteria = mkOption {
+ type = types.listOf criteriaModule;
+ default = [ ];
+ description =
+ "List of criteria for windows that should be opened in a floating mode.";
+ example = [
+ { "title" = "Steam - Update News"; }
+ { "class" = "Pavucontrol"; }
+ ];
+ };
+ };
+ };
+ default = { };
+ description = "Floating window settings.";
+ };
+
+ focus = mkOption {
+ type = types.submodule {
+ options = {
+ newWindow = mkOption {
+ type = types.enum [ "smart" "urgent" "focus" "none" ];
+ default = "smart";
+ description = ''
+ This option modifies focus behavior on new window activation.
+
+ See <link xlink:href="https://i3wm.org/docs/userguide.html#focus_on_window_activation"/>
+ '';
+ example = "none";
+ };
+
+ followMouse = mkOption {
+ type = if moduleName == "sway" then
+ types.either (types.enum [ "yes" "no" "always" ]) types.bool
+ else
+ types.bool;
+ default = if moduleName == "sway" then "yes" else true;
+ description = "Whether focus should follow the mouse.";
+ apply = val:
+ if (moduleName == "sway" && isBool val) then
+ (if val then "yes" else "no")
+ else
+ val;
+ };
+
+ forceWrapping = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to force focus wrapping in tabbed or stacked container.
+
+ See <link xlink:href="https://i3wm.org/docs/userguide.html#_focus_wrapping"/>
+ '';
+ };
+
+ mouseWarping = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Whether mouse cursor should be warped to the center of the window when switching focus
+ to a window on a different output.
+ '';
+ };
+ };
+ };
+ default = { };
+ description = "Focus related settings.";
+ };
+
+ assigns = mkOption {
+ type = types.attrsOf (types.listOf criteriaModule);
+ default = { };
+ description = ''
+ An attribute set that assigns applications to workspaces based
+ on criteria.
+ '';
+ example = literalExample ''
+ {
+ "1: web" = [{ class = "^Firefox$"; }];
+ "0: extra" = [{ class = "^Firefox$"; window_role = "About"; }];
+ }
+ '';
+ };
+
+ modifier = mkOption {
+ type = types.enum [ "Shift" "Control" "Mod1" "Mod2" "Mod3" "Mod4" "Mod5" ];
+ default = "Mod1";
+ description = "Modifier key that is used for all default keybindings.";
+ example = "Mod4";
+ };
+
+ workspaceLayout = mkOption {
+ type = types.enum [ "default" "stacked" "tabbed" ];
+ default = "default";
+ example = "tabbed";
+ description = ''
+ The mode in which new containers on workspace level will
+ start.
+ '';
+ };
+
+ workspaceAutoBackAndForth = mkOption {
+ type = types.bool;
+ default = false;
+ example = true;
+ description = ''
+ Assume you are on workspace "1: www" and switch to "2: IM" using
+ mod+2 because somebody sent you a message. You don’t need to remember
+ where you came from now, you can just press $mod+2 again to switch
+ back to "1: www".
+ '';
+ };
+
+ keycodebindings = mkOption {
+ type = types.attrsOf (types.nullOr types.str);
+ default = { };
+ description = ''
+ An attribute set that assigns keypress to an action using key code.
+ See <link xlink:href="https://i3wm.org/docs/userguide.html#keybindings"/>.
+ '';
+ example = { "214" = "exec /bin/script.sh"; };
+ };
+
+ colors = mkOption {
+ type = types.submodule {
+ options = {
+ background = mkOption {
+ type = types.str;
+ default = "#ffffff";
+ description = ''
+ Background color of the window. Only applications which do not cover
+ the whole area expose the color.
+ '';
+ };
+
+ focused = mkOption {
+ type = colorSetModule;
+ default = {
+ border = "#4c7899";
+ background = "#285577";
+ text = "#ffffff";
+ indicator = "#2e9ef4";
+ childBorder = "#285577";
+ };
+ description = "A window which currently has the focus.";
+ };
+
+ focusedInactive = mkOption {
+ type = colorSetModule;
+ default = {
+ border = "#333333";
+ background = "#5f676a";
+ text = "#ffffff";
+ indicator = "#484e50";
+ childBorder = "#5f676a";
+ };
+ description = ''
+ A window which is the focused one of its container,
+ but it does not have the focus at the moment.
+ '';
+ };
+
+ unfocused = mkOption {
+ type = colorSetModule;
+ default = {
+ border = "#333333";
+ background = "#222222";
+ text = "#888888";
+ indicator = "#292d2e";
+ childBorder = "#222222";
+ };
+ description = "A window which is not focused.";
+ };
+
+ urgent = mkOption {
+ type = colorSetModule;
+ default = {
+ border = "#2f343a";
+ background = "#900000";
+ text = "#ffffff";
+ indicator = "#900000";
+ childBorder = "#900000";
+ };
+ description = "A window which has its urgency hint activated.";
+ };
+
+ placeholder = mkOption {
+ type = colorSetModule;
+ default = {
+ border = "#000000";
+ background = "#0c0c0c";
+ text = "#ffffff";
+ indicator = "#000000";
+ childBorder = "#0c0c0c";
+ };
+ description = ''
+ Background and text color are used to draw placeholder window
+ contents (when restoring layouts). Border and indicator are ignored.
+ '';
+ };
+ };
+ };
+ default = { };
+ description = ''
+ Color settings. All color classes can be specified using submodules
+ with 'border', 'background', 'text', 'indicator' and 'childBorder' fields
+ and RGB color hex-codes as values. See default values for the reference.
+ Note that '${moduleName}.config.colors.background' parameter takes a single RGB value.
+
+ See <link xlink:href="https://i3wm.org/docs/userguide.html#_changing_colors"/>.
+ '';
+ };
+
+ bars = mkOption {
+ type = types.listOf barModule;
+ default = if versionAtLeast config.home.stateVersion "20.09" then [{
+ mode = "dock";
+ hiddenState = "hide";
+ position = "bottom";
+ workspaceButtons = true;
+ workspaceNumbers = true;
+ statusCommand = "${pkgs.i3status}/bin/i3status";
+ fonts = [ "monospace 8" ];
+ trayOutput = "primary";
+ colors = {
+ background = "#000000";
+ statusline = "#ffffff";
+ separator = "#666666";
+ focusedWorkspace = {
+ border = "#4c7899";
+ background = "#285577";
+ text = "#ffffff";
+ };
+ activeWorkspace = {
+ border = "#333333";
+ background = "#5f676a";
+ text = "#ffffff";
+ };
+ inactiveWorkspace = {
+ border = "#333333";
+ background = "#222222";
+ text = "#888888";
+ };
+ urgentWorkspace = {
+ border = "#2f343a";
+ background = "#900000";
+ text = "#ffffff";
+ };
+ bindingMode = {
+ border = "#2f343a";
+ background = "#900000";
+ text = "#ffffff";
+ };
+ };
+ }] else
+ [ { } ];
+ description = ''
+ ${capitalModuleName} bars settings blocks. Set to empty list to remove bars completely.
+ '';
+ };
+
+ startup = mkOption {
+ type = types.listOf startupModule;
+ default = [ ];
+ description = ''
+ Commands that should be executed at startup.
+
+ See <link xlink:href="https://i3wm.org/docs/userguide.html#_automatically_starting_applications_on_i3_startup"/>.
+ '';
+ example = if moduleName == "i3" then
+ literalExample ''
+ [
+ { command = "systemctl --user restart polybar"; always = true; notification = false; }
+ { command = "dropbox start"; notification = false; }
+ { command = "firefox"; workspace = "1: web"; }
+ ];
+ ''
+ else
+ literalExample ''
+ [
+ { command = "systemctl --user restart waybar"; always = true; }
+ { command = "dropbox start"; }
+ { command = "firefox"; }
+ ]
+ '';
+ };
+
+ gaps = mkOption {
+ type = types.nullOr (types.submodule {
+ options = {
+ inner = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Inner gaps value.";
+ example = 12;
+ };
+
+ outer = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Outer gaps value.";
+ example = 5;
+ };
+
+ horizontal = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Horizontal gaps value.";
+ example = 5;
+ };
+
+ vertical = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Vertical gaps value.";
+ example = 5;
+ };
+
+ top = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Top gaps value.";
+ example = 5;
+ };
+
+ left = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Left gaps value.";
+ example = 5;
+ };
+
+ bottom = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Bottom gaps value.";
+ example = 5;
+ };
+
+ right = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ description = "Right gaps value.";
+ example = 5;
+ };
+
+ smartGaps = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ This option controls whether to disable all gaps (outer and inner)
+ on workspace with a single container.
+ '';
+ example = true;
+ };
+
+ smartBorders = mkOption {
+ type = types.enum [ "on" "off" "no_gaps" ];
+ default = "off";
+ description = ''
+ This option controls whether to disable container borders on
+ workspace with a single container.
+ '';
+ };
+ };
+ });
+ default = null;
+ description = if moduleName == "sway" then ''
+ Gaps related settings.
+ '' else ''
+ i3Gaps related settings. The i3-gaps package must be used for these features to work.
+ '';
+ };
+
+ terminal = mkOption {
+ type = types.str;
+ default = if moduleName == "i3" then
+ "i3-sensible-terminal"
+ else
+ "${pkgs.rxvt-unicode-unwrapped}/bin/urxvt";
+ description = "Default terminal to run.";
+ example = "alacritty";
+ };
+
+ menu = mkOption {
+ type = types.str;
+ default = if moduleName == "sway" then
+ "${pkgs.dmenu}/bin/dmenu_path | ${pkgs.dmenu}/bin/dmenu | ${pkgs.findutils}/bin/xargs swaymsg exec --"
+ else
+ "${pkgs.dmenu}/bin/dmenu_run";
+ description = "Default launcher to use.";
+ example = "bemenu-run";
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/window-managers/i3-sway/sway.nix b/infra/libkookie/home-manager/modules/services/window-managers/i3-sway/sway.nix
new file mode 100644
index 000000000000..14d82d267121
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/window-managers/i3-sway/sway.nix
@@ -0,0 +1,416 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.wayland.windowManager.sway;
+
+ commonOptions = import ./lib/options.nix {
+ inherit config lib cfg pkgs;
+ moduleName = "sway";
+ capitalModuleName = "Sway";
+ };
+
+ configModule = types.submodule {
+ options = {
+ inherit (commonOptions)
+ fonts window floating focus assigns workspaceLayout
+ workspaceAutoBackAndForth modifier keycodebindings colors bars startup
+ gaps menu terminal;
+
+ left = mkOption {
+ type = types.str;
+ default = "h";
+ description = "Home row direction key for moving left.";
+ };
+
+ down = mkOption {
+ type = types.str;
+ default = "j";
+ description = "Home row direction key for moving down.";
+ };
+
+ up = mkOption {
+ type = types.str;
+ default = "k";
+ description = "Home row direction key for moving up.";
+ };
+
+ right = mkOption {
+ type = types.str;
+ default = "l";
+ description = "Home row direction key for moving right.";
+ };
+
+ keybindings = mkOption {
+ type = types.attrsOf (types.nullOr types.str);
+ default = mapAttrs (n: mkOptionDefault) {
+ "${cfg.config.modifier}+Return" = "exec ${cfg.config.terminal}";
+ "${cfg.config.modifier}+Shift+q" = "kill";
+ "${cfg.config.modifier}+d" = "exec ${cfg.config.menu}";
+
+ "${cfg.config.modifier}+${cfg.config.left}" = "focus left";
+ "${cfg.config.modifier}+${cfg.config.down}" = "focus down";
+ "${cfg.config.modifier}+${cfg.config.up}" = "focus up";
+ "${cfg.config.modifier}+${cfg.config.right}" = "focus right";
+
+ "${cfg.config.modifier}+Left" = "focus left";
+ "${cfg.config.modifier}+Down" = "focus down";
+ "${cfg.config.modifier}+Up" = "focus up";
+ "${cfg.config.modifier}+Right" = "focus right";
+
+ "${cfg.config.modifier}+Shift+${cfg.config.left}" = "move left";
+ "${cfg.config.modifier}+Shift+${cfg.config.down}" = "move down";
+ "${cfg.config.modifier}+Shift+${cfg.config.up}" = "move up";
+ "${cfg.config.modifier}+Shift+${cfg.config.right}" = "move right";
+
+ "${cfg.config.modifier}+Shift+Left" = "move left";
+ "${cfg.config.modifier}+Shift+Down" = "move down";
+ "${cfg.config.modifier}+Shift+Up" = "move up";
+ "${cfg.config.modifier}+Shift+Right" = "move right";
+
+ "${cfg.config.modifier}+b" = "splith";
+ "${cfg.config.modifier}+v" = "splitv";
+ "${cfg.config.modifier}+f" = "fullscreen toggle";
+ "${cfg.config.modifier}+a" = "focus parent";
+
+ "${cfg.config.modifier}+s" = "layout stacking";
+ "${cfg.config.modifier}+w" = "layout tabbed";
+ "${cfg.config.modifier}+e" = "layout toggle split";
+
+ "${cfg.config.modifier}+Shift+space" = "floating toggle";
+ "${cfg.config.modifier}+space" = "focus mode_toggle";
+
+ "${cfg.config.modifier}+1" = "workspace number 1";
+ "${cfg.config.modifier}+2" = "workspace number 2";
+ "${cfg.config.modifier}+3" = "workspace number 3";
+ "${cfg.config.modifier}+4" = "workspace number 4";
+ "${cfg.config.modifier}+5" = "workspace number 5";
+ "${cfg.config.modifier}+6" = "workspace number 6";
+ "${cfg.config.modifier}+7" = "workspace number 7";
+ "${cfg.config.modifier}+8" = "workspace number 8";
+ "${cfg.config.modifier}+9" = "workspace number 9";
+
+ "${cfg.config.modifier}+Shift+1" =
+ "move container to workspace number 1";
+ "${cfg.config.modifier}+Shift+2" =
+ "move container to workspace number 2";
+ "${cfg.config.modifier}+Shift+3" =
+ "move container to workspace number 3";
+ "${cfg.config.modifier}+Shift+4" =
+ "move container to workspace number 4";
+ "${cfg.config.modifier}+Shift+5" =
+ "move container to workspace number 5";
+ "${cfg.config.modifier}+Shift+6" =
+ "move container to workspace number 6";
+ "${cfg.config.modifier}+Shift+7" =
+ "move container to workspace number 7";
+ "${cfg.config.modifier}+Shift+8" =
+ "move container to workspace number 8";
+ "${cfg.config.modifier}+Shift+9" =
+ "move container to workspace number 9";
+
+ "${cfg.config.modifier}+Shift+minus" = "move scratchpad";
+ "${cfg.config.modifier}+minus" = "scratchpad show";
+
+ "${cfg.config.modifier}+Shift+c" = "reload";
+ "${cfg.config.modifier}+Shift+e" =
+ "exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -b 'Yes, exit sway' 'swaymsg exit'";
+
+ "${cfg.config.modifier}+r" = "mode resize";
+ };
+ defaultText = "Default sway keybindings.";
+ description = ''
+ An attribute set that assigns a key press to an action using a key symbol.
+ See <link xlink:href="https://i3wm.org/docs/userguide.html#keybindings"/>.
+ </para><para>
+ Consider to use <code>lib.mkOptionDefault</code> function to extend or override
+ default keybindings instead of specifying all of them from scratch.
+ '';
+ example = literalExample ''
+ let
+ modifier = config.wayland.windowManager.sway.config.modifier;
+ in lib.mkOptionDefault {
+ "''${modifier}+Return" = "exec ${cfg.config.terminal}";
+ "''${modifier}+Shift+q" = "kill";
+ "''${modifier}+d" = "exec ${cfg.config.menu}";
+ }
+ '';
+ };
+
+ bindkeysToCode = mkOption {
+ type = types.bool;
+ default = false;
+ example = true;
+ description = ''
+ Whether to make use of <option>--to-code</option> in keybindings.
+ '';
+ };
+
+ input = mkOption {
+ type = types.attrsOf (types.attrsOf types.str);
+ default = { };
+ example = { "*" = { xkb_variant = "dvorak"; }; };
+ description = ''
+ An attribute set that defines input modules. See man sway_input for options.
+ '';
+ };
+
+ output = mkOption {
+ type = types.attrsOf (types.attrsOf types.str);
+ default = { };
+ example = { "HDMI-A-2" = { bg = "~/path/to/background.png fill"; }; };
+ description = ''
+ An attribute set that defines output modules. See man sway_output for options.
+ '';
+ };
+
+ modes = mkOption {
+ type = types.attrsOf (types.attrsOf types.str);
+ default = {
+ resize = {
+ "${cfg.config.left}" = "resize shrink width 10 px";
+ "${cfg.config.down}" = "resize grow height 10 px";
+ "${cfg.config.up}" = "resize shrink height 10 px";
+ "${cfg.config.right}" = "resize grow width 10 px";
+ "Left" = "resize shrink width 10 px";
+ "Down" = "resize grow height 10 px";
+ "Up" = "resize shrink height 10 px";
+ "Right" = "resize grow width 10 px";
+ "Escape" = "mode default";
+ "Return" = "mode default";
+ };
+ };
+ description = ''
+ An attribute set that defines binding modes and keybindings
+ inside them
+
+ Only basic keybinding is supported (bindsym keycomb action),
+ for more advanced setup use 'sway.extraConfig'.
+ '';
+ };
+ };
+ };
+
+ wrapperOptions = types.submodule {
+ options = let
+ mkWrapperFeature = default: description:
+ mkOption {
+ type = types.bool;
+ inherit default;
+ example = !default;
+ description = "Whether to make use of the ${description}";
+ };
+ in {
+ base = mkWrapperFeature true ''
+ base wrapper to execute extra session commands and prepend a
+ dbus-run-session to the sway command.
+ '';
+ gtk = mkWrapperFeature false ''
+ wrapGAppsHook wrapper to execute sway with required environment
+ variables for GTK applications.
+ '';
+ };
+ };
+
+ commonFunctions = import ./lib/functions.nix {
+ inherit cfg lib;
+ moduleName = "sway";
+ };
+
+ inherit (commonFunctions)
+ keybindingsStr keycodebindingsStr modeStr assignStr barStr gapsStr
+ floatingCriteriaStr windowCommandsStr colorSetStr;
+
+ startupEntryStr = { command, always, ... }: ''
+ ${if always then "exec_always" else "exec"} ${command}
+ '';
+
+ inputStr = name: attrs: ''
+ input "${name}" {
+ ${concatStringsSep "\n"
+ (mapAttrsToList (name: value: "${name} ${value}") attrs)}
+ }
+ '';
+
+ outputStr = name: attrs: ''
+ output "${name}" {
+ ${concatStringsSep "\n"
+ (mapAttrsToList (name: value: "${name} ${value}") attrs)}
+ }
+ '';
+
+ configFile = pkgs.writeText "sway.conf" ((if cfg.config != null then
+ with cfg.config; ''
+ font pango:${concatStringsSep ", " fonts}
+ floating_modifier ${floating.modifier}
+ default_border ${if window.titlebar then "normal" else "pixel"} ${
+ toString window.border
+ }
+ default_floating_border ${
+ if floating.titlebar then "normal" else "pixel"
+ } ${toString floating.border}
+ hide_edge_borders ${window.hideEdgeBorders}
+ focus_wrapping ${if focus.forceWrapping then "yes" else "no"}
+ focus_follows_mouse ${focus.followMouse}
+ focus_on_window_activation ${focus.newWindow}
+ mouse_warping ${if focus.mouseWarping then "output" else "none"}
+ workspace_layout ${workspaceLayout}
+ workspace_auto_back_and_forth ${
+ if workspaceAutoBackAndForth then "yes" else "no"
+ }
+
+ client.focused ${colorSetStr colors.focused}
+ client.focused_inactive ${colorSetStr colors.focusedInactive}
+ client.unfocused ${colorSetStr colors.unfocused}
+ client.urgent ${colorSetStr colors.urgent}
+ client.placeholder ${colorSetStr colors.placeholder}
+ client.background ${colors.background}
+
+ ${keybindingsStr {
+ inherit keybindings;
+ bindsymArgs =
+ lib.optionalString (cfg.config.bindkeysToCode) "--to-code";
+ }}
+ ${keycodebindingsStr keycodebindings}
+ ${concatStringsSep "\n" (mapAttrsToList inputStr input)}
+ ${concatStringsSep "\n" (mapAttrsToList outputStr output)}
+ ${concatStringsSep "\n" (mapAttrsToList modeStr modes)}
+ ${concatStringsSep "\n" (mapAttrsToList assignStr assigns)}
+ ${concatStringsSep "\n" (map barStr bars)}
+ ${optionalString (gaps != null) gapsStr}
+ ${concatStringsSep "\n" (map floatingCriteriaStr floating.criteria)}
+ ${concatStringsSep "\n" (map windowCommandsStr window.commands)}
+ ${concatStringsSep "\n" (map startupEntryStr startup)}
+ ''
+ else
+ "") + "\n" + (if cfg.systemdIntegration then ''
+ exec "systemctl --user import-environment; systemctl --user start sway-session.target"
+ '' else
+ "") + cfg.extraConfig);
+
+ defaultSwayPackage = pkgs.sway.override {
+ extraSessionCommands = cfg.extraSessionCommands;
+ extraOptions = cfg.extraOptions;
+ withBaseWrapper = cfg.wrapperFeatures.base;
+ withGtkWrapper = cfg.wrapperFeatures.gtk;
+ };
+
+in {
+ meta.maintainers = [ maintainers.alexarice ];
+
+ options.wayland.windowManager.sway = {
+ enable = mkEnableOption "sway wayland compositor";
+
+ package = mkOption {
+ type = with types; nullOr package;
+ default = defaultSwayPackage;
+ defaultText = literalExample "${pkgs.sway}";
+ description = ''
+ Sway package to use. Will override the options
+ 'wrapperFeatures', 'extraSessionCommands', and 'extraOptions'.
+ Set to <code>null</code> to not add any Sway package to your
+ path. This should be done if you want to use the NixOS Sway
+ module to install Sway.
+ '';
+ };
+
+ systemdIntegration = mkOption {
+ type = types.bool;
+ default = pkgs.stdenv.isLinux;
+ example = false;
+ description = ''
+ Whether to enable <filename>sway-session.target</filename> on
+ sway startup. This links to
+ <filename>graphical-session.target</filename>.
+ '';
+ };
+
+ xwayland = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Enable xwayland, which is needed for the default configuration of sway.
+ '';
+ };
+
+ wrapperFeatures = mkOption {
+ type = wrapperOptions;
+ default = { };
+ example = { gtk = true; };
+ description = ''
+ Attribute set of features to enable in the wrapper.
+ '';
+ };
+
+ extraSessionCommands = mkOption {
+ type = types.lines;
+ default = "";
+ example = ''
+ export SDL_VIDEODRIVER=wayland
+ # needs qt5.qtwayland in systemPackages
+ export QT_QPA_PLATFORM=wayland
+ export QT_WAYLAND_DISABLE_WINDOWDECORATION="1"
+ # Fix for some Java AWT applications (e.g. Android Studio),
+ # use this if they aren't displayed properly:
+ export _JAVA_AWT_WM_NONREPARENTING=1
+ '';
+ description = ''
+ Shell commands executed just before Sway is started.
+ '';
+ };
+
+ extraOptions = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [
+ "--verbose"
+ "--debug"
+ "--unsupported-gpu"
+ "--my-next-gpu-wont-be-nvidia"
+ ];
+ description = ''
+ Command line arguments passed to launch Sway. Please DO NOT report
+ issues if you use an unsupported GPU (proprietary drivers).
+ '';
+ };
+
+ config = mkOption {
+ type = types.nullOr configModule;
+ default = { };
+ description = "Sway configuration options.";
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description =
+ "Extra configuration lines to add to ~/.config/sway/config.";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = optional (cfg.package != null) cfg.package
+ ++ optional cfg.xwayland pkgs.xwayland;
+ xdg.configFile."sway/config" = {
+ source = configFile;
+ onChange = ''
+ swaySocket=''${XDG_RUNTIME_DIR:-/run/user/$UID}/sway-ipc.$UID.$(${pkgs.procps}/bin/pgrep -x sway || ${pkgs.coreutils}/bin/true).sock
+ if [ -S $swaySocket ]; then
+ echo "Reloading sway"
+ $DRY_RUN_CMD ${pkgs.sway}/bin/swaymsg -s $swaySocket reload
+ fi
+ '';
+ };
+ systemd.user.targets.sway-session = mkIf cfg.systemdIntegration {
+ Unit = {
+ Description = "sway compositor session";
+ Documentation = [ "man:systemd.special(7)" ];
+ BindsTo = [ "graphical-session.target" ];
+ Wants = [ "graphical-session-pre.target" ];
+ After = [ "graphical-session-pre.target" ];
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/window-managers/xmonad.nix b/infra/libkookie/home-manager/modules/services/window-managers/xmonad.nix
new file mode 100644
index 000000000000..7be03874a89e
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/window-managers/xmonad.nix
@@ -0,0 +1,101 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.xsession.windowManager.xmonad;
+
+ xmonad = pkgs.xmonad-with-packages.override {
+ ghcWithPackages = cfg.haskellPackages.ghcWithPackages;
+ packages = self:
+ cfg.extraPackages self ++ optionals cfg.enableContribAndExtras [
+ self.xmonad-contrib
+ self.xmonad-extras
+ ];
+ };
+
+in {
+ options = {
+ xsession.windowManager.xmonad = {
+ enable = mkEnableOption "xmonad window manager";
+
+ haskellPackages = mkOption {
+ default = pkgs.haskellPackages;
+ defaultText = literalExample "pkgs.haskellPackages";
+ example = literalExample "pkgs.haskell.packages.ghc784";
+ description = ''
+ The <varname>haskellPackages</varname> used to build xmonad
+ and other packages. This can be used to change the GHC
+ version used to build xmonad and the packages listed in
+ <varname>extraPackages</varname>.
+ '';
+ };
+
+ extraPackages = mkOption {
+ default = self: [ ];
+ defaultText = "self: []";
+ example = literalExample ''
+ haskellPackages: [
+ haskellPackages.xmonad-contrib
+ haskellPackages.monad-logger
+ ]
+ '';
+ description = ''
+ Extra packages available to GHC when rebuilding xmonad. The
+ value must be a function which receives the attribute set
+ defined in <varname>haskellPackages</varname> as the sole
+ argument.
+ '';
+ };
+
+ enableContribAndExtras = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Enable xmonad-{contrib,extras} in xmonad.";
+ };
+
+ config = mkOption {
+ type = types.nullOr types.path;
+ default = null;
+ example = literalExample ''
+ pkgs.writeText "xmonad.hs" '''
+ import XMonad
+ main = xmonad defaultConfig
+ { terminal = "urxvt"
+ , modMask = mod4Mask
+ , borderWidth = 3
+ }
+ '''
+ '';
+ description = ''
+ The configuration file to be used for xmonad. This must be
+ an absolute path or <literal>null</literal> in which case
+ <filename>~/.xmonad/xmonad.hs</filename> will not be managed
+ by Home Manager.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable (mkMerge [
+ {
+ home.packages = [ (lowPrio xmonad) ];
+ xsession.windowManager.command = "${xmonad}/bin/xmonad";
+ }
+
+ (mkIf (cfg.config != null) {
+ home.file.".xmonad/xmonad.hs".source = cfg.config;
+ home.file.".xmonad/xmonad.hs".onChange = ''
+ echo "Recompiling xmonad"
+ $DRY_RUN_CMD ${config.xsession.windowManager.command} --recompile
+
+ # Attempt to restart xmonad if X is running.
+ if [[ -v DISPLAY ]] ; then
+ echo "Restarting xmonad"
+ $DRY_RUN_CMD ${config.xsession.windowManager.command} --restart
+ fi
+ '';
+ })
+ ]);
+}
diff --git a/infra/libkookie/home-manager/modules/services/wlsunset.nix b/infra/libkookie/home-manager/modules/services/wlsunset.nix
new file mode 100644
index 000000000000..084dbdb7c3d6
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/wlsunset.nix
@@ -0,0 +1,97 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.wlsunset;
+
+in {
+ meta.maintainers = [ maintainers.matrss ];
+
+ options.services.wlsunset = {
+ enable = mkEnableOption "Whether to enable wlsunset.";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.wlsunset;
+ defaultText = "pkgs.wlsunset";
+ description = ''
+ wlsunset derivation to use.
+ '';
+ };
+
+ latitude = mkOption {
+ type = types.str;
+ description = ''
+ Your current latitude, between <literal>-90.0</literal> and
+ <literal>90.0</literal>.
+ '';
+ };
+
+ longitude = mkOption {
+ type = types.str;
+ description = ''
+ Your current longitude, between <literal>-180.0</literal> and
+ <literal>180.0</literal>.
+ '';
+ };
+
+ temperature = {
+ day = mkOption {
+ type = types.int;
+ default = 6500;
+ description = ''
+ Colour temperature to use during the day, in Kelvin (K).
+ This value must be greater than <literal>temperature.night</literal>.
+ '';
+ };
+
+ night = mkOption {
+ type = types.int;
+ default = 4000;
+ description = ''
+ Colour temperature to use during the night, in Kelvin (K).
+ This value must be smaller than <literal>temperature.day</literal>.
+ '';
+ };
+ };
+
+ gamma = mkOption {
+ type = types.str;
+ default = "1.0";
+ description = ''
+ Gamma value to use.
+ '';
+ };
+
+ systemdTarget = mkOption {
+ type = types.str;
+ default = "graphical-session.target";
+ description = ''
+ Systemd target to bind to.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.wlsunset = {
+ Unit = {
+ Description = "Day/night gamma adjustments for Wayland compositors.";
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ ExecStart = let
+ args = [
+ "-l ${cfg.latitude}"
+ "-L ${cfg.longitude}"
+ "-t ${toString cfg.temperature.night}"
+ "-T ${toString cfg.temperature.day}"
+ "-g ${cfg.gamma}"
+ ];
+ in "${cfg.package}/bin/wlsunset ${concatStringsSep " " args}";
+ };
+
+ Install = { WantedBy = [ cfg.systemdTarget ]; };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/xcape.nix b/infra/libkookie/home-manager/modules/services/xcape.nix
new file mode 100644
index 000000000000..f4f77caa3317
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/xcape.nix
@@ -0,0 +1,76 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.xcape;
+
+in {
+ meta.maintainers = [ maintainers.nickhu ];
+
+ options = {
+ services.xcape = {
+ enable = mkEnableOption "xcape";
+
+ timeout = mkOption {
+ type = types.nullOr types.int;
+ default = null;
+ example = 500;
+ description = ''
+ If you hold a key longer than this timeout, xcape will not
+ generate a key event. Default is 500 ms.
+ '';
+ };
+
+ mapExpression = mkOption {
+ type = types.attrsOf types.str;
+ default = { };
+ example = {
+ Shift_L = "Escape";
+ Control_L = "Control_L|O";
+ };
+ description = ''
+ The value has the grammar <literal>Key[|OtherKey]</literal>.
+ </para>
+ <para>
+ The list of key names is found in the header file
+ <filename>X11/keysymdef.h</filename> (remove the
+ <literal>XK_</literal> prefix). Note that due to limitations
+ of X11 shifted keys must be specified as a shift key
+ followed by the key to be pressed rather than the actual
+ name of the character. For example to generate "{" the
+ expression <literal>Shift_L|bracketleft</literal> could be
+ used (assuming that you have a key with "{" above "[").
+ </para>
+ <para>
+ You can also specify keys in decimal (prefix #), octal (#0),
+ or hexadecimal (#0x). They will be interpreted as keycodes
+ unless no corresponding key name is found.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.xcape = {
+ Unit = {
+ Description = "xcape";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Service = {
+ Type = "forking";
+ ExecStart = "${pkgs.xcape}/bin/xcape"
+ + optionalString (cfg.timeout != null) " -t ${toString cfg.timeout}"
+ + optionalString (cfg.mapExpression != { }) " -e '${
+ builtins.concatStringsSep ";"
+ (attrsets.mapAttrsToList (n: v: "${n}=${v}") cfg.mapExpression)
+ }'";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/xembed-sni-proxy.nix b/infra/libkookie/home-manager/modules/services/xembed-sni-proxy.nix
new file mode 100644
index 000000000000..ff63d108b774
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/xembed-sni-proxy.nix
@@ -0,0 +1,45 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.xembed-sni-proxy;
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ services.xembed-sni-proxy = {
+ enable = mkEnableOption "XEmbed SNI Proxy";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.plasma-workspace;
+ defaultText = literalExample "pkgs.plasma-workspace";
+ description = ''
+ Package containing the <command>xembedsniproxy</command>
+ program.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.user.services.xembed-sni-proxy = {
+ Unit = {
+ Description = "XEmbed SNI Proxy";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ Environment = "PATH=${config.home.profileDirectory}/bin";
+ ExecStart = "${cfg.package}/bin/xembedsniproxy";
+ Restart = "on-abort";
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/xscreensaver.nix b/infra/libkookie/home-manager/modules/services/xscreensaver.nix
new file mode 100644
index 000000000000..ac6194e70c13
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/xscreensaver.nix
@@ -0,0 +1,56 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.xscreensaver;
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ services.xscreensaver = {
+ enable = mkEnableOption "XScreenSaver";
+
+ settings = mkOption {
+ type = with types; attrsOf (either bool (either int str));
+ default = { };
+ example = {
+ mode = "blank";
+ lock = false;
+ fadeTicks = 20;
+ };
+ description = ''
+ The settings to use for XScreenSaver.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ # To make the xscreensaver-command tool available.
+ home.packages = [ pkgs.xscreensaver ];
+
+ xresources.properties =
+ mapAttrs' (n: nameValuePair "xscreensaver.${n}") cfg.settings;
+
+ systemd.user.services.xscreensaver = {
+ Unit = {
+ Description = "XScreenSaver";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+
+ # Make sure the service is restarted if the settings change.
+ X-Restart-Triggers =
+ [ (builtins.hashString "md5" (builtins.toJSON cfg.settings)) ];
+ };
+
+ Service = {
+ ExecStart = "${pkgs.xscreensaver}/bin/xscreensaver -no-splash";
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/services/xsuspender.nix b/infra/libkookie/home-manager/modules/services/xsuspender.nix
new file mode 100644
index 000000000000..7d855f05d51f
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/services/xsuspender.nix
@@ -0,0 +1,195 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.xsuspender;
+
+ iniFormat = pkgs.formats.ini { };
+
+ xsuspenderOptions = types.submodule {
+ options = {
+ matchWmClassContains = mkOption {
+ description = "Match windows that wm class contains string.";
+ type = types.nullOr types.str;
+ default = null;
+ };
+
+ matchWmClassGroupContains = mkOption {
+ description = "Match windows where wm class group contains string.";
+ type = types.nullOr types.str;
+ default = null;
+ };
+
+ matchWmNameContains = mkOption {
+ description = "Match windows where wm name contains string.";
+ type = types.nullOr types.str;
+ default = null;
+ };
+
+ suspendDelay = mkOption {
+ description = "Initial suspend delay in seconds.";
+ type = types.int;
+ default = 5;
+ };
+
+ resumeEvery = mkOption {
+ description = "Resume interval in seconds.";
+ type = types.int;
+ default = 50;
+ };
+
+ resumeFor = mkOption {
+ description = "Resume duration in seconds.";
+ type = types.int;
+ default = 5;
+ };
+
+ execSuspend = mkOption {
+ description = ''
+ Before suspending, execute this shell script. If it fails,
+ abort suspension.
+ '';
+ type = types.nullOr types.str;
+ default = null;
+ example = ''echo "suspending window $XID of process $PID"'';
+ };
+
+ execResume = mkOption {
+ description = ''
+ Before resuming, execute this shell script. Resume the
+ process regardless script failure.
+ '';
+ type = types.nullOr types.str;
+ default = null;
+ example = "echo resuming ...";
+ };
+
+ sendSignals = mkOption {
+ description = ''
+ Whether to send SIGSTOP / SIGCONT signals or not.
+ If false just the exec scripts are run.
+ '';
+ type = types.bool;
+ default = true;
+ };
+
+ suspendSubtreePattern = mkOption {
+ description =
+ "Also suspend descendant processes that match this regex.";
+ type = types.nullOr types.str;
+ default = null;
+ };
+
+ onlyOnBattery = mkOption {
+ description = "Whether to enable process suspend only on battery.";
+ type = types.bool;
+ default = false;
+ };
+
+ autoSuspendOnBattery = mkOption {
+ description = ''
+ Whether to auto-apply rules when switching to battery
+ power even if the window(s) didn't just lose focus.
+ '';
+ type = types.bool;
+ default = true;
+ };
+
+ downclockOnBattery = mkOption {
+ description = ''
+ Limit CPU consumption for this factor when on battery power.
+ Value 1 means 50% decrease, 2 means 66%, 3 means 75% etc.
+ '';
+ type = types.int;
+ default = 0;
+ };
+ };
+ };
+
+in {
+ meta.maintainers = [ maintainers.offline ];
+
+ options = {
+ services.xsuspender = {
+ enable = mkEnableOption "XSuspender";
+
+ defaults = mkOption {
+ description = "XSuspender defaults.";
+ type = xsuspenderOptions;
+ default = { };
+ };
+
+ rules = mkOption {
+ description = "Attribute set of XSuspender rules.";
+ type = types.attrsOf xsuspenderOptions;
+ default = { };
+ example = {
+ Chromium = {
+ suspendDelay = 10;
+ matchWmClassContains = "chromium-browser";
+ suspendSubtreePattern = "chromium";
+ };
+ };
+ };
+
+ debug = mkOption {
+ description = "Whether to enable debug output.";
+ type = types.bool;
+ default = false;
+ };
+
+ iniContent = mkOption {
+ type = iniFormat.type;
+ internal = true;
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services.xsuspender.iniContent = let
+ mkSection = values:
+ filterAttrs (_: v: v != null) {
+ match_wm_class_contains = values.matchWmClassContains;
+ match_wm_class_group_contains = values.matchWmClassGroupContains;
+ match_wm_name_contains = values.matchWmNameContains;
+ suspend_delay = values.suspendDelay;
+ resume_every = values.resumeEvery;
+ resume_for = values.resumeFor;
+ exec_suspend = values.execSuspend;
+ exec_resume = values.execResume;
+ send_signals = values.sendSignals;
+ suspend_subtree_pattern = values.suspendSubtreePattern;
+ only_on_battery = values.onlyOnBattery;
+ auto_suspend_on_battery = values.autoSuspendOnBattery;
+ downclock_on_battery = values.downclockOnBattery;
+ };
+ in {
+ Default = mkSection cfg.defaults;
+ } // mapAttrs (_: mkSection) cfg.rules;
+
+ # To make the xsuspender tool available.
+ home.packages = [ pkgs.xsuspender ];
+
+ xdg.configFile."xsuspender.conf".source =
+ iniFormat.generate "xsuspender.conf" cfg.iniContent;
+
+ systemd.user.services.xsuspender = {
+ Unit = {
+ Description = "XSuspender";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ X-Restart-Triggers =
+ [ "${config.xdg.configFile."xsuspender.conf".source}" ];
+ };
+
+ Service = {
+ ExecStart = "${pkgs.xsuspender}/bin/xsuspender";
+ Environment = mkIf cfg.debug [ "G_MESSAGE_DEBUG=all" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/systemd-activate.rb b/infra/libkookie/home-manager/modules/systemd-activate.rb
new file mode 100644
index 000000000000..31d06d8fc199
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/systemd-activate.rb
@@ -0,0 +1,216 @@
+require 'set'
+require 'open3'
+
+@dry_run = ENV['DRY_RUN']
+@verbose = ENV['VERBOSE']
+
+UnitsDir = 'home-files/.config/systemd/user'
+
+# 1. Stop all services from the old generation that are not present in the new generation.
+# 2. Ensure all services from the new generation that are wanted by active targets are running:
+# - Start services that are not already running.
+# - Restart services whose unit config files have changed between generations.
+# 3. If any services were (re)started, wait 'start_timeout_ms' and report services
+# that failed to start. This helps debugging quickly failing services.
+#
+# Whenever service failures are detected, show the output of
+# 'systemd --user status' for the affected services.
+#
+def setup_services(old_gen_path, new_gen_path, start_timeout_ms_string)
+ start_timeout_ms = start_timeout_ms_string.to_i
+
+ old_units_path = File.join(old_gen_path, UnitsDir) unless old_gen_path.empty?
+ new_units_path = File.join(new_gen_path, UnitsDir)
+
+ old_services = get_services(old_units_path)
+ new_services = get_services(new_units_path)
+
+ exit if old_services.empty? && new_services.empty?
+
+ all_services = get_active_targets_units(new_units_path)
+ maybe_changed = all_services & old_services
+ changed_services = get_changed_services(old_units_path, new_units_path, maybe_changed)
+ unchanged_oneshots = get_oneshot_services(maybe_changed - changed_services)
+
+ # These services should be running when this script is finished
+ services_to_run = all_services - unchanged_oneshots
+
+ # Only stop active services, otherwise we might get a 'service not loaded' error
+ # for inactive services that were removed in the current generation.
+ to_stop = get_active_units(old_services - new_services)
+ to_restart = changed_services
+ to_start = get_inactive_units(services_to_run - to_restart)
+
+ raise "daemon-reload failed" unless run_cmd('systemctl', '--user', 'daemon-reload')
+
+ # Exclude units that shouldn't be (re)started or stopped
+ no_manual_start, no_manual_stop, no_restart = get_restricted_units(to_stop + to_restart + to_start)
+ notify_skipped_units(to_restart & no_restart)
+ to_stop -= no_manual_stop
+ to_restart -= no_manual_stop + no_manual_start + no_restart
+ to_start -= no_manual_start
+
+ if to_stop.empty? && to_start.empty? && to_restart.empty?
+ print_service_msg("All services are already running", services_to_run)
+ else
+ puts "Setting up services" if @verbose
+ systemctl_action('stop', to_stop)
+ systemctl_action('start', to_start)
+ systemctl_action('restart', to_restart)
+ started_services = to_start + to_restart
+ if start_timeout_ms > 0 && !started_services.empty? && !@dry_run
+ failed = wait_and_get_failed_services(started_services, start_timeout_ms)
+ if failed.empty?
+ print_service_msg("All services are running", services_to_run)
+ else
+ puts
+ puts "Error. These services failed to start:", failed
+ show_failed_services_status(failed)
+ exit 1
+ end
+ end
+ end
+end
+
+def get_services(dir)
+ services = get_service_files(dir) if dir && Dir.exists?(dir)
+ Set.new(services)
+end
+
+def get_service_files(dir)
+ Dir.chdir(dir) { Dir['*[^@].{service,socket,timer}'] }
+end
+
+def get_changed_services(dir_a, dir_b, services)
+ services.select do |service|
+ a = File.join(dir_a, service)
+ b = File.join(dir_b, service)
+ (File.size(a) != File.size(b)) || (File.read(a) != File.read(b))
+ end
+end
+
+TargetDirRegexp = /^(.*\.target)\.wants$/
+
+# @return all units wanted by active targets
+def get_active_targets_units(units_dir)
+ return Set.new unless Dir.exists?(units_dir)
+ targets = Dir.entries(units_dir).map { |entry| entry[TargetDirRegexp, 1] }.compact
+ active_targets = get_active_units(targets)
+ active_units = active_targets.map do |target|
+ get_service_files(File.join(units_dir, "#{target}.wants"))
+ end.flatten
+ Set.new(active_units)
+end
+
+# @return true on success
+def run_cmd(*cmd)
+ print_cmd cmd
+ @dry_run || system(*cmd)
+end
+
+def systemctl_action(cmd, services)
+ return if services.empty?
+
+ verb = (cmd == 'stop') ? 'Stopping' : "#{cmd.capitalize}ing"
+ puts "#{verb}: #{services.join(' ')}"
+
+ cmd = ['systemctl', '--user', cmd, *services]
+ if @dry_run
+ puts cmd.join(' ')
+ return
+ end
+
+ output, status = Open3.capture2e(*cmd)
+ print output
+ # Show status for failed services
+ unless status.success?
+ # Due to a bug in systemd, the '--user' argument is not always provided
+ output.scan(/systemctl (?:--user )?(status .*?)['"]/).flatten.each do |status_cmd|
+ puts
+ run_cmd("systemctl --user #{status_cmd}")
+ end
+ exit 1
+ end
+end
+
+def systemctl(*cmd)
+ output, _ = Open3.capture2('systemctl', '--user', *cmd)
+ output
+end
+
+def print_cmd(cmd)
+ puts [*cmd].join(' ') if @verbose || @dry_run
+end
+
+def get_active_units(units)
+ filter_units(units) { |state| state == 'active' }
+end
+
+def get_inactive_units(units)
+ filter_units(units) { |state| state != 'active' }
+end
+
+def get_failed_units(units)
+ filter_units(units) { |state| state == 'failed' }
+end
+
+def filter_units(units)
+ return [] if units.empty?
+ states = systemctl('is-active', *units).split
+ units.select.with_index { |_, i| yield states[i] }
+end
+
+def get_oneshot_services(units)
+ return [] if units.empty?
+ types = systemctl('show', '-p', 'Type', *units).split
+ units.select.with_index do |_, i|
+ types[i] == 'Type=oneshot'
+ end
+end
+
+def get_restricted_units(units)
+ infos = systemctl('show', '-p', 'RefuseManualStart', '-p', 'RefuseManualStop', *units)
+ .split("\n\n")
+ no_manual_start = []
+ no_manual_stop = []
+ infos.zip(units).each do |info, unit|
+ no_start, no_stop = info.split("\n")
+ no_manual_start << unit if no_start.end_with?('yes')
+ no_manual_stop << unit if no_stop.end_with?('yes')
+ end
+ # Get units that should not be restarted even if a change has been detected.
+ no_restart_regexp = /^\s*X-RestartIfChanged\s*=\s*false\b/
+ no_restart = units.select { |unit| systemctl('cat', unit) =~ no_restart_regexp }
+ [no_manual_start, no_manual_stop, no_restart]
+end
+
+def wait_and_get_failed_services(services, start_timeout_ms)
+ puts "Waiting #{start_timeout_ms} ms for services to fail"
+ # Force the previous message to always be visible before sleeping
+ STDOUT.flush
+ sleep(start_timeout_ms / 1000.0)
+ get_failed_units(services)
+end
+
+def show_failed_services_status(services)
+ puts
+ services.each do |service|
+ run_cmd('systemctl', '--user', 'status', service)
+ puts
+ end
+end
+
+def print_service_msg(msg, services)
+ return if services.empty?
+ if @verbose
+ puts "#{msg}:", services.to_a
+ else
+ puts msg
+ end
+end
+
+def notify_skipped_units(no_restart)
+ puts "Not restarting: #{no_restart.join(' ')}" unless no_restart.empty?
+end
+
+setup_services(*ARGV)
diff --git a/infra/libkookie/home-manager/modules/systemd-activate.sh b/infra/libkookie/home-manager/modules/systemd-activate.sh
new file mode 100644
index 000000000000..1c464693cfc3
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/systemd-activate.sh
@@ -0,0 +1,114 @@
+#!/usr/bin/env bash
+
+function isStartable() {
+ local service="$1"
+ [[ $(systemctl --user show -p RefuseManualStart "$service") == *=no ]]
+}
+
+function isStoppable() {
+ if [[ -v oldGenPath ]] ; then
+ local service="$1"
+ [[ $(systemctl --user show -p RefuseManualStop "$service") == *=no ]]
+ fi
+}
+
+function systemdPostReload() {
+ local workDir
+ workDir="$(mktemp -d)"
+
+ if [[ -v oldGenPath ]] ; then
+ local oldUserServicePath="$oldGenPath/home-files/.config/systemd/user"
+ fi
+
+ local newUserServicePath="$newGenPath/home-files/.config/systemd/user"
+ local oldServiceFiles="$workDir/old-files"
+ local newServiceFiles="$workDir/new-files"
+ local servicesDiffFile="$workDir/diff-files"
+
+ if [[ ! (-v oldUserServicePath && -d "$oldUserServicePath") \
+ && ! -d "$newUserServicePath" ]]; then
+ return
+ fi
+
+ if [[ ! (-v oldUserServicePath && -d "$oldUserServicePath") ]]; then
+ touch "$oldServiceFiles"
+ else
+ find "$oldUserServicePath" \
+ -maxdepth 1 -name '*.service' -exec basename '{}' ';' \
+ | sort \
+ > "$oldServiceFiles"
+ fi
+
+ if [[ ! -d "$newUserServicePath" ]]; then
+ touch "$newServiceFiles"
+ else
+ find "$newUserServicePath" \
+ -maxdepth 1 -name '*.service' -exec basename '{}' ';' \
+ | sort \
+ > "$newServiceFiles"
+ fi
+
+ diff \
+ --new-line-format='+%L' \
+ --old-line-format='-%L' \
+ --unchanged-line-format=' %L' \
+ "$oldServiceFiles" "$newServiceFiles" \
+ > "$servicesDiffFile" || true
+
+ local -a maybeRestart=( $(grep '^ ' "$servicesDiffFile" | cut -c2-) )
+ local -a maybeStop=( $(grep '^-' "$servicesDiffFile" | cut -c2-) )
+ local -a maybeStart=( $(grep '^+' "$servicesDiffFile" | cut -c2-) )
+ local -a toRestart=( )
+ local -a toStop=( )
+ local -a toStart=( )
+
+ for f in "${maybeRestart[@]}" ; do
+ if isStoppable "$f" \
+ && isStartable "$f" \
+ && systemctl --quiet --user is-active "$f" \
+ && ! cmp --quiet \
+ "$oldUserServicePath/$f" \
+ "$newUserServicePath/$f" ; then
+ toRestart+=("$f")
+ fi
+ done
+
+ for f in "${maybeStop[@]}" ; do
+ if isStoppable "$f" ; then
+ toStop+=("$f")
+ fi
+ done
+
+ for f in "${maybeStart[@]}" ; do
+ if isStartable "$f" ; then
+ toStart+=("$f")
+ fi
+ done
+
+ rm -r "$workDir"
+
+ local sugg=""
+
+ if [[ -n "${toRestart[@]}" ]] ; then
+ sugg="${sugg}systemctl --user restart ${toRestart[@]}\n"
+ fi
+
+ if [[ -n "${toStop[@]}" ]] ; then
+ sugg="${sugg}systemctl --user stop ${toStop[@]}\n"
+ fi
+
+ if [[ -n "${toStart[@]}" ]] ; then
+ sugg="${sugg}systemctl --user start ${toStart[@]}\n"
+ fi
+
+ if [[ -n "$sugg" ]] ; then
+ echo "Suggested commands:"
+ echo -n -e "$sugg"
+ fi
+}
+
+oldGenPath="$1"
+newGenPath="$2"
+
+$DRY_RUN_CMD systemctl --user daemon-reload
+systemdPostReload
diff --git a/infra/libkookie/home-manager/modules/systemd.nix b/infra/libkookie/home-manager/modules/systemd.nix
new file mode 100644
index 000000000000..e1261b7b4544
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/systemd.nix
@@ -0,0 +1,338 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.systemd.user;
+
+ enabled = cfg.services != {}
+ || cfg.slices != {}
+ || cfg.sockets != {}
+ || cfg.targets != {}
+ || cfg.timers != {}
+ || cfg.paths != {}
+ || cfg.mounts != {}
+ || cfg.sessionVariables != {};
+
+ toSystemdIni = generators.toINI {
+ listsAsDuplicateKeys = true;
+ mkKeyValue = key: value:
+ let
+ value' =
+ if isBool value then (if value then "true" else "false")
+ else toString value;
+ in
+ "${key}=${value'}";
+ };
+
+ buildService = style: name: serviceCfg:
+ let
+ filename = "${name}.${style}";
+ pathSafeName = lib.replaceChars ["@" ":" "\\" "[" "]"]
+ ["-" "-" "-" "" "" ]
+ filename;
+
+ # Needed because systemd derives unit names from the ultimate
+ # link target.
+ source = pkgs.writeTextFile {
+ name = pathSafeName;
+ text = toSystemdIni serviceCfg;
+ destination = "/${filename}";
+ } + "/${filename}";
+
+ wantedBy = target:
+ {
+ name = "systemd/user/${target}.wants/${filename}";
+ value = { inherit source; };
+ };
+ in
+ singleton {
+ name = "systemd/user/${filename}";
+ value = { inherit source; };
+ }
+ ++
+ map wantedBy (serviceCfg.Install.WantedBy or []);
+
+ buildServices = style: serviceCfgs:
+ concatLists (mapAttrsToList (buildService style) serviceCfgs);
+
+ servicesStartTimeoutMs = builtins.toString cfg.servicesStartTimeoutMs;
+
+ unitType = unitKind: with types;
+ let
+ primitive = either bool (either int str);
+ in
+ attrsOf (attrsOf (attrsOf (either primitive (listOf primitive))))
+ // {
+ description = "systemd ${unitKind} unit configuration";
+ };
+
+ unitDescription = type: ''
+ Definition of systemd per-user ${type} units. Attributes are
+ merged recursively.
+ </para><para>
+ Note that the attributes follow the capitalization and naming used
+ by systemd. More details can be found in
+ <citerefentry>
+ <refentrytitle>systemd.${type}</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry>.
+ '';
+
+ unitExample = type: literalExample ''
+ {
+ ${toLower type}-name = {
+ Unit = {
+ Description = "Example description";
+ Documentation = [ "man:example(1)" "man:example(5)" ];
+ };
+
+ ${type} = {
+ …
+ };
+ };
+ };
+ '';
+
+ sessionVariables = mkIf (cfg.sessionVariables != {}) {
+ "environment.d/10-home-manager.conf".text =
+ concatStringsSep "\n" (
+ mapAttrsToList (n: v: "${n}=${toString v}") cfg.sessionVariables
+ ) + "\n";
+ };
+
+in
+
+{
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ systemd.user = {
+ systemctlPath = mkOption {
+ default = "${pkgs.systemd}/bin/systemctl";
+ defaultText = "\${pkgs.systemd}/bin/systemctl";
+ type = types.str;
+ description = ''
+ Absolute path to the <command>systemctl</command> tool. This
+ option may need to be set if running Home Manager on a
+ non-NixOS distribution.
+ '';
+ };
+
+ services = mkOption {
+ default = {};
+ type = unitType "service";
+ description = unitDescription "service";
+ example = unitExample "Service";
+ };
+
+ slices = mkOption {
+ default = {};
+ type = unitType "slices";
+ description = unitDescription "slices";
+ example = unitExample "Slices";
+ };
+
+ sockets = mkOption {
+ default = {};
+ type = unitType "socket";
+ description = unitDescription "socket";
+ example = unitExample "Socket";
+ };
+
+ targets = mkOption {
+ default = {};
+ type = unitType "target";
+ description = unitDescription "target";
+ example = unitExample "Target";
+ };
+
+ timers = mkOption {
+ default = {};
+ type = unitType "timer";
+ description = unitDescription "timer";
+ example = unitExample "Timer";
+ };
+
+ paths = mkOption {
+ default = {};
+ type = unitType "path";
+ description = unitDescription "path";
+ example = unitExample "Path";
+ };
+
+ mounts = mkOption {
+ default = {};
+ type = unitType "mount";
+ description = unitDescription "mount";
+ example = unitExample "Mount";
+ };
+
+ startServices = mkOption {
+ default = "suggest";
+ type = with types; either bool (enum ["suggest" "legacy" "sd-switch"]);
+ apply = p:
+ if isBool p then if p then "legacy" else "suggest"
+ else p;
+ description = ''
+ Whether new or changed services that are wanted by active targets
+ should be started. Additionally, stop obsolete services from the
+ previous generation.
+ </para><para>
+ The alternatives are
+ <variablelist>
+ <varlistentry>
+ <term><literal>suggest</literal> (or <literal>false</literal>)</term>
+ <listitem><para>
+ Use a very simple shell script to print suggested
+ <command>systemctl</command> commands to run. You will have to
+ manually run those commands after the switch.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>legacy</literal> (or <literal>true</literal>)</term>
+ <listitem><para>
+ Use a Ruby script to, in a more robust fashion, determine the
+ necessary changes and automatically run the
+ <command>systemctl</command> commands.
+ </para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><literal>sd-switch</literal></term>
+ <listitem><para>
+ Use sd-switch, a third party application, to perform the service
+ updates. This tool offers more features while having a small
+ closure size. Note, it requires a fully functional user D-Bus
+ session. Once tested and deemed sufficiently robust, this will
+ become the default.
+ </para></listitem>
+ </varlistentry>
+ </variablelist>
+ '';
+ };
+
+ servicesStartTimeoutMs = mkOption {
+ default = 0;
+ type = types.ints.unsigned;
+ description = ''
+ How long to wait for started services to fail until their start is
+ considered successful. The value 0 indicates no timeout.
+ '';
+ };
+
+ sessionVariables = mkOption {
+ default = {};
+ type = with types; attrsOf (either int str);
+ example = { EDITOR = "vim"; };
+ description = ''
+ Environment variables that will be set for the user session.
+ The variable values must be as described in
+ <citerefentry>
+ <refentrytitle>environment.d</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry>.
+ '';
+ };
+ };
+ };
+
+ config = mkMerge [
+ {
+ assertions = [
+ {
+ assertion = enabled -> pkgs.stdenv.isLinux;
+ message =
+ let
+ names = concatStringsSep ", " (
+ attrNames (
+ cfg.services // cfg.slices // cfg.sockets // cfg.targets
+ // cfg.timers // cfg.paths // cfg.mounts // cfg.sessionVariables
+ )
+ );
+ in
+ "Must use Linux for modules that require systemd: " + names;
+ }
+ ];
+ }
+
+ # If we run under a Linux system we assume that systemd is
+ # available, in particular we assume that systemctl is in PATH.
+ (mkIf pkgs.stdenv.isLinux {
+ xdg.configFile = mkMerge [
+ (listToAttrs (
+ (buildServices "service" cfg.services)
+ ++
+ (buildServices "slices" cfg.slices)
+ ++
+ (buildServices "socket" cfg.sockets)
+ ++
+ (buildServices "target" cfg.targets)
+ ++
+ (buildServices "timer" cfg.timers)
+ ++
+ (buildServices "path" cfg.paths)
+ ++
+ (buildServices "mount" cfg.mounts)
+ ))
+
+ sessionVariables
+ ];
+
+ # Run systemd service reload if user is logged in. If we're
+ # running this from the NixOS module then XDG_RUNTIME_DIR is not
+ # set and systemd commands will fail. We'll therefore have to
+ # set it ourselves in that case.
+ home.activation.reloadSystemd = hm.dag.entryAfter ["linkGeneration"] (
+ let
+ cmd = {
+ suggest = ''
+ PATH=${dirOf cfg.systemctlPath}:$PATH \
+ bash ${./systemd-activate.sh} "''${oldGenPath=}" "$newGenPath"
+ '';
+ legacy = ''
+ PATH=${dirOf cfg.systemctlPath}:$PATH \
+ ${pkgs.ruby}/bin/ruby ${./systemd-activate.rb} \
+ "''${oldGenPath=}" "$newGenPath" "${servicesStartTimeoutMs}"
+ '';
+ sd-switch =
+ let
+ timeoutArg =
+ if cfg.servicesStartTimeoutMs != 0 then
+ "--timeout " + servicesStartTimeoutMs
+ else
+ "";
+ in ''
+ ${pkgs.sd-switch}/bin/sd-switch \
+ ''${DRY_RUN:+--dry-run} $VERBOSE_ARG ${timeoutArg} \
+ ''${oldGenPath:+--old-units $oldGenPath/home-files/.config/systemd/user} \
+ --new-units $newGenPath/home-files/.config/systemd/user
+ '';
+ };
+
+ ensureRuntimeDir = "XDG_RUNTIME_DIR=\${XDG_RUNTIME_DIR:-/run/user/$(id -u)}";
+
+ systemctl = "${ensureRuntimeDir} ${cfg.systemctlPath}";
+ in
+ ''
+ systemdStatus=$(${systemctl} --user is-system-running 2>&1 || true)
+
+ if [[ $systemdStatus == 'running' || $systemdStatus == 'degraded' ]]; then
+ if [[ $systemdStatus == 'degraded' ]]; then
+ warnEcho "The user systemd session is degraded:"
+ ${systemctl} --user --no-pager --state=failed
+ warnEcho "Attempting to reload services anyway..."
+ fi
+
+ ${ensureRuntimeDir} \
+ ${getAttr cfg.startServices cmd}
+ else
+ echo "User systemd daemon not running. Skipping reload."
+ fi
+
+ unset systemdStatus
+ ''
+ );
+ })
+ ];
+}
diff --git a/infra/libkookie/home-manager/modules/targets/darwin.nix b/infra/libkookie/home-manager/modules/targets/darwin.nix
new file mode 100644
index 000000000000..118321e8f117
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/targets/darwin.nix
@@ -0,0 +1,16 @@
+{ config, lib, pkgs, ... }:
+
+{
+ # Disabled for now due to conflicting behavior with nix-darwin. See
+ # https://github.com/nix-community/home-manager/issues/1341#issuecomment-687286866
+ config = lib.mkIf (false && pkgs.stdenv.hostPlatform.isDarwin) {
+ # Install MacOS applications to the user environment.
+ home.file."Applications/Home Manager Apps".source = let
+ apps = pkgs.buildEnv {
+ name = "home-manager-applications";
+ paths = config.home.packages;
+ pathsToLink = "/Applications";
+ };
+ in "${apps}/Applications";
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/targets/generic-linux.nix b/infra/libkookie/home-manager/modules/targets/generic-linux.nix
new file mode 100644
index 000000000000..f813df9c59b4
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/targets/generic-linux.nix
@@ -0,0 +1,55 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ profileDirectory = config.home.profileDirectory;
+
+in {
+ options.targets.genericLinux = {
+ enable = mkEnableOption "" // {
+ description = ''
+ Whether to enable settings that make Home Manager work better on
+ GNU/Linux distributions other than NixOS.
+ '';
+ };
+
+ extraXdgDataDirs = mkOption {
+ type = types.listOf types.str;
+ default = [ ];
+ example = [ "/usr/share" "/usr/local/share" ];
+ description = ''
+ List of directory names to add to <envar>XDG_DATA_DIRS</envar>.
+ '';
+ };
+ };
+
+ config = mkIf config.targets.genericLinux.enable {
+ home.sessionVariables = let
+ profiles =
+ [ "\${NIX_STATE_DIR:-/nix/var/nix}/profiles/default" profileDirectory ];
+ dataDirs = concatStringsSep ":"
+ (map (profile: "${profile}/share") profiles
+ ++ config.targets.genericLinux.extraXdgDataDirs);
+ in {
+ XDG_DATA_DIRS = "${dataDirs}\${XDG_DATA_DIRS:+:}$XDG_DATA_DIRS";
+ LOCALE_ARCHIVE_2_27 = "${pkgs.glibcLocales}/lib/locale/locale-archive";
+ };
+
+ home.sessionVariablesExtra = ''
+ . "${pkgs.nix}/etc/profile.d/nix.sh"
+ '';
+
+ # We need to source both nix.sh and hm-session-vars.sh as noted in
+ # https://github.com/nix-community/home-manager/pull/797#issuecomment-544783247
+ programs.bash.initExtra = ''
+ . "${pkgs.nix}/etc/profile.d/nix.sh"
+ . "${profileDirectory}/etc/profile.d/hm-session-vars.sh"
+ '';
+
+ systemd.user.sessionVariables = {
+ NIX_PATH = "$HOME/.nix-defexpr/channels\${NIX_PATH:+:}$NIX_PATH";
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/xcursor.nix b/infra/libkookie/home-manager/modules/xcursor.nix
new file mode 100644
index 000000000000..63ceef387df8
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/xcursor.nix
@@ -0,0 +1,83 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.xsession.pointerCursor;
+
+ cursorType = types.submodule {
+ options = {
+ package = mkOption {
+ type = types.package;
+ example = literalExample "pkgs.vanilla-dmz";
+ description = "Package providing the cursor theme.";
+ };
+
+ name = mkOption {
+ type = types.str;
+ example = "Vanilla-DMZ";
+ description = "The cursor name within the package.";
+ };
+
+ size = mkOption {
+ type = types.int;
+ default = 32;
+ example = 64;
+ description = "The cursor size.";
+ };
+
+ defaultCursor = mkOption {
+ type = types.str;
+ default = "left_ptr";
+ example = "X_cursor";
+ description = "The default cursor file to use within the package.";
+ };
+ };
+ };
+
+in {
+ meta.maintainers = [ maintainers.league ];
+
+ options = {
+ xsession.pointerCursor = mkOption {
+ type = types.nullOr cursorType;
+ default = null;
+ description = ''
+ The X cursor theme and settings. The package
+ <varname>xorg.xcursorthemes</varname> contains cursors named
+ whiteglass, redglass, and handhelds. The package
+ <varname>vanilla-dmz</varname> contains cursors named Vanilla-DMZ
+ and Vanilla-DMZ-AA. Note: handhelds does not seem to work at
+ custom sizes.
+ '';
+ };
+ };
+
+ config = mkIf (cfg != null) {
+
+ home.packages = [ cfg.package ];
+
+ xsession.initExtra = ''
+ ${pkgs.xorg.xsetroot}/bin/xsetroot -xcf ${cfg.package}/share/icons/${cfg.name}/cursors/${cfg.defaultCursor} ${
+ toString cfg.size
+ }
+ '';
+
+ xresources.properties = {
+ "Xcursor.theme" = cfg.name;
+ "Xcursor.size" = cfg.size;
+ };
+
+ gtk.gtk2.extraConfig = ''
+ gtk-cursor-theme-name="${cfg.name}"
+ gtk-cursor-theme-size=${toString cfg.size}
+ '';
+
+ gtk.gtk3.extraConfig = {
+ "gtk-cursor-theme-name" = cfg.name;
+ "gtk-cursor-theme-size" = cfg.size;
+ };
+
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/xresources.nix b/infra/libkookie/home-manager/modules/xresources.nix
new file mode 100644
index 000000000000..dc59e50c46ea
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/xresources.nix
@@ -0,0 +1,92 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.xresources;
+
+ formatLine = n: v:
+ let
+ formatList = x:
+ if isList x then
+ throw "can not convert 2-dimensional lists to Xresources format"
+ else
+ formatValue x;
+
+ formatValue = v:
+ if isBool v then
+ (if v then "true" else "false")
+ else if isList v then
+ concatMapStringsSep ", " formatList v
+ else
+ toString v;
+ in "${n}: ${formatValue v}";
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ xresources.properties = mkOption {
+ type = with types;
+ let
+ prim = either bool (either int str);
+ entry = either prim (listOf prim);
+ in nullOr (attrsOf entry);
+ default = null;
+ example = literalExample ''
+ {
+ "Emacs*toolBar" = 0;
+ "XTerm*faceName" = "dejavu sans mono";
+ "XTerm*charClass" = [ "37:48" "45-47:48" "58:48" "64:48" "126:48" ];
+ }
+ '';
+ description = ''
+ X server resources that should be set.
+ Booleans are formatted as "true" or "false" respectively.
+ List elements are recursively formatted as a string and joined by commas.
+ All other values are directly formatted using builtins.toString.
+ Note, that 2-dimensional lists are not supported and specifying one will throw an exception.
+ If this and all other xresources options are
+ <code>null</code>, then this feature is disabled and no
+ <filename>~/.Xresources</filename> link is produced.
+ '';
+ };
+
+ xresources.extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ example = literalExample ''
+ builtins.readFile (
+ pkgs.fetchFromGitHub {
+ owner = "solarized";
+ repo = "xresources";
+ rev = "025ceddbddf55f2eb4ab40b05889148aab9699fc";
+ sha256 = "0lxv37gmh38y9d3l8nbnsm1mskcv10g3i83j0kac0a2qmypv1k9f";
+ } + "/Xresources.dark"
+ )
+ '';
+ description = ''
+ Additional X server resources contents.
+ If this and all other xresources options are
+ <code>null</code>, then this feature is disabled and no
+ <filename>~/.Xresources</filename> link is produced.
+ '';
+ };
+ };
+
+ config = mkIf ((cfg.properties != null && cfg.properties != { })
+ || cfg.extraConfig != "") {
+ home.file.".Xresources" = {
+ text = concatStringsSep "\n" ([ ]
+ ++ optional (cfg.extraConfig != "") cfg.extraConfig
+ ++ optionals (cfg.properties != null)
+ (mapAttrsToList formatLine cfg.properties)) + "\n";
+ onChange = ''
+ if [[ -v DISPLAY ]] ; then
+ $DRY_RUN_CMD ${pkgs.xorg.xrdb}/bin/xrdb -merge $HOME/.Xresources
+ fi
+ '';
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/modules/xsession.nix b/infra/libkookie/home-manager/modules/xsession.nix
new file mode 100644
index 000000000000..d32c2849163f
--- /dev/null
+++ b/infra/libkookie/home-manager/modules/xsession.nix
@@ -0,0 +1,168 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.xsession;
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ xsession = {
+ enable = mkEnableOption "X Session";
+
+ scriptPath = mkOption {
+ type = types.str;
+ default = ".xsession";
+ example = ".xsession-hm";
+ description = ''
+ Path, relative <envar>HOME</envar>, where Home Manager
+ should write the X session script.
+ '';
+ };
+
+ windowManager.command = mkOption {
+ type = types.str;
+ example = literalExample ''
+ let
+ xmonad = pkgs.xmonad-with-packages.override {
+ packages = self: [ self.xmonad-contrib self.taffybar ];
+ };
+ in
+ "''${xmonad}/bin/xmonad";
+ '';
+ description = ''
+ Window manager start command.
+ '';
+ };
+
+ preferStatusNotifierItems = mkOption {
+ type = types.bool;
+ default = false;
+ example = true;
+ description = ''
+ Whether tray applets should prefer using the Status Notifier
+ Items (SNI) protocol, commonly called App Indicators. Note,
+ not all tray applets or status bars support SNI.
+ '';
+ };
+
+ profileExtra = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Extra shell commands to run before session start.";
+ };
+
+ initExtra = mkOption {
+ type = types.lines;
+ default = "";
+ description = "Extra shell commands to run during initialization.";
+ };
+
+ importedVariables = mkOption {
+ type = types.listOf (types.strMatching "[a-zA-Z_][a-zA-Z0-9_]*");
+ example = [ "GDK_PIXBUF_ICON_LOADER" ];
+ visible = false;
+ description = ''
+ Environment variables to import into the user systemd
+ session. The will be available for use by graphical
+ services.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ xsession.importedVariables = [
+ "DBUS_SESSION_BUS_ADDRESS"
+ "DISPLAY"
+ "SSH_AUTH_SOCK"
+ "XAUTHORITY"
+ "XDG_DATA_DIRS"
+ "XDG_RUNTIME_DIR"
+ "XDG_SESSION_ID"
+ ];
+
+ systemd.user = {
+ services = mkIf (config.home.keyboard != null) {
+ setxkbmap = {
+ Unit = {
+ Description = "Set up keyboard in X";
+ After = [ "graphical-session-pre.target" ];
+ PartOf = [ "graphical-session.target" ];
+ };
+
+ Install = { WantedBy = [ "graphical-session.target" ]; };
+
+ Service = {
+ Type = "oneshot";
+ RemainAfterExit = true;
+ ExecStart = with config.home.keyboard;
+ let
+ args = optional (layout != null) "-layout '${layout}'"
+ ++ optional (variant != null) "-variant '${variant}'"
+ ++ optional (model != null) "-model '${model}'"
+ ++ map (v: "-option '${v}'") options;
+ in "${pkgs.xorg.setxkbmap}/bin/setxkbmap ${toString args}";
+ };
+ };
+ };
+
+ # A basic graphical session target for Home Manager.
+ targets.hm-graphical-session = {
+ Unit = {
+ Description = "Home Manager X session";
+ Requires = [ "graphical-session-pre.target" ];
+ BindsTo = [ "graphical-session.target" ];
+ };
+ };
+ };
+
+ home.file.".xprofile".text = ''
+ . "${config.home.profileDirectory}/etc/profile.d/hm-session-vars.sh"
+
+ if [ -e "$HOME/.profile" ]; then
+ . "$HOME/.profile"
+ fi
+
+ # If there are any running services from a previous session.
+ # Need to run this in xprofile because the NixOS xsession
+ # script starts up graphical-session.target.
+ systemctl --user stop graphical-session.target graphical-session-pre.target
+
+ ${optionalString (cfg.importedVariables != [ ])
+ ("systemctl --user import-environment "
+ + toString (unique cfg.importedVariables))}
+
+ ${cfg.profileExtra}
+
+ export HM_XPROFILE_SOURCED=1
+ '';
+
+ home.file.${cfg.scriptPath} = {
+ executable = true;
+ text = ''
+ if [ -z "$HM_XPROFILE_SOURCED" ]; then
+ . ~/.xprofile
+ fi
+ unset HM_XPROFILE_SOURCED
+
+ systemctl --user start hm-graphical-session.target
+
+ ${cfg.initExtra}
+
+ ${cfg.windowManager.command}
+
+ systemctl --user stop graphical-session.target
+ systemctl --user stop graphical-session-pre.target
+
+ # Wait until the units actually stop.
+ while [ -n "$(systemctl --user --no-legend --state=deactivating list-units)" ]; do
+ sleep 0.5
+ done
+ '';
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/nix-darwin/default.nix b/infra/libkookie/home-manager/nix-darwin/default.nix
new file mode 100644
index 000000000000..12d02174332d
--- /dev/null
+++ b/infra/libkookie/home-manager/nix-darwin/default.nix
@@ -0,0 +1,107 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.home-manager;
+
+ extendedLib = import ../modules/lib/stdlib-extended.nix pkgs.lib;
+
+ hmModule = types.submoduleWith {
+ specialArgs = { lib = extendedLib; };
+ modules = [
+ ({ name, ... }: {
+ imports = import ../modules/modules.nix {
+ inherit pkgs;
+ lib = extendedLib;
+ useNixpkgsModule = !cfg.useGlobalPkgs;
+ };
+
+ config = {
+ submoduleSupport.enable = true;
+ submoduleSupport.externalPackageInstall = cfg.useUserPackages;
+
+ home.username = config.users.users.${name}.name;
+ home.homeDirectory = config.users.users.${name}.home;
+ };
+ })
+ ];
+ };
+
+in
+
+{
+ options = {
+ home-manager = {
+ useUserPackages = mkEnableOption ''
+ installation of user packages through the
+ <option>users.users.‹name?›.packages</option> option.
+ '';
+
+ useGlobalPkgs = mkEnableOption ''
+ using the system configuration's <literal>pkgs</literal>
+ argument in Home Manager. This disables the Home Manager
+ options <option>nixpkgs.*</option>
+ '';
+
+ backupFileExtension = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "backup";
+ description = ''
+ On activation move existing files by appending the given
+ file extension rather than exiting with an error.
+ '';
+ };
+
+ verbose = mkEnableOption "verbose output on activation";
+
+ users = mkOption {
+ type = types.attrsOf hmModule;
+ default = {};
+ description = ''
+ Per-user Home Manager configuration.
+ '';
+ };
+ };
+ };
+
+ config = mkIf (cfg.users != {}) {
+ warnings =
+ flatten (flip mapAttrsToList cfg.users (user: config:
+ flip map config.warnings (warning:
+ "${user} profile: ${warning}"
+ )
+ ));
+
+ assertions =
+ flatten (flip mapAttrsToList cfg.users (user: config:
+ flip map config.assertions (assertion:
+ {
+ inherit (assertion) assertion;
+ message = "${user} profile: ${assertion.message}";
+ }
+ )
+ ));
+
+ users.users = mkIf cfg.useUserPackages (
+ mapAttrs (username: usercfg: {
+ packages = [ usercfg.home.path ];
+ }) cfg.users
+ );
+
+ environment.pathsToLink = mkIf cfg.useUserPackages [ "/etc/profile.d" ];
+
+ system.activationScripts.postActivation.text =
+ concatStringsSep "\n" (mapAttrsToList (username: usercfg: ''
+ echo Activating home-manager configuration for ${username}
+ sudo -u ${username} -i ${pkgs.writeShellScript "activation-${username}" ''
+ ${lib.optionalString (cfg.backupFileExtension != null)
+ "export HOME_MANAGER_BACKUP_EXT=${lib.escapeShellArg cfg.backupFileExtension}"}
+ ${lib.optionalString cfg.verbose "export VERBOSE=1"}
+ exec ${usercfg.home.activationPackage}/activate
+ ''}
+ '') cfg.users);
+ };
+}
diff --git a/infra/libkookie/home-manager/nixos/default.nix b/infra/libkookie/home-manager/nixos/default.nix
new file mode 100644
index 000000000000..e5ca61c02f68
--- /dev/null
+++ b/infra/libkookie/home-manager/nixos/default.nix
@@ -0,0 +1,124 @@
+{ config, lib, pkgs, utils, ... }:
+
+with lib;
+
+let
+
+ cfg = config.home-manager;
+
+ extendedLib = import ../modules/lib/stdlib-extended.nix pkgs.lib;
+
+ hmModule = types.submoduleWith {
+ specialArgs = {
+ lib = extendedLib;
+ nixosConfig = config;
+ };
+ modules = [
+ ({ name, ... }: {
+ imports = import ../modules/modules.nix {
+ inherit pkgs;
+ lib = extendedLib;
+ useNixpkgsModule = !cfg.useGlobalPkgs;
+ };
+
+ config = {
+ submoduleSupport.enable = true;
+ submoduleSupport.externalPackageInstall = cfg.useUserPackages;
+
+ # The per-user directory inside /etc/profiles is not known by
+ # fontconfig by default.
+ fonts.fontconfig.enable = cfg.useUserPackages
+ && config.fonts.fontconfig.enable;
+
+ home.username = config.users.users.${name}.name;
+ home.homeDirectory = config.users.users.${name}.home;
+ };
+ })
+ ];
+ };
+
+ serviceEnvironment = optionalAttrs (cfg.backupFileExtension != null) {
+ HOME_MANAGER_BACKUP_EXT = cfg.backupFileExtension;
+ } // optionalAttrs cfg.verbose { VERBOSE = "1"; };
+
+in {
+ options = {
+ home-manager = {
+ useUserPackages = mkEnableOption ''
+ installation of user packages through the
+ <option>users.users.‹name?›.packages</option> option.
+ '';
+
+ useGlobalPkgs = mkEnableOption ''
+ using the system configuration's <literal>pkgs</literal>
+ argument in Home Manager. This disables the Home Manager
+ options <option>nixpkgs.*</option>
+ '';
+
+ backupFileExtension = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "backup";
+ description = ''
+ On activation move existing files by appending the given
+ file extension rather than exiting with an error.
+ '';
+ };
+
+ verbose = mkEnableOption "verbose output on activation";
+
+ users = mkOption {
+ type = types.attrsOf hmModule;
+ default = { };
+ description = ''
+ Per-user Home Manager configuration.
+ '';
+ };
+ };
+ };
+
+ config = mkIf (cfg.users != { }) {
+ warnings = flatten (flip mapAttrsToList cfg.users (user: config:
+ flip map config.warnings (warning: "${user} profile: ${warning}")));
+
+ assertions = flatten (flip mapAttrsToList cfg.users (user: config:
+ flip map config.assertions (assertion: {
+ inherit (assertion) assertion;
+ message = "${user} profile: ${assertion.message}";
+ })));
+
+ users.users = mkIf cfg.useUserPackages
+ (mapAttrs (username: usercfg: { packages = [ usercfg.home.path ]; })
+ cfg.users);
+
+ environment.pathsToLink = mkIf cfg.useUserPackages [ "/etc/profile.d" ];
+
+ systemd.services = mapAttrs' (_: usercfg:
+ let username = usercfg.home.username;
+ in nameValuePair ("home-manager-${utils.escapeSystemdPath username}") {
+ description = "Home Manager environment for ${username}";
+ wantedBy = [ "multi-user.target" ];
+ wants = [ "nix-daemon.socket" ];
+ after = [ "nix-daemon.socket" ];
+
+ environment = serviceEnvironment;
+
+ unitConfig = { RequiresMountsFor = usercfg.home.homeDirectory; };
+
+ serviceConfig = {
+ User = usercfg.home.username;
+ Type = "oneshot";
+ RemainAfterExit = "yes";
+ SyslogIdentifier = "hm-activate-${username}";
+
+ # The activation script is run by a login shell to make sure
+ # that the user is given a sane Nix environment.
+ ExecStart = pkgs.writeScript "activate-${username}" ''
+ #! ${pkgs.runtimeShell} -el
+ echo Activating home-manager configuration for ${username}
+ exec ${usercfg.home.activationPackage}/activate
+ '';
+ };
+ }) cfg.users;
+ };
+}
diff --git a/infra/libkookie/home-manager/overlay.nix b/infra/libkookie/home-manager/overlay.nix
new file mode 100644
index 000000000000..35136cc8556b
--- /dev/null
+++ b/infra/libkookie/home-manager/overlay.nix
@@ -0,0 +1,3 @@
+self: super: {
+ home-manager = super.callPackage ./home-manager { path = toString ./.; };
+}
diff --git a/infra/libkookie/home-manager/tests/default.nix b/infra/libkookie/home-manager/tests/default.nix
new file mode 100644
index 000000000000..0cfa26bbccba
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/default.nix
@@ -0,0 +1,108 @@
+{ pkgs ? import <nixpkgs> {} }:
+
+let
+
+ lib = import ../modules/lib/stdlib-extended.nix pkgs.lib;
+
+ nmt = pkgs.fetchFromGitLab {
+ owner = "rycee";
+ repo = "nmt";
+ rev = "26af5c54c88695ed73be93a9eae6b71f2d76d04a";
+ sha256 = "1m10cwjh63qkz2rgnm0wk16pxh52lp8i9kjfv6cfhbzl5df4q95p";
+ };
+
+ modules = import ../modules/modules.nix {
+ inherit lib pkgs;
+ check = false;
+ } ++ [
+ {
+ # Fix impurities. Without these some of the user's environment
+ # will leak into the tests through `builtins.getEnv`.
+ xdg.enable = true;
+ home.username = "hm-user";
+ home.homeDirectory = "/home/hm-user";
+
+ # Avoid including documentation since this will cause
+ # unnecessary rebuilds of the tests.
+ manual.manpages.enable = false;
+ }
+ ];
+
+in
+
+import nmt {
+ inherit lib pkgs modules;
+ testedAttrPath = [ "home" "activationPackage" ];
+ tests = builtins.foldl' (a: b: a // (import b)) { } ([
+ ./lib/types
+ ./modules/files
+ ./modules/home-environment
+ ./modules/misc/fontconfig
+ ./modules/programs/alacritty
+ ./modules/programs/alot
+ ./modules/programs/aria2
+ ./modules/programs/autojump
+ ./modules/programs/bash
+ ./modules/programs/browserpass
+ ./modules/programs/dircolors
+ ./modules/programs/direnv
+ ./modules/programs/feh
+ ./modules/programs/fish
+ ./modules/programs/gh
+ ./modules/programs/git
+ ./modules/programs/gpg
+ ./modules/programs/i3status
+ ./modules/programs/kakoune
+ ./modules/programs/lf
+ ./modules/programs/lieer
+ ./modules/programs/man
+ ./modules/programs/mbsync
+ ./modules/programs/ncmpcpp
+ ./modules/programs/ne
+ ./modules/programs/neomutt
+ ./modules/programs/newsboat
+ ./modules/programs/nushell
+ ./modules/programs/powerline-go
+ ./modules/programs/qutebrowser
+ ./modules/programs/readline
+ ./modules/programs/ssh
+ ./modules/programs/starship
+ ./modules/programs/texlive
+ ./modules/programs/tmux
+ ./modules/programs/vscode
+ ./modules/programs/zplug
+ ./modules/programs/zsh
+ ./modules/xresources
+ ] ++ lib.optionals pkgs.stdenv.hostPlatform.isDarwin [
+ ./modules/targets-darwin
+ ] ++ lib.optionals pkgs.stdenv.hostPlatform.isLinux [
+ ./modules/misc/debug
+ ./modules/misc/numlock
+ ./modules/misc/pam
+ ./modules/misc/xdg
+ ./modules/misc/xsession
+ ./modules/programs/abook
+ ./modules/programs/autorandr
+ ./modules/programs/firefox
+ ./modules/programs/getmail
+ ./modules/programs/i3status-rust
+ ./modules/programs/ncmpcpp-linux
+ ./modules/programs/neovim # Broken package dependency on Darwin.
+ ./modules/programs/rofi
+ ./modules/programs/rofi-pass
+ ./modules/programs/waybar
+ ./modules/services/dropbox
+ ./modules/services/emacs
+ ./modules/services/fluidsynth
+ ./modules/services/kanshi
+ ./modules/services/lieer
+ ./modules/services/pbgopy
+ ./modules/services/polybar
+ ./modules/services/sxhkd
+ ./modules/services/window-managers/i3
+ ./modules/services/window-managers/sway
+ ./modules/services/wlsunset
+ ./modules/systemd
+ ./modules/targets-linux
+ ]);
+}
diff --git a/infra/libkookie/home-manager/tests/lib/types/dag-merge-result.txt b/infra/libkookie/home-manager/tests/lib/types/dag-merge-result.txt
new file mode 100644
index 000000000000..9779ef13c0f5
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/lib/types/dag-merge-result.txt
@@ -0,0 +1,3 @@
+before:before
+between:between
+after:after
diff --git a/infra/libkookie/home-manager/tests/lib/types/dag-merge.nix b/infra/libkookie/home-manager/tests/lib/types/dag-merge.nix
new file mode 100644
index 000000000000..138a0b64fb75
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/lib/types/dag-merge.nix
@@ -0,0 +1,32 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ dag = config.lib.dag;
+
+ result = let
+ sorted = dag.topoSort config.tested.dag;
+ data = map (e: "${e.name}:${e.data}") sorted.result;
+ in concatStringsSep "\n" data + "\n";
+
+in {
+ options.tested.dag = mkOption { type = hm.types.dagOf types.str; };
+
+ config = {
+ tested = mkMerge [
+ { dag.after = "after"; }
+ { dag.before = dag.entryBefore [ "after" ] "before"; }
+ { dag.between = dag.entryBetween [ "after" ] [ "before" ] "between"; }
+ ];
+
+ home.file."result.txt".text = result;
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/result.txt \
+ ${./dag-merge-result.txt}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/lib/types/dag-submodule.nix b/infra/libkookie/home-manager/tests/lib/types/dag-submodule.nix
new file mode 100644
index 000000000000..552e804acbc5
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/lib/types/dag-submodule.nix
@@ -0,0 +1,43 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ dag = config.lib.dag;
+
+ result = let
+ sorted = dag.topoSort config.tested.dag;
+ data = map (e: "${e.name}:${e.data.name}") sorted.result;
+ in concatStringsSep "\n" data + "\n";
+
+in {
+ options.tested.dag = mkOption {
+ type = hm.types.dagOf (types.submodule ({ dagName, ... }: {
+ options.name = mkOption { type = types.str; };
+ config.name = "dn-${dagName}";
+ }));
+ };
+
+ config = {
+ tested.dag = {
+ after = { };
+ before = dag.entryBefore [ "after" ] { };
+ between = dag.entryBetween [ "after" ] [ "before" ] { };
+ };
+
+ home.file."result.txt".text = result;
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/result.txt \
+ ${
+ pkgs.writeText "result.txt" ''
+ before:dn-before
+ between:dn-between
+ after:dn-after
+ ''
+ }
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/lib/types/default.nix b/infra/libkookie/home-manager/tests/lib/types/default.nix
new file mode 100644
index 000000000000..acb565012725
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/lib/types/default.nix
@@ -0,0 +1,7 @@
+{
+ lib-types-dag-submodule = ./dag-submodule.nix;
+ lib-types-dag-merge = ./dag-merge.nix;
+ lib-types-list-or-dag-merge = ./list-or-dag-merge.nix;
+
+ lib-types-gvariant-merge = ./gvariant-merge.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/lib/types/gvariant-merge.nix b/infra/libkookie/home-manager/tests/lib/types/gvariant-merge.nix
new file mode 100644
index 000000000000..867534c1f149
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/lib/types/gvariant-merge.nix
@@ -0,0 +1,62 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+in {
+ options.examples = mkOption { type = types.attrsOf hm.types.gvariant; };
+
+ config = {
+ examples = with hm.gvariant;
+ mkMerge [
+ { bool = true; }
+ { bool = true; }
+
+ { float = 3.14; }
+
+ { int = 42; }
+ { int = 42; }
+
+ { list = [ "one" ]; }
+ { list = mkArray type.string [ "two" ]; }
+
+ { emptyArray1 = [ ]; }
+ { emptyArray2 = mkEmptyArray type.uint32; }
+
+ { string = "foo"; }
+ { string = "foo"; }
+ { escapedString = "' \\"; }
+
+ { tuple = mkTuple [ 1 [ "foo" ] ]; }
+
+ { maybe1 = mkNothing type.string; }
+ { maybe2 = mkJust (mkUint32 4); }
+ ];
+
+ home.file."result.txt".text = let
+ mkLine = n: v: "${n} = ${toString (hm.gvariant.mkValue v)}";
+ result = concatStringsSep "\n" (mapAttrsToList mkLine config.examples);
+ in result + "\n";
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/result.txt \
+ ${
+ pkgs.writeText "expected.txt" ''
+ bool = true
+ emptyArray1 = @as []
+ emptyArray2 = @as []
+ escapedString = '\' \\'
+ float = 3.140000
+ int = 42
+ list = @as ['one','two']
+ maybe1 = @ms nothing
+ maybe2 = just @u 4
+ string = 'foo'
+ tuple = @(ias) (1,@as ['foo'])
+ ''
+ }
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/lib/types/list-or-dag-merge-result.txt b/infra/libkookie/home-manager/tests/lib/types/list-or-dag-merge-result.txt
new file mode 100644
index 000000000000..5fb67a5101c5
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/lib/types/list-or-dag-merge-result.txt
@@ -0,0 +1,15 @@
+before:before
+between:between
+after:after
+unnamed-1.1:k
+unnamed-1.2:l
+unnamed-2.01:a
+unnamed-2.02:b
+unnamed-2.03:c
+unnamed-2.04:d
+unnamed-2.05:e
+unnamed-2.06:f
+unnamed-2.07:g
+unnamed-2.08:h
+unnamed-2.09:i
+unnamed-2.10:j
diff --git a/infra/libkookie/home-manager/tests/lib/types/list-or-dag-merge.nix b/infra/libkookie/home-manager/tests/lib/types/list-or-dag-merge.nix
new file mode 100644
index 000000000000..08216140e53d
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/lib/types/list-or-dag-merge.nix
@@ -0,0 +1,34 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ dag = config.lib.dag;
+
+ result = let
+ sorted = dag.topoSort config.tested.dag;
+ data = map (e: "${e.name}:${e.data}") sorted.result;
+ in concatStringsSep "\n" data + "\n";
+
+in {
+ options.tested.dag = mkOption { type = hm.types.listOrDagOf types.str; };
+
+ config = {
+ tested = mkMerge [
+ { dag = [ "k" "l" ]; }
+ { dag = [ "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" ]; }
+ { dag.after = "after"; }
+ { dag.before = dag.entryBefore [ "after" ] "before"; }
+ { dag.between = dag.entryBetween [ "after" ] [ "before" ] "between"; }
+ ];
+
+ home.file."result.txt".text = result;
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/result.txt \
+ ${./list-or-dag-merge-result.txt}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/accounts/email-test-accounts.nix b/infra/libkookie/home-manager/tests/modules/accounts/email-test-accounts.nix
new file mode 100644
index 000000000000..9a4e0b8e72f0
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/accounts/email-test-accounts.nix
@@ -0,0 +1,28 @@
+{ ... }:
+
+{
+ accounts.email = {
+ maildirBasePath = "Mail";
+
+ accounts = {
+ "hm@example.com" = {
+ address = "hm@example.com";
+ userName = "home.manager";
+ realName = "H. M. Test";
+ passwordCommand = "password-command";
+ imap.host = "imap.example.com";
+ smtp.host = "smtp.example.com";
+ };
+
+ hm-account = {
+ address = "hm@example.org";
+ userName = "home.manager.jr";
+ realName = "H. M. Test Jr.";
+ passwordCommand = "password-command 2";
+ imap.host = "imap.example.org";
+ smtp.host = "smtp.example.org";
+ smtp.tls.useStartTls = true;
+ };
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/files/.hidden b/infra/libkookie/home-manager/tests/modules/files/.hidden
new file mode 100644
index 000000000000..ca05448e7a00
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/files/.hidden
@@ -0,0 +1 @@
+The name of this file has a dot prefix.
diff --git a/infra/libkookie/home-manager/tests/modules/files/default.nix b/infra/libkookie/home-manager/tests/modules/files/default.nix
new file mode 100644
index 000000000000..6f1ef24b8103
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/files/default.nix
@@ -0,0 +1,8 @@
+{
+ files-executable = ./executable.nix;
+ files-hidden-source = ./hidden-source.nix;
+ files-out-of-store-symlink = ./out-of-store-symlink.nix;
+ files-source-with-spaces = ./source-with-spaces.nix;
+ files-target-with-shellvar = ./target-with-shellvar.nix;
+ files-text = ./text.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/files/executable.nix b/infra/libkookie/home-manager/tests/modules/files/executable.nix
new file mode 100644
index 000000000000..b286c2b499f7
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/files/executable.nix
@@ -0,0 +1,17 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+ config = {
+ home.file."executable" = {
+ text = "";
+ executable = true;
+ };
+
+ nmt.script = ''
+ assertFileExists home-files/executable
+ assertFileIsExecutable home-files/executable;
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/files/hidden-source.nix b/infra/libkookie/home-manager/tests/modules/files/hidden-source.nix
new file mode 100644
index 000000000000..8169fedcd7fc
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/files/hidden-source.nix
@@ -0,0 +1,19 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+ config = {
+ home.file.".hidden".source = ./.hidden;
+
+ nmt.script = ''
+ assertFileExists home-files/.hidden;
+ assertFileContent home-files/.hidden ${
+ builtins.path {
+ path = ./.hidden;
+ name = "expected";
+ }
+ }
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/files/out-of-store-symlink.nix b/infra/libkookie/home-manager/tests/modules/files/out-of-store-symlink.nix
new file mode 100644
index 000000000000..af274aaebec6
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/files/out-of-store-symlink.nix
@@ -0,0 +1,29 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+
+ filePath = ./. + "/source with spaces!";
+
+in {
+ config = {
+ home.file."oos".source = config.lib.file.mkOutOfStoreSymlink filePath;
+
+ nmt.script = ''
+ assertLinkExists "home-files/oos"
+
+ storePath="$(readlink $TESTED/home-files/oos)"
+
+ if [[ ! -L $storePath ]]; then
+ fail "Expected $storePath to be a symbolic link, but it was not."
+ fi
+
+ actual="$(readlink "$storePath")"
+ expected="${toString filePath}"
+ if [[ $actual != $expected ]]; then
+ fail "Symlink home-files/oos should point to $expected via the Nix store, but it actually points to $actual."
+ fi
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/files/source with spaces! b/infra/libkookie/home-manager/tests/modules/files/source with spaces!
new file mode 100644
index 000000000000..e1ace4041744
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/files/source with spaces!
@@ -0,0 +1 @@
+Source with spaces!
diff --git a/infra/libkookie/home-manager/tests/modules/files/source-with-spaces.nix b/infra/libkookie/home-manager/tests/modules/files/source-with-spaces.nix
new file mode 100644
index 000000000000..1d593c64256b
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/files/source-with-spaces.nix
@@ -0,0 +1,20 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+ config = {
+ home.file."source with spaces!".source = ./. + "/source with spaces!";
+
+ nmt.script = ''
+ assertFileExists 'home-files/source with spaces!';
+ assertFileContent 'home-files/source with spaces!' \
+ ${
+ builtins.path {
+ path = ./. + "/source with spaces!";
+ name = "source-with-spaces-expected";
+ }
+ }
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/files/target-with-shellvar.nix b/infra/libkookie/home-manager/tests/modules/files/target-with-shellvar.nix
new file mode 100644
index 000000000000..c54946eb9eba
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/files/target-with-shellvar.nix
@@ -0,0 +1,15 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ home.file."$HOME/$FOO/bar baz".text = "blah";
+
+ nmt.script = ''
+ assertFileExists 'home-files/$HOME/$FOO/bar baz';
+ assertFileContent 'home-files/$HOME/$FOO/bar baz' \
+ ${pkgs.writeText "expected" "blah"}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/files/text-expected.txt b/infra/libkookie/home-manager/tests/modules/files/text-expected.txt
new file mode 100644
index 000000000000..b3a0ff2db121
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/files/text-expected.txt
@@ -0,0 +1,2 @@
+This is the
+expected text.
diff --git a/infra/libkookie/home-manager/tests/modules/files/text.nix b/infra/libkookie/home-manager/tests/modules/files/text.nix
new file mode 100644
index 000000000000..6fc9a26fcb43
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/files/text.nix
@@ -0,0 +1,18 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+ config = {
+ home.file."using-text".text = ''
+ This is the
+ expected text.
+ '';
+
+ nmt.script = ''
+ assertFileExists home-files/using-text
+ assertFileIsNotExecutable home-files/using-text
+ assertFileContent home-files/using-text ${./text-expected.txt}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/home-environment/default.nix b/infra/libkookie/home-manager/tests/modules/home-environment/default.nix
new file mode 100644
index 000000000000..e76e248a1643
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/home-environment/default.nix
@@ -0,0 +1,4 @@
+{
+ home-session-variables = ./session-variables.nix;
+ home-session-path = ./session-path.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/home-environment/session-path.nix b/infra/libkookie/home-manager/tests/modules/home-environment/session-path.nix
new file mode 100644
index 000000000000..3b40059eac9a
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/home-environment/session-path.nix
@@ -0,0 +1,27 @@
+{ config, lib, pkgs, ... }:
+
+{
+ imports = [
+ ({ ... }: { config.home.sessionPath = [ "foo" ]; })
+ ({ ... }: { config.home.sessionPath = [ "bar" "baz" ]; })
+ ];
+
+ nmt.script = ''
+ assertFileExists home-path/etc/profile.d/hm-session-vars.sh
+ assertFileContent \
+ home-path/etc/profile.d/hm-session-vars.sh \
+ ${
+ pkgs.writeText "session-path-expected.txt" ''
+ # Only source this once.
+ if [ -n "$__HM_SESS_VARS_SOURCED" ]; then return; fi
+ export __HM_SESS_VARS_SOURCED=1
+
+ export XDG_CACHE_HOME="/home/hm-user/.cache"
+ export XDG_CONFIG_HOME="/home/hm-user/.config"
+ export XDG_DATA_HOME="/home/hm-user/.local/share"
+ export PATH="$PATH''${PATH:+:}bar:baz:foo"
+ ''
+ }
+ '';
+
+}
diff --git a/infra/libkookie/home-manager/tests/modules/home-environment/session-variables-expected.txt b/infra/libkookie/home-manager/tests/modules/home-environment/session-variables-expected.txt
new file mode 100644
index 000000000000..4e18e2b31a8d
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/home-environment/session-variables-expected.txt
@@ -0,0 +1,9 @@
+# Only source this once.
+if [ -n "$__HM_SESS_VARS_SOURCED" ]; then return; fi
+export __HM_SESS_VARS_SOURCED=1
+
+export V1="v1"
+export V2="v2-v1"
+export XDG_CACHE_HOME="/home/hm-user/.cache"
+export XDG_CONFIG_HOME="/home/hm-user/.config"
+export XDG_DATA_HOME="/home/hm-user/.local/share"
diff --git a/infra/libkookie/home-manager/tests/modules/home-environment/session-variables.nix b/infra/libkookie/home-manager/tests/modules/home-environment/session-variables.nix
new file mode 100644
index 000000000000..9f326ebc1b84
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/home-environment/session-variables.nix
@@ -0,0 +1,19 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+ config = {
+ home.sessionVariables = {
+ V1 = "v1";
+ V2 = "v2-${config.home.sessionVariables.V1}";
+ };
+
+ nmt.script = ''
+ assertFileExists home-path/etc/profile.d/hm-session-vars.sh
+ assertFileContent \
+ home-path/etc/profile.d/hm-session-vars.sh \
+ ${./session-variables-expected.txt}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/misc/debug/default.nix b/infra/libkookie/home-manager/tests/modules/misc/debug/default.nix
new file mode 100644
index 000000000000..b42462d0802a
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/misc/debug/default.nix
@@ -0,0 +1,25 @@
+{
+ debug = { pkgs, config, lib, ... }: {
+ home.enableDebugInfo = true;
+ home.packages = with pkgs; [ curl gdb ];
+
+ nmt.script = ''
+ [ -L $TESTED/home-path/lib/debug/curl ] \
+ || fail "Debug-symbols for pkgs.curl should exist in \`/home-path/lib/debug'!"
+
+ #source $TESTED/home-path/etc/profile.d/hm-session-vars.sh
+ #[[ "$NIX_DEBUG_INFO_DIRS" =~ /lib/debug$ ]] \
+ #|| fail "Invalid NIX_DEBUG_INFO_DIRS!"
+ assertFileExists home-path/etc/profile.d/hm-session-vars.sh
+ assertFileRegex home-path/etc/profile.d/hm-session-vars.sh \
+ 'NIX_DEBUG_INFO_DIRS=.*/lib/debug'
+
+ # We need to override NIX_DEBUG_INFO_DIRS here as $HOME evalutes to the home
+ # of the user who executes this testcase :/
+ { echo quit | PATH="$TESTED/home-path/bin''${PATH:+:}$PATH" NIX_DEBUG_INFO_DIRS=$TESTED/home-path/lib/debug \
+ gdb curl 2>&1 | \
+ grep 'Reading symbols from ${builtins.storeDir}/'; \
+ } || fail "Failed to read debug symbols from curl in gdb"
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/misc/fontconfig/default.nix b/infra/libkookie/home-manager/tests/modules/misc/fontconfig/default.nix
new file mode 100644
index 000000000000..bea306165c42
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/misc/fontconfig/default.nix
@@ -0,0 +1,22 @@
+{
+ fontconfig-no-font-package = ./no-font-package.nix;
+ fontconfig-single-font-package = ./single-font-package.nix;
+ # Disabled due to test failing with message
+ #
+ # Expected directory home-path/lib/fontconfig/cache to exist but it was not found.
+ #
+ # Verbose output from fc-cache:
+ #
+ # Font directories:
+ # /nix/store/da…g5-home-manager-path/lib/X11/fonts
+ # /nix/store/da…g5-home-manager-path/share/fonts
+ # /nix/store/da…g5-home-manager-path/share/fonts/truetype
+ # /nix/store/da…g5-home-manager-path/lib/X11/fonts: skipping, no such directory
+ # /nix/store/da…g5-home-manager-path/share/fonts: caching, new cache contents: 1 fonts, 1 dirs
+ # /nix/store/da…g5-home-manager-path/share/fonts/truetype: caching, new cache contents: 3 fonts, 0 dirs
+ # /nix/store/da…g5-home-manager-path/share/fonts/truetype: skipping, looped directory detected
+ # /nix/store/da…g5-home-manager-path/lib/fontconfig/cache: cleaning cache directory
+ # /nix/store/da…g5-home-manager-path/lib/fontconfig/cache: invalid cache file: 786068e7df13f7c2105017ef3d78e351-x86_64.cache-7
+ # /nix/store/da…g5-home-manager-path/lib/fontconfig/cache: invalid cache file: 4766193978ddda4bd196f2b98c00fb00-x86_64.cache-7
+ #fontconfig-multiple-font-packages = ./multiple-font-packages.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/misc/fontconfig/multiple-font-packages.nix b/infra/libkookie/home-manager/tests/modules/misc/fontconfig/multiple-font-packages.nix
new file mode 100644
index 000000000000..3845b4ba4b10
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/misc/fontconfig/multiple-font-packages.nix
@@ -0,0 +1,15 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ home.packages = [ pkgs.comic-relief pkgs.unifont ];
+
+ fonts.fontconfig.enable = true;
+
+ nmt.script = ''
+ assertDirectoryNotEmpty home-path/lib/fontconfig/cache
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/misc/fontconfig/no-font-package.nix b/infra/libkookie/home-manager/tests/modules/misc/fontconfig/no-font-package.nix
new file mode 100644
index 000000000000..c4c687a13209
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/misc/fontconfig/no-font-package.nix
@@ -0,0 +1,17 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ home.packages = [
+ # Look, no font!
+ ];
+
+ fonts.fontconfig.enable = true;
+
+ nmt.script = ''
+ assertPathNotExists home-path/lib/fontconfig/cache
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/misc/fontconfig/single-font-package.nix b/infra/libkookie/home-manager/tests/modules/misc/fontconfig/single-font-package.nix
new file mode 100644
index 000000000000..b70bdf8a9a7d
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/misc/fontconfig/single-font-package.nix
@@ -0,0 +1,15 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ home.packages = [ pkgs.comic-relief ];
+
+ fonts.fontconfig.enable = true;
+
+ nmt.script = ''
+ assertDirectoryNotEmpty home-path/lib/fontconfig/cache
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/misc/numlock/default.nix b/infra/libkookie/home-manager/tests/modules/misc/numlock/default.nix
new file mode 100644
index 000000000000..47ca563fec04
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/misc/numlock/default.nix
@@ -0,0 +1 @@
+{ numlock = ./numlock.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/misc/numlock/numlock.nix b/infra/libkookie/home-manager/tests/modules/misc/numlock/numlock.nix
new file mode 100644
index 000000000000..aa468c212c3e
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/misc/numlock/numlock.nix
@@ -0,0 +1,18 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ xsession.numlock.enable = true;
+
+ nixpkgs.overlays = [
+ (self: super: { numlockx = pkgs.writeScriptBin "dummy-numlockx" ""; })
+ ];
+
+ nmt.script = ''
+ serviceFile=home-files/.config/systemd/user/numlockx.service
+ assertFileExists $serviceFile
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/misc/pam/default.nix b/infra/libkookie/home-manager/tests/modules/misc/pam/default.nix
new file mode 100644
index 000000000000..81c435e76416
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/misc/pam/default.nix
@@ -0,0 +1 @@
+{ pam-session-variables = ./session-variables.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/misc/pam/session-variables-expected.txt b/infra/libkookie/home-manager/tests/modules/misc/pam/session-variables-expected.txt
new file mode 100644
index 000000000000..b84a12b76758
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/misc/pam/session-variables-expected.txt
@@ -0,0 +1,2 @@
+V1 OVERRIDE="v1"
+V2 OVERRIDE="v2-v1"
diff --git a/infra/libkookie/home-manager/tests/modules/misc/pam/session-variables.nix b/infra/libkookie/home-manager/tests/modules/misc/pam/session-variables.nix
new file mode 100644
index 000000000000..4fbec4163b57
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/misc/pam/session-variables.nix
@@ -0,0 +1,19 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+ config = {
+ pam.sessionVariables = {
+ V1 = "v1";
+ V2 = "v2-${config.pam.sessionVariables.V1}";
+ };
+
+ nmt.script = ''
+ assertFileExists home-files/.pam_environment
+ assertFileContent \
+ home-files/.pam_environment \
+ ${./session-variables-expected.txt}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/misc/xdg/default.nix b/infra/libkookie/home-manager/tests/modules/misc/xdg/default.nix
new file mode 100644
index 000000000000..813a25202fac
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/misc/xdg/default.nix
@@ -0,0 +1,4 @@
+{
+ xdg-mime-apps-basics = ./mime-apps-basics.nix;
+ xdg-file-attr-names = ./file-attr-names.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/misc/xdg/file-attr-names.nix b/infra/libkookie/home-manager/tests/modules/misc/xdg/file-attr-names.nix
new file mode 100644
index 000000000000..0aa8fcffe6f1
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/misc/xdg/file-attr-names.nix
@@ -0,0 +1,26 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ xdg.configFile.test.text = "config";
+ xdg.dataFile.test.text = "data";
+ home.file.test.text = "home";
+
+ nmt.script = ''
+ assertFileExists home-files/.config/test
+ assertFileExists home-files/.local/share/test
+ assertFileExists home-files/test
+ assertFileContent \
+ home-files/.config/test \
+ ${builtins.toFile "test" "config"}
+ assertFileContent \
+ home-files/.local/share/test \
+ ${builtins.toFile "test" "data"}
+ assertFileContent \
+ home-files/test \
+ ${builtins.toFile "test" "home"}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/misc/xdg/mime-apps-basics-expected.ini b/infra/libkookie/home-manager/tests/modules/misc/xdg/mime-apps-basics-expected.ini
new file mode 100644
index 000000000000..c27181eb58f5
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/misc/xdg/mime-apps-basics-expected.ini
@@ -0,0 +1,9 @@
+[Added Associations]
+mimetype1=foo1.desktop;foo2.desktop;foo3.desktop
+mimetype2=foo4.desktop
+
+[Default Applications]
+mimetype1=default1.desktop;default2.desktop
+
+[Removed Associations]
+mimetype1=foo5.desktop
diff --git a/infra/libkookie/home-manager/tests/modules/misc/xdg/mime-apps-basics.nix b/infra/libkookie/home-manager/tests/modules/misc/xdg/mime-apps-basics.nix
new file mode 100644
index 000000000000..e181e8206f60
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/misc/xdg/mime-apps-basics.nix
@@ -0,0 +1,28 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ xdg.mimeApps = {
+ enable = true;
+ associations = {
+ added = {
+ "mimetype1" = [ "foo1.desktop" "foo2.desktop" "foo3.desktop" ];
+ "mimetype2" = "foo4.desktop";
+ };
+ removed = { mimetype1 = "foo5.desktop"; };
+ };
+ defaultApplications = {
+ "mimetype1" = [ "default1.desktop" "default2.desktop" ];
+ };
+ };
+
+ nmt.script = ''
+ assertFileExists home-files/.config/mimeapps.list
+ assertFileContent \
+ home-files/.config/mimeapps.list \
+ ${./mime-apps-basics-expected.ini}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/misc/xsession/basic-setxkbmap-expected.service b/infra/libkookie/home-manager/tests/modules/misc/xsession/basic-setxkbmap-expected.service
new file mode 100644
index 000000000000..39f876dd60e6
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/misc/xsession/basic-setxkbmap-expected.service
@@ -0,0 +1,12 @@
+[Install]
+WantedBy=graphical-session.target
+
+[Service]
+ExecStart=@setxkbmap@/bin/setxkbmap -layout 'us' -variant ''
+RemainAfterExit=true
+Type=oneshot
+
+[Unit]
+After=graphical-session-pre.target
+Description=Set up keyboard in X
+PartOf=graphical-session.target
diff --git a/infra/libkookie/home-manager/tests/modules/misc/xsession/basic-xprofile-expected.txt b/infra/libkookie/home-manager/tests/modules/misc/xsession/basic-xprofile-expected.txt
new file mode 100644
index 000000000000..282f8f5c5de5
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/misc/xsession/basic-xprofile-expected.txt
@@ -0,0 +1,16 @@
+. "/home/hm-user/.nix-profile/etc/profile.d/hm-session-vars.sh"
+
+if [ -e "$HOME/.profile" ]; then
+ . "$HOME/.profile"
+fi
+
+# If there are any running services from a previous session.
+# Need to run this in xprofile because the NixOS xsession
+# script starts up graphical-session.target.
+systemctl --user stop graphical-session.target graphical-session-pre.target
+
+systemctl --user import-environment DBUS_SESSION_BUS_ADDRESS DISPLAY SSH_AUTH_SOCK XAUTHORITY XDG_DATA_DIRS XDG_RUNTIME_DIR XDG_SESSION_ID EXTRA_IMPORTED_VARIABLE
+
+profile extra commands
+
+export HM_XPROFILE_SOURCED=1
diff --git a/infra/libkookie/home-manager/tests/modules/misc/xsession/basic-xsession-expected.txt b/infra/libkookie/home-manager/tests/modules/misc/xsession/basic-xsession-expected.txt
new file mode 100644
index 000000000000..c11b7c33048a
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/misc/xsession/basic-xsession-expected.txt
@@ -0,0 +1,18 @@
+if [ -z "$HM_XPROFILE_SOURCED" ]; then
+ . ~/.xprofile
+fi
+unset HM_XPROFILE_SOURCED
+
+systemctl --user start hm-graphical-session.target
+
+init extra commands
+
+window manager command
+
+systemctl --user stop graphical-session.target
+systemctl --user stop graphical-session-pre.target
+
+# Wait until the units actually stop.
+while [ -n "$(systemctl --user --no-legend --state=deactivating list-units)" ]; do
+ sleep 0.5
+done
diff --git a/infra/libkookie/home-manager/tests/modules/misc/xsession/basic.nix b/infra/libkookie/home-manager/tests/modules/misc/xsession/basic.nix
new file mode 100644
index 000000000000..d6756291cfe6
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/misc/xsession/basic.nix
@@ -0,0 +1,40 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ xsession = {
+ enable = true;
+ windowManager.command = "window manager command";
+ importedVariables = [ "EXTRA_IMPORTED_VARIABLE" ];
+ initExtra = "init extra commands";
+ profileExtra = "profile extra commands";
+ };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ xorg = super.xorg // {
+ setxkbmap = super.xorg.setxkbmap // { outPath = "@setxkbmap@"; };
+ };
+ })
+ ];
+
+ nmt.script = ''
+ assertFileExists home-files/.xprofile
+ assertFileContent \
+ home-files/.xprofile \
+ ${./basic-xprofile-expected.txt}
+
+ assertFileExists home-files/.xsession
+ assertFileContent \
+ home-files/.xsession \
+ ${./basic-xsession-expected.txt}
+
+ assertFileExists home-files/.config/systemd/user/setxkbmap.service
+ assertFileContent \
+ home-files/.config/systemd/user/setxkbmap.service \
+ ${./basic-setxkbmap-expected.service}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/misc/xsession/default.nix b/infra/libkookie/home-manager/tests/modules/misc/xsession/default.nix
new file mode 100644
index 000000000000..2ddbf47efacb
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/misc/xsession/default.nix
@@ -0,0 +1,4 @@
+{
+ xsession-basic = ./basic.nix;
+ xsession-keyboard-without-layout = ./keyboard-without-layout.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/misc/xsession/keyboard-without-layout-expected.service b/infra/libkookie/home-manager/tests/modules/misc/xsession/keyboard-without-layout-expected.service
new file mode 100644
index 000000000000..a04af53dad77
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/misc/xsession/keyboard-without-layout-expected.service
@@ -0,0 +1,12 @@
+[Install]
+WantedBy=graphical-session.target
+
+[Service]
+ExecStart=@setxkbmap@/bin/setxkbmap -option 'ctrl:nocaps' -option 'altwin:no_win'
+RemainAfterExit=true
+Type=oneshot
+
+[Unit]
+After=graphical-session-pre.target
+Description=Set up keyboard in X
+PartOf=graphical-session.target
diff --git a/infra/libkookie/home-manager/tests/modules/misc/xsession/keyboard-without-layout.nix b/infra/libkookie/home-manager/tests/modules/misc/xsession/keyboard-without-layout.nix
new file mode 100644
index 000000000000..90038cfd034e
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/misc/xsession/keyboard-without-layout.nix
@@ -0,0 +1,34 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ home.stateVersion = "19.09";
+
+ home.keyboard = { options = [ "ctrl:nocaps" "altwin:no_win" ]; };
+
+ xsession = {
+ enable = true;
+ windowManager.command = "window manager command";
+ importedVariables = [ "EXTRA_IMPORTED_VARIABLE" ];
+ initExtra = "init extra commands";
+ profileExtra = "profile extra commands";
+ };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ xorg = super.xorg // {
+ setxkbmap = super.xorg.setxkbmap // { outPath = "@setxkbmap@"; };
+ };
+ })
+ ];
+
+ nmt.script = ''
+ assertFileExists home-files/.config/systemd/user/setxkbmap.service
+ assertFileContent \
+ home-files/.config/systemd/user/setxkbmap.service \
+ ${./keyboard-without-layout-expected.service}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/abook/default.nix b/infra/libkookie/home-manager/tests/modules/programs/abook/default.nix
new file mode 100644
index 000000000000..12ad238ed250
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/abook/default.nix
@@ -0,0 +1,4 @@
+{
+ abook-no-settings = ./no-settings.nix;
+ abook-with-settings = ./with-settings.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/abook/no-settings.nix b/infra/libkookie/home-manager/tests/modules/programs/abook/no-settings.nix
new file mode 100644
index 000000000000..ad04acd23650
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/abook/no-settings.nix
@@ -0,0 +1,16 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.abook.enable = true;
+
+ nixpkgs.overlays =
+ [ (self: super: { abook = pkgs.writeScriptBin "dummy-abook" ""; }) ];
+
+ nmt.script = ''
+ assertPathNotExists home-files/.config/abook/abookrc
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/abook/with-settings.cfg b/infra/libkookie/home-manager/tests/modules/programs/abook/with-settings.cfg
new file mode 100644
index 000000000000..d73445f4d830
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/abook/with-settings.cfg
@@ -0,0 +1,21 @@
+# Generated by Home Manager.
+# See http://abook.sourceforge.net/
+
+# Defining a new custom field
+# -----------------------------
+
+field pager = Pager
+field address_lines = Address, list
+field birthday = Birthday, date
+
+# Defining a view/tab
+# ---------------------
+
+view CONTACT = name, email
+view ADDRESS = address_lines, city, state, zip, country
+view PHONE = phone, workphone, pager, mobile, fax
+view OTHER = url, birthday
+
+# Automatically save database on exit
+set autosave=true
+
diff --git a/infra/libkookie/home-manager/tests/modules/programs/abook/with-settings.nix b/infra/libkookie/home-manager/tests/modules/programs/abook/with-settings.nix
new file mode 100644
index 000000000000..3cb02a2666ca
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/abook/with-settings.nix
@@ -0,0 +1,39 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.abook = {
+ enable = true;
+
+ extraConfig = ''
+ # Defining a new custom field
+ # -----------------------------
+
+ field pager = Pager
+ field address_lines = Address, list
+ field birthday = Birthday, date
+
+ # Defining a view/tab
+ # ---------------------
+
+ view CONTACT = name, email
+ view ADDRESS = address_lines, city, state, zip, country
+ view PHONE = phone, workphone, pager, mobile, fax
+ view OTHER = url, birthday
+
+ # Automatically save database on exit
+ set autosave=true
+ '';
+ };
+
+ nixpkgs.overlays =
+ [ (self: super: { abook = pkgs.writeScriptBin "dummy-abook" ""; }) ];
+
+ nmt.script = ''
+ assertFileExists home-files/.config/abook/abookrc
+ assertFileContent home-files/.config/abook/abookrc ${./with-settings.cfg}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/alacritty/default.nix b/infra/libkookie/home-manager/tests/modules/programs/alacritty/default.nix
new file mode 100644
index 000000000000..3ccd9a91f33a
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/alacritty/default.nix
@@ -0,0 +1,5 @@
+{
+ alacritty-example-settings = ./example-settings.nix;
+ alacritty-empty-settings = ./empty-settings.nix;
+ alacritty-merging-settings = ./settings-merging.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/alacritty/empty-settings.nix b/infra/libkookie/home-manager/tests/modules/programs/alacritty/empty-settings.nix
new file mode 100644
index 000000000000..65470473c1ac
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/alacritty/empty-settings.nix
@@ -0,0 +1,17 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.alacritty.enable = true;
+
+ nixpkgs.overlays = [
+ (self: super: { alacritty = pkgs.writeScriptBin "dummy-alacritty" ""; })
+ ];
+
+ nmt.script = ''
+ assertPathNotExists home-files/.config/alacritty
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/alacritty/example-settings-expected.yml b/infra/libkookie/home-manager/tests/modules/programs/alacritty/example-settings-expected.yml
new file mode 100644
index 000000000000..061624192c3c
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/alacritty/example-settings-expected.yml
@@ -0,0 +1 @@
+{"key_bindings":[{"chars":"\x0c","key":"K","mods":"Control"}],"window":{"dimensions":{"columns":200,"lines":3}}} \ No newline at end of file
diff --git a/infra/libkookie/home-manager/tests/modules/programs/alacritty/example-settings.nix b/infra/libkookie/home-manager/tests/modules/programs/alacritty/example-settings.nix
new file mode 100644
index 000000000000..c3671aa0ca35
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/alacritty/example-settings.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.alacritty = {
+ enable = true;
+ package = pkgs.writeScriptBin "dummy-alacritty" "";
+
+ settings = {
+ window.dimensions = {
+ lines = 3;
+ columns = 200;
+ };
+
+ key_bindings = [{
+ key = "K";
+ mods = "Control";
+ chars = "\\x0c";
+ }];
+ };
+ };
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/.config/alacritty/alacritty.yml \
+ ${./example-settings-expected.yml}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/alacritty/settings-merging-expected.yml b/infra/libkookie/home-manager/tests/modules/programs/alacritty/settings-merging-expected.yml
new file mode 100644
index 000000000000..49d92a614e88
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/alacritty/settings-merging-expected.yml
@@ -0,0 +1 @@
+{"font":{"bold":{"family":"SFMono"},"normal":{"family":"SFMono"}},"key_bindings":[{"chars":"\x0c","key":"K","mods":"Control"}],"window":{"dimensions":{"columns":200,"lines":3}}} \ No newline at end of file
diff --git a/infra/libkookie/home-manager/tests/modules/programs/alacritty/settings-merging.nix b/infra/libkookie/home-manager/tests/modules/programs/alacritty/settings-merging.nix
new file mode 100644
index 000000000000..1b8559d69de8
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/alacritty/settings-merging.nix
@@ -0,0 +1,39 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.alacritty = {
+ enable = true;
+ package = pkgs.writeScriptBin "dummy-alacritty" "";
+
+ settings = {
+ window.dimensions = {
+ lines = 3;
+ columns = 200;
+ };
+
+ key_bindings = [{
+ key = "K";
+ mods = "Control";
+ chars = "\\x0c";
+ }];
+
+ font = let
+ defaultFont =
+ lib.mkMerge [ (lib.mkIf true "SFMono") (lib.mkIf false "Iosevka") ];
+ in {
+ normal.family = defaultFont;
+ bold.family = defaultFont;
+ };
+ };
+ };
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/.config/alacritty/alacritty.yml \
+ ${./settings-merging-expected.yml}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/alot/alot-expected.conf b/infra/libkookie/home-manager/tests/modules/programs/alot/alot-expected.conf
new file mode 100644
index 000000000000..6d3ace4a3721
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/alot/alot-expected.conf
@@ -0,0 +1,37 @@
+# Generated by Home Manager.
+# See http://alot.readthedocs.io/en/latest/configuration/config_options.html
+
+auto_remove_unread = True
+handle_mouse = True
+initial_command = search tag:inbox AND NOT tag:killed
+prefer_plaintext = True
+
+
+[tags]
+[bindings]
+
+
+[[bufferlist]]
+
+[[search]]
+
+[[envelope]]
+
+[[taglist]]
+
+[[thread]]
+
+
+[accounts]
+
+[[hm@example.com]]
+address=hm@example.com
+draft_box=maildir:///home/hm-user/Mail/hm@example.com/Drafts
+realname=H. M. Test
+sendmail_command=
+sent_box=maildir:///home/hm-user/Mail/hm@example.com/Sent
+auto_remove_unread = True
+ask_subject = False
+handle_mouse = True
+
+[[[abook]]]
diff --git a/infra/libkookie/home-manager/tests/modules/programs/alot/alot.nix b/infra/libkookie/home-manager/tests/modules/programs/alot/alot.nix
new file mode 100644
index 000000000000..40028b7aac65
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/alot/alot.nix
@@ -0,0 +1,36 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ imports = [ ../../accounts/email-test-accounts.nix ];
+
+ config = {
+ accounts.email.accounts = {
+ "hm@example.com" = {
+ primary = true;
+ notmuch.enable = true;
+ alot = {
+ contactCompletion = { };
+ extraConfig = ''
+ auto_remove_unread = True
+ ask_subject = False
+ handle_mouse = True
+ '';
+ };
+ imap.port = 993;
+ };
+ };
+
+ programs.alot = { enable = true; };
+
+ nixpkgs.overlays =
+ [ (self: super: { alot = pkgs.writeScriptBin "dummy-alot" ""; }) ];
+
+ nmt.script = ''
+ assertFileExists home-files/.config/alot/config
+ assertFileContent home-files/.config/alot/config ${./alot-expected.conf}
+ '';
+ };
+}
+
diff --git a/infra/libkookie/home-manager/tests/modules/programs/alot/default.nix b/infra/libkookie/home-manager/tests/modules/programs/alot/default.nix
new file mode 100644
index 000000000000..9c912fdb43a0
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/alot/default.nix
@@ -0,0 +1 @@
+{ alot = ./alot.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/programs/aria2/default.nix b/infra/libkookie/home-manager/tests/modules/programs/aria2/default.nix
new file mode 100644
index 000000000000..2964841d0980
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/aria2/default.nix
@@ -0,0 +1 @@
+{ aria2-settings = ./settings.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/programs/aria2/settings.nix b/infra/libkookie/home-manager/tests/modules/programs/aria2/settings.nix
new file mode 100644
index 000000000000..0b5a52d90c52
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/aria2/settings.nix
@@ -0,0 +1,41 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.aria2 = {
+ enable = true;
+
+ settings = {
+ listen-port = 60000;
+ dht-listen-port = 60000;
+ seed-ratio = 1.0;
+ max-upload-limit = "50K";
+ ftp-pasv = true;
+ };
+
+ extraConfig = ''
+ # Extra aria2 configuration.
+ '';
+ };
+
+ nixpkgs.overlays =
+ [ (self: super: { aria2 = pkgs.writeScriptBin "dummy-aria2" ""; }) ];
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/.config/aria2/aria2.conf \
+ ${
+ pkgs.writeText "aria2-expected-config.conf" ''
+ dht-listen-port=60000
+ ftp-pasv=true
+ listen-port=60000
+ max-upload-limit=50K
+ seed-ratio=1.000000
+ # Extra aria2 configuration.
+ ''
+ }
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/autojump/default-settings.nix b/infra/libkookie/home-manager/tests/modules/programs/autojump/default-settings.nix
new file mode 100644
index 000000000000..2f6670272722
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/autojump/default-settings.nix
@@ -0,0 +1,13 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.autojump.enable = true;
+
+ nmt.script = ''
+ assertFileExists home-path/bin/autojump
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/autojump/default.nix b/infra/libkookie/home-manager/tests/modules/programs/autojump/default.nix
new file mode 100644
index 000000000000..eaaaba3b4b28
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/autojump/default.nix
@@ -0,0 +1 @@
+{ autojump = ./default-settings.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/programs/autorandr/basic-configuration.conf b/infra/libkookie/home-manager/tests/modules/programs/autorandr/basic-configuration.conf
new file mode 100644
index 000000000000..681574cf204f
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/autorandr/basic-configuration.conf
@@ -0,0 +1,10 @@
+output DP1
+off
+
+output DP2
+pos 0x0
+crtc 0
+primary
+mode 1920x1080
+transform 0.600000,0.000000,0.000000,0.000000,0.600000,0.000000,0.000000,0.000000,1.000000
+scale 2x4 \ No newline at end of file
diff --git a/infra/libkookie/home-manager/tests/modules/programs/autorandr/basic-configuration.nix b/infra/libkookie/home-manager/tests/modules/programs/autorandr/basic-configuration.nix
new file mode 100644
index 000000000000..190511016fe2
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/autorandr/basic-configuration.nix
@@ -0,0 +1,48 @@
+{ config, pkgs, ... }:
+
+{
+ config = {
+ programs.autorandr = {
+ enable = true;
+ profiles = {
+ default = {
+ fingerprint = {
+ DP1 = "XXX";
+ DP2 = "YYY";
+ };
+ config = {
+ DP1.enable = false;
+ DP2 = {
+ crtc = 0;
+ primary = true;
+ position = "0x0";
+ mode = "1920x1080";
+ scale = {
+ x = 2;
+ y = 4;
+ };
+ transform = [
+ [ 0.6 0.0 0.0 ] # a b c
+ [ 0.0 0.6 0.0 ] # d e f
+ [ 0.0 0.0 1.0 ] # g h i
+ ];
+ };
+ };
+ };
+ };
+ };
+
+ nmt.script = ''
+ config=home-files/.config/autorandr/default/config
+ setup=home-files/.config/autorandr/default/setup
+
+ assertFileExists $setup
+ assertFileRegex $setup 'DP1 XXX'
+ assertFileRegex $setup 'DP2 YYY'
+
+ assertFileExists $config
+ assertFileContent $config \
+ ${./basic-configuration.conf}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/autorandr/default.nix b/infra/libkookie/home-manager/tests/modules/programs/autorandr/default.nix
new file mode 100644
index 000000000000..5f12d4093804
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/autorandr/default.nix
@@ -0,0 +1 @@
+{ autorandr-basic-configuration = ./basic-configuration.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/programs/bash/default.nix b/infra/libkookie/home-manager/tests/modules/programs/bash/default.nix
new file mode 100644
index 000000000000..e9f431cd2b99
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/bash/default.nix
@@ -0,0 +1,4 @@
+{
+ bash-logout = ./logout.nix;
+ bash-session-variables = ./session-variables.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/bash/logout-expected.txt b/infra/libkookie/home-manager/tests/modules/programs/bash/logout-expected.txt
new file mode 100644
index 000000000000..9462f58f732a
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/bash/logout-expected.txt
@@ -0,0 +1,4 @@
+# -*- mode: sh -*-
+
+clear-console
+
diff --git a/infra/libkookie/home-manager/tests/modules/programs/bash/logout.nix b/infra/libkookie/home-manager/tests/modules/programs/bash/logout.nix
new file mode 100644
index 000000000000..8f96dc7e1ae2
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/bash/logout.nix
@@ -0,0 +1,22 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.bash = {
+ enable = true;
+
+ logoutExtra = ''
+ clear-console
+ '';
+ };
+
+ nmt.script = ''
+ assertFileExists home-files/.bash_logout
+ assertFileContent \
+ home-files/.bash_logout \
+ ${./logout-expected.txt}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/bash/session-variables-expected.txt b/infra/libkookie/home-manager/tests/modules/programs/bash/session-variables-expected.txt
new file mode 100644
index 000000000000..0d93217a9151
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/bash/session-variables-expected.txt
@@ -0,0 +1,8 @@
+# -*- mode: sh -*-
+
+. "/home/hm-user/.nix-profile/etc/profile.d/hm-session-vars.sh"
+
+export V1="v1"
+export V2="v2-v1"
+
+
diff --git a/infra/libkookie/home-manager/tests/modules/programs/bash/session-variables.nix b/infra/libkookie/home-manager/tests/modules/programs/bash/session-variables.nix
new file mode 100644
index 000000000000..1ef65a34442b
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/bash/session-variables.nix
@@ -0,0 +1,23 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.bash = {
+ enable = true;
+
+ sessionVariables = {
+ V1 = "v1";
+ V2 = "v2-${config.programs.bash.sessionVariables.V1}";
+ };
+ };
+
+ nmt.script = ''
+ assertFileExists home-files/.profile
+ assertFileContent \
+ home-files/.profile \
+ ${./session-variables-expected.txt}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/browserpass/browserpass.nix b/infra/libkookie/home-manager/tests/modules/programs/browserpass/browserpass.nix
new file mode 100644
index 000000000000..9189a445ac04
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/browserpass/browserpass.nix
@@ -0,0 +1,29 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.browserpass = {
+ enable = true;
+ browsers = [ "chrome" "chromium" "firefox" "vivaldi" ];
+ };
+
+ nmt.script = if pkgs.stdenv.hostPlatform.isDarwin then ''
+ for dir in "Google/Chrome" "Chromium" "Mozilla" "Vivaldi"; do
+ assertFileExists "home-files/Library/Application Support/$dir/NativeMessagingHosts/com.github.browserpass.native.json"
+ done
+
+ for dir in "Google/Chrome" "Chromium" "Vivaldi"; do
+ assertFileExists "home-files/Library/Application Support/$dir/policies/managed/com.github.browserpass.native.json"
+ done
+ '' else ''
+ for dir in "google-chrome" "chromium" "vivaldi"; do
+ assertFileExists "home-files/.config/$dir/NativeMessagingHosts/com.github.browserpass.native.json"
+ assertFileExists "home-files/.config/$dir/policies/managed/com.github.browserpass.native.json"
+ done
+
+ assertFileExists "home-files/.mozilla/native-messaging-hosts/com.github.browserpass.native.json"
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/browserpass/default.nix b/infra/libkookie/home-manager/tests/modules/programs/browserpass/default.nix
new file mode 100644
index 000000000000..fa40ddcab317
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/browserpass/default.nix
@@ -0,0 +1 @@
+{ browserpass = ./browserpass.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/programs/dircolors/default.nix b/infra/libkookie/home-manager/tests/modules/programs/dircolors/default.nix
new file mode 100644
index 000000000000..a82e2b859af8
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/dircolors/default.nix
@@ -0,0 +1 @@
+{ dircolors-settings = ./settings.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/programs/dircolors/settings-expected.conf b/infra/libkookie/home-manager/tests/modules/programs/dircolors/settings-expected.conf
new file mode 100644
index 000000000000..17bc247c0c20
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/dircolors/settings-expected.conf
@@ -0,0 +1,133 @@
+# Extra dircolors configuration.
+
+.7z 01;31
+.aac 00;36
+.ace 01;31
+.alz 01;31
+.arc 01;31
+.arj 01;31
+.asf 01;35
+.au 00;36
+.avi 01;35
+.bmp 01;35
+.bz 01;31
+.bz2 01;31
+.cab 01;31
+.cgm 01;35
+.cpio 01;31
+.csh 01;32
+.deb 01;31
+.dl 01;35
+.dwm 01;31
+.dz 01;31
+.ear 01;31
+.emf 01;35
+.esd 01;31
+.flac 00;36
+.flc 01;35
+.fli 01;35
+.flv 01;35
+.gif 01;35
+.gl 01;35
+.gz 01;31
+.jar 01;31
+.jpeg 01;35
+.jpg 01;35
+.lha 01;31
+.lrz 01;31
+.lz 01;31
+.lz4 01;31
+.lzh 01;31
+.lzma 01;31
+.lzo 01;31
+.m2v 01;35
+.m4a 00;36
+.m4v 01;35
+.mid 00;36
+.midi 00;36
+.mjpeg 01;35
+.mjpg 01;35
+.mka 00;36
+.mkv 01;35
+.mng 01;35
+.mov 01;35
+.mp3 00;36
+.mp4 01;35
+.mp4v 01;35
+.mpc 00;36
+.mpeg 01;35
+.mpg 01;35
+.nuv 01;35
+.oga 00;36
+.ogg 00;36
+.ogm 01;35
+.ogv 01;35
+.ogx 01;35
+.opus 00;36
+.pbm 01;35
+.pcx 01;35
+.pgm 01;35
+.png 01;35
+.ppm 01;35
+.qt 01;35
+.ra 00;36
+.rar 01;31
+.rm 01;35
+.rmvb 01;35
+.rpm 01;31
+.rz 01;31
+.sar 01;31
+.sh 01;32
+.spx 00;36
+.svg 01;35
+.svgz 01;35
+.swm 01;31
+.t7z 01;31
+.tar 01;31
+.taz 01;31
+.tbz 01;31
+.tbz2 01;31
+.tga 01;35
+.tgz 01;31
+.tif 01;35
+.tiff 01;35
+.tlz 01;31
+.txz 01;31
+.tz 01;31
+.tzo 01;31
+.tzst 01;31
+.vob 01;35
+.war 01;31
+.wav 00;36
+.webm 01;35
+.wim 01;31
+.wmv 01;35
+.xbm 01;35
+.xcf 01;35
+.xpm 01;35
+.xspf 00;36
+.xwd 01;35
+.xz 01;31
+.yuv 01;35
+.z 01;31
+.zip 01;31
+.zoo 01;31
+.zst 01;31
+BLK 40;33;01
+CAPABILITY 30;41
+CHR 40;33;01
+DIR 01;34
+DOOR 01;35
+EXEC 01;32
+FIFO 40;33
+LINK 01;36
+MISSING 00
+MULTIHARDLINK 00
+ORPHAN 40;31;01
+OTHER_WRITABLE 30;46
+RESET 0
+SETGID 30;43
+SETUID 37;41
+SOCK 01;35
+STICKY 37;44
+STICKY_OTHER_WRITABLE 30;42
diff --git a/infra/libkookie/home-manager/tests/modules/programs/dircolors/settings.nix b/infra/libkookie/home-manager/tests/modules/programs/dircolors/settings.nix
new file mode 100644
index 000000000000..9ca676ef9c05
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/dircolors/settings.nix
@@ -0,0 +1,27 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.dircolors = {
+ enable = true;
+
+ settings = {
+ OTHER_WRITABLE = "30;46";
+ ".sh" = "01;32";
+ ".csh" = "01;32";
+ };
+
+ extraConfig = ''
+ # Extra dircolors configuration.
+ '';
+ };
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/.dir_colors \
+ ${./settings-expected.conf}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/direnv/bash.nix b/infra/libkookie/home-manager/tests/modules/programs/direnv/bash.nix
new file mode 100644
index 000000000000..db0d6b391f59
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/direnv/bash.nix
@@ -0,0 +1,17 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.bash.enable = true;
+ programs.direnv.enable = true;
+
+ nmt.script = ''
+ assertFileExists home-files/.bashrc
+ assertFileRegex \
+ home-files/.bashrc \
+ 'eval "\$(/nix/store/.*direnv.*/bin/direnv hook bash)"'
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/direnv/default.nix b/infra/libkookie/home-manager/tests/modules/programs/direnv/default.nix
new file mode 100644
index 000000000000..3efad2b69458
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/direnv/default.nix
@@ -0,0 +1,6 @@
+{
+ direnv-bash = ./bash.nix;
+ direnv-nix-direnv = ./nix-direnv.nix;
+ direnv-stdlib = ./stdlib.nix;
+ direnv-stdlib-and-nix-direnv = ./stdlib-and-nix-direnv.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/direnv/nix-direnv.nix b/infra/libkookie/home-manager/tests/modules/programs/direnv/nix-direnv.nix
new file mode 100644
index 000000000000..57b3907dda87
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/direnv/nix-direnv.nix
@@ -0,0 +1,18 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.bash.enable = true;
+ programs.direnv.enable = true;
+ programs.direnv.enableNixDirenvIntegration = true;
+
+ nmt.script = ''
+ assertFileExists home-files/.bashrc
+ assertFileRegex \
+ home-files/.config/direnv/direnvrc \
+ 'source /nix/store/.*nix-direnv.*/share/nix-direnv/direnvrc'
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/direnv/stdlib-and-nix-direnv.nix b/infra/libkookie/home-manager/tests/modules/programs/direnv/stdlib-and-nix-direnv.nix
new file mode 100644
index 000000000000..1dc224317a2f
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/direnv/stdlib-and-nix-direnv.nix
@@ -0,0 +1,23 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let expectedContent = "something important";
+in {
+ config = {
+ programs.bash.enable = true;
+ programs.direnv.enable = true;
+ programs.direnv.enableNixDirenvIntegration = true;
+ programs.direnv.stdlib = expectedContent;
+
+ nmt.script = ''
+ assertFileExists home-files/.bashrc
+ assertFileRegex \
+ home-files/.config/direnv/direnvrc \
+ 'source /nix/store/.*nix-direnv.*/share/nix-direnv/direnvrc'
+ assertFileRegex \
+ home-files/.config/direnv/direnvrc \
+ '${expectedContent}'
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/direnv/stdlib.nix b/infra/libkookie/home-manager/tests/modules/programs/direnv/stdlib.nix
new file mode 100644
index 000000000000..1d06a0bc2a97
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/direnv/stdlib.nix
@@ -0,0 +1,19 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let expectedContent = "something important";
+in {
+ config = {
+ programs.bash.enable = true;
+ programs.direnv.enable = true;
+ programs.direnv.stdlib = expectedContent;
+
+ nmt.script = ''
+ assertFileExists home-files/.bashrc
+ assertFileRegex \
+ home-files/.config/direnv/direnvrc \
+ '${expectedContent}'
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/feh/default.nix b/infra/libkookie/home-manager/tests/modules/programs/feh/default.nix
new file mode 100644
index 000000000000..48bab8ab6e79
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/feh/default.nix
@@ -0,0 +1,4 @@
+{
+ feh-empty-config = ./feh-empty-settings.nix;
+ feh-bindings = ./feh-bindings.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/feh/feh-bindings-expected-buttons b/infra/libkookie/home-manager/tests/modules/programs/feh/feh-bindings-expected-buttons
new file mode 100644
index 000000000000..f285ada64f26
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/feh/feh-bindings-expected-buttons
@@ -0,0 +1,4 @@
+zoom_in
+next_img C-4
+prev_img 3 C-3
+zoom_out 4
diff --git a/infra/libkookie/home-manager/tests/modules/programs/feh/feh-bindings-expected-keys b/infra/libkookie/home-manager/tests/modules/programs/feh/feh-bindings-expected-keys
new file mode 100644
index 000000000000..ad558e6aa7ce
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/feh/feh-bindings-expected-keys
@@ -0,0 +1,3 @@
+zoom_in
+prev_img h Left
+zoom_out minus
diff --git a/infra/libkookie/home-manager/tests/modules/programs/feh/feh-bindings.nix b/infra/libkookie/home-manager/tests/modules/programs/feh/feh-bindings.nix
new file mode 100644
index 000000000000..f6b9e5b6e944
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/feh/feh-bindings.nix
@@ -0,0 +1,33 @@
+{ pkgs, ... }:
+
+{
+ config = {
+ programs.feh.enable = true;
+
+ programs.feh.buttons = {
+ zoom_in = null;
+ zoom_out = 4;
+ next_img = "C-4";
+ prev_img = [ 3 "C-3" ];
+ };
+
+ programs.feh.keybindings = {
+ zoom_in = null;
+ zoom_out = "minus";
+ prev_img = [ "h" "Left" ];
+ };
+
+ nixpkgs.overlays =
+ [ (self: super: { feh = pkgs.writeScriptBin "dummy-feh" ""; }) ];
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/.config/feh/buttons \
+ ${./feh-bindings-expected-buttons}
+
+ assertFileContent \
+ home-files/.config/feh/keys \
+ ${./feh-bindings-expected-keys}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/feh/feh-empty-settings.nix b/infra/libkookie/home-manager/tests/modules/programs/feh/feh-empty-settings.nix
new file mode 100644
index 000000000000..ad0d15153d2a
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/feh/feh-empty-settings.nix
@@ -0,0 +1,15 @@
+{ pkgs, ... }:
+
+{
+ config = {
+ programs.feh.enable = true;
+
+ nixpkgs.overlays =
+ [ (self: super: { feh = pkgs.writeScriptBin "dummy-feh" ""; }) ];
+
+ nmt.script = ''
+ assertPathNotExists home-files/.config/feh/buttons
+ assertPathNotExists home-files/.config/feh/keys
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/firefox/default.nix b/infra/libkookie/home-manager/tests/modules/programs/firefox/default.nix
new file mode 100644
index 000000000000..6612a9ac978a
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/firefox/default.nix
@@ -0,0 +1,4 @@
+{
+ firefox-profile-settings = ./profile-settings.nix;
+ firefox-state-version-19_09 = ./state-version-19_09.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/firefox/profile-settings-expected-user.js b/infra/libkookie/home-manager/tests/modules/programs/firefox/profile-settings-expected-user.js
new file mode 100644
index 000000000000..0edd47b9101d
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/firefox/profile-settings-expected-user.js
@@ -0,0 +1,6 @@
+// Generated by Home Manager.
+
+user_pref("general.smoothScroll", false);
+
+
+
diff --git a/infra/libkookie/home-manager/tests/modules/programs/firefox/profile-settings.nix b/infra/libkookie/home-manager/tests/modules/programs/firefox/profile-settings.nix
new file mode 100644
index 000000000000..8c5fb4ec1fc5
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/firefox/profile-settings.nix
@@ -0,0 +1,36 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.firefox = {
+ enable = true;
+ profiles.test.settings = { "general.smoothScroll" = false; };
+ };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ firefox-unwrapped = pkgs.runCommand "firefox-0" {
+ meta.description = "I pretend to be Firefox";
+ preferLocalBuild = true;
+ allowSubstitutes = false;
+ } ''
+ mkdir -p "$out/bin"
+ touch "$out/bin/firefox"
+ chmod 755 "$out/bin/firefox"
+ '';
+ })
+ ];
+
+ nmt.script = ''
+ assertFileRegex \
+ home-path/bin/firefox \
+ MOZ_APP_LAUNCHER
+
+ assertFileContent \
+ home-files/.mozilla/firefox/test/user.js \
+ ${./profile-settings-expected-user.js}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/firefox/state-version-19_09.nix b/infra/libkookie/home-manager/tests/modules/programs/firefox/state-version-19_09.nix
new file mode 100644
index 000000000000..27dc867ad295
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/firefox/state-version-19_09.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ home.stateVersion = "19.09";
+
+ programs.firefox.enable = true;
+
+ nixpkgs.overlays = [
+ (self: super: {
+ firefox-unwrapped = pkgs.runCommand "firefox-0" {
+ meta.description = "I pretend to be Firefox";
+ preferLocalBuild = true;
+ allowSubstitutes = false;
+ } ''
+ mkdir -p "$out/bin"
+ touch "$out/bin/firefox"
+ chmod 755 "$out/bin/firefox"
+ '';
+ })
+ ];
+
+ nmt.script = ''
+ assertFileRegex \
+ home-path/bin/firefox \
+ MOZ_APP_LAUNCHER
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/fish/default.nix b/infra/libkookie/home-manager/tests/modules/programs/fish/default.nix
new file mode 100644
index 000000000000..99fe81367005
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/fish/default.nix
@@ -0,0 +1,5 @@
+{
+ fish-functions = ./functions.nix;
+ fish-no-functions = ./no-functions.nix;
+ fish-plugins = ./plugins.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/fish/functions.nix b/infra/libkookie/home-manager/tests/modules/programs/fish/functions.nix
new file mode 100644
index 000000000000..424d0a288c7f
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/fish/functions.nix
@@ -0,0 +1,48 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ func = pkgs.writeText "func.fish" ''
+ function func
+ echo "Hello"
+ end
+ '';
+
+ funcEvent = pkgs.writeText "func-event.fish" ''
+ function func-event --on-event="fish_command_not_found"
+ echo "Not found!"
+ end
+ '';
+
+in {
+ config = {
+ programs.fish = {
+ enable = true;
+
+ functions = {
+ func = ''echo "Hello"'';
+ func-event = {
+ body = ''echo "Not found!"'';
+ onEvent = "fish_command_not_found";
+ };
+ };
+ };
+
+ nmt = {
+ description =
+ "if fish.function is set, check file exists and contents match";
+ script = ''
+ assertFileExists home-files/.config/fish/functions/func.fish
+ echo ${func}
+ assertFileContent home-files/.config/fish/functions/func.fish ${func}
+
+ assertFileExists home-files/.config/fish/functions/func-event.fish
+ echo ${funcEvent}
+ assertFileContent home-files/.config/fish/functions/func-event.fish ${funcEvent}
+ '';
+
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/fish/no-functions.nix b/infra/libkookie/home-manager/tests/modules/programs/fish/no-functions.nix
new file mode 100644
index 000000000000..c817b388953c
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/fish/no-functions.nix
@@ -0,0 +1,22 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.fish = {
+ enable = true;
+
+ functions = { };
+ };
+
+ nmt = {
+ description =
+ "if fish.functions is blank, the functions folder should not exist.";
+ script = ''
+ assertPathNotExists home-files/.config/fish/functions
+ '';
+
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/fish/plugins.nix b/infra/libkookie/home-manager/tests/modules/programs/fish/plugins.nix
new file mode 100644
index 000000000000..657c33f39bfe
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/fish/plugins.nix
@@ -0,0 +1,60 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ fooPluginSrc = pkgs.writeText "fooPluginSrc" "";
+
+ generatedConfdFile = pkgs.writeText "plugin-foo.fish" ''
+ # Plugin foo
+ set -l plugin_dir ${fooPluginSrc}
+
+ # Set paths to import plugin components
+ if test -d $plugin_dir/functions
+ set fish_function_path $fish_function_path[1] $plugin_dir/functions $fish_function_path[2..-1]
+ end
+
+ if test -d $plugin_dir/completions
+ set fish_complete_path $fish_complete_path[1] $plugin_dir/completions $fish_complete_path[2..-1]
+ end
+
+ # Source initialization code if it exists.
+ if test -d $plugin_dir/conf.d
+ for f in $plugin_dir/conf.d/*.fish
+ source $f
+ end
+ end
+
+ if test -f $plugin_dir/key_bindings.fish
+ source $plugin_dir/key_bindings.fish
+ end
+
+ if test -f $plugin_dir/init.fish
+ source $plugin_dir/init.fish
+ end
+ '';
+
+in {
+ config = {
+ programs.fish = {
+ enable = true;
+
+ plugins = [{
+ name = "foo";
+ src = fooPluginSrc;
+ }];
+ };
+
+ nmt = {
+ description =
+ "if fish.plugins set, check conf.d file exists and contents match";
+ script = ''
+ assertDirectoryExists home-files/.config/fish/conf.d
+ assertFileExists home-files/.config/fish/conf.d/plugin-foo.fish
+ assertFileContent home-files/.config/fish/conf.d/plugin-foo.fish ${generatedConfdFile}
+ '';
+
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/getmail/default.nix b/infra/libkookie/home-manager/tests/modules/programs/getmail/default.nix
new file mode 100644
index 000000000000..cb789a90d64b
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/getmail/default.nix
@@ -0,0 +1 @@
+{ getmail = ./getmail.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/programs/getmail/getmail-expected.conf b/infra/libkookie/home-manager/tests/modules/programs/getmail/getmail-expected.conf
new file mode 100644
index 000000000000..90dc963e5742
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/getmail/getmail-expected.conf
@@ -0,0 +1,16 @@
+# Generated by Home-Manager.
+[retriever]
+type = SimpleIMAPSSLRetriever
+server = imap.example.com
+port = 993
+username = home.manager
+password_command = ('password-command')
+mailboxes = ( 'INBOX', 'Sent', 'Work' )
+
+[destination]
+type = MDA_external
+path = /bin/maildrop
+
+[options]
+delete = false
+read_all = true
diff --git a/infra/libkookie/home-manager/tests/modules/programs/getmail/getmail.nix b/infra/libkookie/home-manager/tests/modules/programs/getmail/getmail.nix
new file mode 100644
index 000000000000..b0d979c46728
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/getmail/getmail.nix
@@ -0,0 +1,28 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ imports = [ ../../accounts/email-test-accounts.nix ];
+
+ config = {
+ accounts.email.accounts = {
+ "hm@example.com" = {
+ getmail = {
+ enable = true;
+ mailboxes = [ "INBOX" "Sent" "Work" ];
+ destinationCommand = "/bin/maildrop";
+ delete = false;
+ };
+ imap.port = 993;
+ };
+ };
+
+ nmt.script = ''
+ assertFileExists home-files/.getmail/getmailhm@example.com
+ assertFileContent home-files/.getmail/getmailhm@example.com ${
+ ./getmail-expected.conf
+ }
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/gh/config-file.nix b/infra/libkookie/home-manager/tests/modules/programs/gh/config-file.nix
new file mode 100644
index 000000000000..0c0186942052
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/gh/config-file.nix
@@ -0,0 +1,27 @@
+{ config, lib, pkgs, ... }:
+
+{
+ config = {
+ programs.gh = {
+ enable = true;
+ aliases = { co = "pr checkout"; };
+ editor = "vim";
+ };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ gitAndTools = super.gitAndTools // {
+ gh = pkgs.writeScriptBin "dummy-gh" "";
+ };
+ })
+ ];
+
+ nmt.script = ''
+ assertFileExists home-files/.config/gh/config.yml
+ assertFileContent home-files/.config/gh/config.yml ${
+ builtins.toFile "config-file.yml" ''
+ {"aliases":{"co":"pr checkout"},"editor":"vim","gitProtocol":"https"}''
+ }
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/gh/default.nix b/infra/libkookie/home-manager/tests/modules/programs/gh/default.nix
new file mode 100644
index 000000000000..680e8b2756a6
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/gh/default.nix
@@ -0,0 +1 @@
+{ gh-config-file = ./config-file.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/programs/git/default.nix b/infra/libkookie/home-manager/tests/modules/programs/git/default.nix
new file mode 100644
index 000000000000..45aface8d268
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/git/default.nix
@@ -0,0 +1,5 @@
+{
+ git-with-email = ./git-with-email.nix;
+ git-with-most-options = ./git.nix;
+ git-with-str-extra-config = ./git-with-str-extra-config.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/git/git-expected-include.conf b/infra/libkookie/home-manager/tests/modules/programs/git/git-expected-include.conf
new file mode 100644
index 000000000000..f05c7b6c7fc6
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/git/git-expected-include.conf
@@ -0,0 +1,3 @@
+[user]
+ email = "user@example.org"
+ name = "John Doe"
diff --git a/infra/libkookie/home-manager/tests/modules/programs/git/git-expected.conf b/infra/libkookie/home-manager/tests/modules/programs/git/git-expected.conf
new file mode 100644
index 000000000000..fe258a0d3be1
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/git/git-expected.conf
@@ -0,0 +1,58 @@
+[alias]
+ a1 = "foo"
+ a2 = "baz"
+ escapes = "\"\\n\t"
+
+[commit]
+ gpgSign = true
+
+[core]
+ pager = "@delta@/bin/delta"
+
+[delta]
+ features = "decorations"
+ whitespace-error-style = "22 reverse"
+
+[delta "decorations"]
+ commit-decoration-style = "bold yellow box ul"
+ file-decoration-style = "none"
+ file-style = "bold yellow ul"
+
+[extra]
+ boolean = true
+ integer = 38
+ multiple = 1
+ multiple = 2
+ name = "value"
+
+[extra "backcompat.with.dots"]
+ previously = "worked"
+
+[extra "subsection"]
+ value = "test"
+
+[filter "lfs"]
+ clean = "git-lfs clean -- %f"
+ process = "git-lfs filter-process"
+ required = true
+ smudge = "git-lfs smudge -- %f"
+
+[gpg]
+ program = "path-to-gpg"
+
+[interactive]
+ diffFilter = "@delta@/bin/delta --color-only"
+
+[user]
+ email = "user@example.org"
+ name = "John Doe"
+ signingKey = "00112233445566778899AABBCCDDEEFF"
+
+[include]
+ path = "~/path/to/config.inc"
+
+[includeIf "gitdir:~/src/dir"]
+ path = "~/path/to/conditional.inc"
+
+[includeIf "gitdir:~/src/dir"]
+ path = "@git_include_path@"
diff --git a/infra/libkookie/home-manager/tests/modules/programs/git/git-with-email-expected.conf b/infra/libkookie/home-manager/tests/modules/programs/git/git-with-email-expected.conf
new file mode 100644
index 000000000000..f48b7c33334d
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/git/git-with-email-expected.conf
@@ -0,0 +1,15 @@
+[sendemail "hm-account"]
+ from = "hm@example.org"
+ smtpEncryption = "tls"
+ smtpServer = "smtp.example.org"
+ smtpUser = "home.manager.jr"
+
+[sendemail "hm@example.com"]
+ from = "hm@example.com"
+ smtpEncryption = "ssl"
+ smtpServer = "smtp.example.com"
+ smtpUser = "home.manager"
+
+[user]
+ email = "hm@example.com"
+ name = "H. M. Test"
diff --git a/infra/libkookie/home-manager/tests/modules/programs/git/git-with-email.nix b/infra/libkookie/home-manager/tests/modules/programs/git/git-with-email.nix
new file mode 100644
index 000000000000..49089a97a0c1
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/git/git-with-email.nix
@@ -0,0 +1,38 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ imports = [ ../../accounts/email-test-accounts.nix ];
+
+ config = {
+ programs.git = {
+ enable = true;
+ package = pkgs.gitMinimal;
+ userEmail = "hm@example.com";
+ userName = "H. M. Test";
+ };
+
+ home.stateVersion = "20.09";
+
+ nmt.script = ''
+ function assertGitConfig() {
+ local value
+ value=$(${pkgs.gitMinimal}/bin/git config \
+ --file $TESTED/home-files/.config/git/config \
+ --get $1)
+ if [[ $value != $2 ]]; then
+ fail "Expected option '$1' to have value '$2' but it was '$value'"
+ fi
+ }
+
+ assertFileExists home-files/.config/git/config
+ assertFileContent home-files/.config/git/config ${
+ ./git-with-email-expected.conf
+ }
+
+ assertGitConfig "sendemail.hm@example.com.from" "hm@example.com"
+ assertGitConfig "sendemail.hm-account.from" "hm@example.org"
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/git/git-with-str-extra-config-expected.conf b/infra/libkookie/home-manager/tests/modules/programs/git/git-with-str-extra-config-expected.conf
new file mode 100644
index 000000000000..071268e831c2
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/git/git-with-str-extra-config-expected.conf
@@ -0,0 +1,5 @@
+This can be anything.
+
+[user]
+ email = "user@example.org"
+ name = "John Doe"
diff --git a/infra/libkookie/home-manager/tests/modules/programs/git/git-with-str-extra-config.nix b/infra/libkookie/home-manager/tests/modules/programs/git/git-with-str-extra-config.nix
new file mode 100644
index 000000000000..3dbc497a5eab
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/git/git-with-str-extra-config.nix
@@ -0,0 +1,23 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.git = {
+ enable = true;
+ package = pkgs.gitMinimal;
+ extraConfig = ''
+ This can be anything.
+ '';
+ userEmail = "user@example.org";
+ userName = "John Doe";
+ };
+
+ nmt.script = ''
+ assertFileExists home-files/.config/git/config
+ assertFileContent home-files/.config/git/config \
+ ${./git-with-str-extra-config-expected.conf}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/git/git.nix b/infra/libkookie/home-manager/tests/modules/programs/git/git.nix
new file mode 100644
index 000000000000..981d1934821b
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/git/git.nix
@@ -0,0 +1,100 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ gitInclude = {
+ user = {
+ name = "John Doe";
+ email = "user@example.org";
+ };
+ };
+
+ substituteExpected = path:
+ pkgs.substituteAll {
+ src = path;
+ git_include_path = pkgs.writeText "contents"
+ (builtins.readFile ./git-expected-include.conf);
+ };
+
+in {
+ config = {
+ programs.git = mkMerge [
+ {
+ enable = true;
+ package = pkgs.gitMinimal;
+ aliases = {
+ a1 = "foo";
+ a2 = "bar";
+ escapes = ''"\n '';
+ };
+ extraConfig = {
+ extra = {
+ name = "value";
+ multiple = [ 1 ];
+ };
+ };
+ ignores = [ "*~" "*.swp" ];
+ includes = [
+ { path = "~/path/to/config.inc"; }
+ {
+ path = "~/path/to/conditional.inc";
+ condition = "gitdir:~/src/dir";
+ }
+ {
+ condition = "gitdir:~/src/dir";
+ contents = gitInclude;
+ }
+ ];
+ signing = {
+ gpgPath = "path-to-gpg";
+ key = "00112233445566778899AABBCCDDEEFF";
+ signByDefault = true;
+ };
+ userEmail = "user@example.org";
+ userName = "John Doe";
+ lfs.enable = true;
+ delta = {
+ enable = true;
+ options = {
+ features = "decorations";
+ whitespace-error-style = "22 reverse";
+ decorations = {
+ commit-decoration-style = "bold yellow box ul";
+ file-style = "bold yellow ul";
+ file-decoration-style = "none";
+ };
+ };
+ };
+ }
+
+ {
+ aliases.a2 = mkForce "baz";
+ extraConfig."extra \"backcompat.with.dots\"".previously = "worked";
+ extraConfig.extra.boolean = true;
+ extraConfig.extra.integer = 38;
+ extraConfig.extra.multiple = [ 2 ];
+ extraConfig.extra.subsection.value = "test";
+ }
+ ];
+
+ nixpkgs.overlays = [
+ (self: super: {
+ git-lfs = pkgs.writeScriptBin "dummy-git-lfs" "";
+ gitAndTools = super.gitAndTools // {
+ delta = pkgs.writeScriptBin "dummy-delta" "" // {
+ outPath = "@delta@";
+ };
+ };
+ })
+ ];
+
+ nmt.script = ''
+ assertFileExists home-files/.config/git/config
+ assertFileContent home-files/.config/git/config ${
+ substituteExpected ./git-expected.conf
+ }
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/gpg/default.nix b/infra/libkookie/home-manager/tests/modules/programs/gpg/default.nix
new file mode 100644
index 000000000000..7fed2cdcc69c
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/gpg/default.nix
@@ -0,0 +1 @@
+{ gpg-override-defaults = ./override-defaults.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/programs/gpg/override-defaults-expected.conf b/infra/libkookie/home-manager/tests/modules/programs/gpg/override-defaults-expected.conf
new file mode 100644
index 000000000000..3198183f7234
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/gpg/override-defaults-expected.conf
@@ -0,0 +1,19 @@
+cert-digest-algo SHA512
+charset utf-8
+default-preference-list SHA512 SHA384 SHA256 AES256 AES192 AES ZLIB BZIP2 ZIP Uncompressed
+fixed-list-mode
+keyid-format 0xlong
+list-options show-uid-validity
+
+no-emit-version
+no-symkey-cache
+personal-cipher-preferences AES256 AES192 AES
+personal-compress-preferences ZLIB BZIP2 ZIP Uncompressed
+personal-digest-preferences SHA512 SHA384 SHA256
+require-cross-certification
+s2k-cipher-algo AES128
+s2k-digest-algo SHA512
+throw-keyids
+use-agent
+verify-options show-uid-validity
+with-fingerprint \ No newline at end of file
diff --git a/infra/libkookie/home-manager/tests/modules/programs/gpg/override-defaults.nix b/infra/libkookie/home-manager/tests/modules/programs/gpg/override-defaults.nix
new file mode 100644
index 000000000000..850334dc589e
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/gpg/override-defaults.nix
@@ -0,0 +1,22 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.gpg = {
+ enable = true;
+
+ settings = {
+ no-comments = false;
+ s2k-cipher-algo = "AES128";
+ throw-keyids = true;
+ };
+ };
+
+ nmt.script = ''
+ assertFileExists home-files/.gnupg/gpg.conf
+ assertFileContent home-files/.gnupg/gpg.conf ${./override-defaults-expected.conf}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/i3status-rust/default.nix b/infra/libkookie/home-manager/tests/modules/programs/i3status-rust/default.nix
new file mode 100644
index 000000000000..50d8c7b853c1
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/i3status-rust/default.nix
@@ -0,0 +1,6 @@
+{
+ i3status-rust-with-default = ./with-default.nix;
+ i3status-rust-with-custom = ./with-custom.nix;
+ i3status-rust-with-extra-settings = ./with-extra-settings.nix;
+ i3status-rust-with-multiple-bars = ./with-multiple-bars.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/i3status-rust/with-custom.nix b/infra/libkookie/home-manager/tests/modules/programs/i3status-rust/with-custom.nix
new file mode 100644
index 000000000000..4ced6add57ba
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/i3status-rust/with-custom.nix
@@ -0,0 +1,186 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.i3status-rust = {
+ enable = true;
+ bars = {
+ custom = {
+ blocks = [
+ {
+ block = "disk_space";
+ path = "/";
+ alias = "/";
+ info_type = "available";
+ unit = "GB";
+ interval = 60;
+ warning = 20.0;
+ alert = 10.0;
+ }
+ {
+ block = "memory";
+ display_type = "memory";
+ format_mem = "{Mug}GB ({Mup}%)";
+ format_swap = "{SUp}%";
+ }
+ {
+ block = "cpu";
+ interval = 1;
+ format = "{barchart}";
+ }
+ {
+ block = "load";
+ interval = 1;
+ format = "{1m} {5m}";
+ }
+ {
+ block = "temperature";
+ collapsed = true;
+ interval = 10;
+ format = "{min}° min, {max}° max, {average}° avg";
+ chip = "*-isa-*";
+ }
+ {
+ block = "networkmanager";
+ ap_format = "{ssid} @ {strength}%";
+ on_click = "kcmshell5 kcm_networkmanagement";
+ }
+ {
+ block = "net";
+ device = "enp9s0u2u1u2c2";
+ speed_up = true;
+ interval = 5;
+ }
+ {
+ block = "speedtest";
+ bytes = true;
+ }
+ {
+ block = "xrandr";
+ interval =
+ 6000; # Because running the commands causes screen lag, see https://github.com/greshake/i3status-rust/issues/668
+ }
+ {
+ block = "sound";
+ format = "{output_name} {volume}%";
+ on_click = "pavucontrol --tab=3";
+ mappings = {
+ "alsa_output.pci-0000_00_1f.3.analog-stereo" = "";
+ "bluez_sink.70_26_05_DA_27_A4.a2dp_sink" = "";
+ };
+ }
+ {
+ block = "music";
+ player = "spotify";
+ buttons = [ "play" "prev" "next" ];
+ on_collapsed_click = "i3-msg '[class=Spotify] focus'";
+ }
+ {
+ block = "time";
+ interval = 60;
+ format = "%a %d.%m %R";
+ }
+ { block = "battery"; }
+ ];
+
+ icons = "awesome5";
+
+ theme = "gruvbox-dark";
+ };
+ };
+ };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ i3status-rust = pkgs.writeScriptBin "dummy-i3status-rust" "";
+ })
+ ];
+
+ nmt.script = ''
+ assertFileExists home-files/.config/i3status-rust/config-custom.toml
+ assertFileContent home-files/.config/i3status-rust/config-custom.toml \
+ ${
+ pkgs.writeText "i3status-rust-expected-config" ''
+ icons = "awesome5"
+ theme = "gruvbox-dark"
+ [[block]]
+ alert = 10
+ alias = "/"
+ block = "disk_space"
+ info_type = "available"
+ interval = 60
+ path = "/"
+ unit = "GB"
+ warning = 20
+
+ [[block]]
+ block = "memory"
+ display_type = "memory"
+ format_mem = "{Mug}GB ({Mup}%)"
+ format_swap = "{SUp}%"
+
+ [[block]]
+ block = "cpu"
+ format = "{barchart}"
+ interval = 1
+
+ [[block]]
+ block = "load"
+ format = "{1m} {5m}"
+ interval = 1
+
+ [[block]]
+ block = "temperature"
+ chip = "*-isa-*"
+ collapsed = true
+ format = "{min}° min, {max}° max, {average}° avg"
+ interval = 10
+
+ [[block]]
+ ap_format = "{ssid} @ {strength}%"
+ block = "networkmanager"
+ on_click = "kcmshell5 kcm_networkmanagement"
+
+ [[block]]
+ block = "net"
+ device = "enp9s0u2u1u2c2"
+ interval = 5
+ speed_up = true
+
+ [[block]]
+ block = "speedtest"
+ bytes = true
+
+ [[block]]
+ block = "xrandr"
+ interval = 6000
+
+ [[block]]
+ block = "sound"
+ format = "{output_name} {volume}%"
+ on_click = "pavucontrol --tab=3"
+
+ [block.mappings]
+ "alsa_output.pci-0000_00_1f.3.analog-stereo" = ""
+ "bluez_sink.70_26_05_DA_27_A4.a2dp_sink" = ""
+
+ [[block]]
+ block = "music"
+ buttons = ["play", "prev", "next"]
+ on_collapsed_click = "i3-msg '[class=Spotify] focus'"
+ player = "spotify"
+
+ [[block]]
+ block = "time"
+ format = "%a %d.%m %R"
+ interval = 60
+
+ [[block]]
+ block = "battery"
+ ''
+ }
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/i3status-rust/with-default.nix b/infra/libkookie/home-manager/tests/modules/programs/i3status-rust/with-default.nix
new file mode 100644
index 000000000000..b62c248c8a15
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/i3status-rust/with-default.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.i3status-rust = { enable = true; };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ i3status-rust = pkgs.writeScriptBin "dummy-i3status-rust" "";
+ })
+ ];
+
+ nmt.script = ''
+ assertFileExists home-files/.config/i3status-rust/config-default.toml
+ assertFileContent home-files/.config/i3status-rust/config-default.toml \
+ ${
+ pkgs.writeText "i3status-rust-expected-config" ''
+ icons = "none"
+ theme = "plain"
+ [[block]]
+ alert = 10
+ alias = "/"
+ block = "disk_space"
+ info_type = "available"
+ interval = 60
+ path = "/"
+ unit = "GB"
+ warning = 20
+
+ [[block]]
+ block = "memory"
+ display_type = "memory"
+ format_mem = "{Mup}%"
+ format_swap = "{SUp}%"
+
+ [[block]]
+ block = "cpu"
+ interval = 1
+
+ [[block]]
+ block = "load"
+ format = "{1m}"
+ interval = 1
+
+ [[block]]
+ block = "sound"
+
+ [[block]]
+ block = "time"
+ format = "%a %d/%m %R"
+ interval = 60
+ ''
+ }
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/i3status-rust/with-extra-settings.nix b/infra/libkookie/home-manager/tests/modules/programs/i3status-rust/with-extra-settings.nix
new file mode 100644
index 000000000000..16f3428a0fba
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/i3status-rust/with-extra-settings.nix
@@ -0,0 +1,202 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.i3status-rust = {
+ enable = true;
+ bars = {
+ extra-settings = {
+ blocks = [
+ {
+ block = "disk_space";
+ path = "/";
+ alias = "/";
+ info_type = "available";
+ unit = "GB";
+ interval = 60;
+ warning = 20.0;
+ alert = 10.0;
+ }
+ {
+ block = "memory";
+ display_type = "memory";
+ format_mem = "{Mug}GB ({Mup}%)";
+ format_swap = "{SUp}%";
+ }
+ {
+ block = "cpu";
+ interval = 1;
+ format = "{barchart}";
+ }
+ {
+ block = "load";
+ interval = 1;
+ format = "{1m} {5m}";
+ }
+ {
+ block = "temperature";
+ collapsed = true;
+ interval = 10;
+ format = "{min}° min, {max}° max, {average}° avg";
+ chip = "*-isa-*";
+ }
+ {
+ block = "networkmanager";
+ ap_format = "{ssid} @ {strength}%";
+ on_click = "kcmshell5 kcm_networkmanagement";
+ }
+ {
+ block = "net";
+ device = "enp9s0u2u1u2c2";
+ speed_up = true;
+ interval = 5;
+ }
+ {
+ block = "speedtest";
+ bytes = true;
+ }
+ {
+ block = "xrandr";
+ interval =
+ 6000; # Because running the commands causes screen lag, see https://github.com/greshake/i3status-rust/issues/668
+ }
+ {
+ block = "sound";
+ format = "{output_name} {volume}%";
+ on_click = "pavucontrol --tab=3";
+ mappings = {
+ "alsa_output.pci-0000_00_1f.3.analog-stereo" = "";
+ "bluez_sink.70_26_05_DA_27_A4.a2dp_sink" = "";
+ };
+ }
+ {
+ block = "music";
+ player = "spotify";
+ buttons = [ "play" "prev" "next" ];
+ on_collapsed_click = "i3-msg '[class=Spotify] focus'";
+ }
+ {
+ block = "time";
+ interval = 60;
+ format = "%a %d.%m %R";
+ }
+ { block = "battery"; }
+ ];
+
+ icons = "awesome5";
+
+ settings = {
+ theme = {
+ name = "solarized-dark";
+ overrides = {
+ idle_bg = "#123456";
+ idle_fg = "#abcdef";
+ };
+ };
+ };
+
+ theme = "gruvbox-dark";
+ };
+ };
+ };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ i3status-rust = pkgs.writeScriptBin "dummy-i3status-rust" "";
+ })
+ ];
+
+ nmt.script = ''
+ assertFileExists home-files/.config/i3status-rust/config-extra-settings.toml
+ assertFileContent home-files/.config/i3status-rust/config-extra-settings.toml \
+ ${
+ pkgs.writeText "i3status-rust-expected-config" ''
+ icons = "awesome5"
+ [[block]]
+ alert = 10
+ alias = "/"
+ block = "disk_space"
+ info_type = "available"
+ interval = 60
+ path = "/"
+ unit = "GB"
+ warning = 20
+
+ [[block]]
+ block = "memory"
+ display_type = "memory"
+ format_mem = "{Mug}GB ({Mup}%)"
+ format_swap = "{SUp}%"
+
+ [[block]]
+ block = "cpu"
+ format = "{barchart}"
+ interval = 1
+
+ [[block]]
+ block = "load"
+ format = "{1m} {5m}"
+ interval = 1
+
+ [[block]]
+ block = "temperature"
+ chip = "*-isa-*"
+ collapsed = true
+ format = "{min}° min, {max}° max, {average}° avg"
+ interval = 10
+
+ [[block]]
+ ap_format = "{ssid} @ {strength}%"
+ block = "networkmanager"
+ on_click = "kcmshell5 kcm_networkmanagement"
+
+ [[block]]
+ block = "net"
+ device = "enp9s0u2u1u2c2"
+ interval = 5
+ speed_up = true
+
+ [[block]]
+ block = "speedtest"
+ bytes = true
+
+ [[block]]
+ block = "xrandr"
+ interval = 6000
+
+ [[block]]
+ block = "sound"
+ format = "{output_name} {volume}%"
+ on_click = "pavucontrol --tab=3"
+
+ [block.mappings]
+ "alsa_output.pci-0000_00_1f.3.analog-stereo" = ""
+ "bluez_sink.70_26_05_DA_27_A4.a2dp_sink" = ""
+
+ [[block]]
+ block = "music"
+ buttons = ["play", "prev", "next"]
+ on_collapsed_click = "i3-msg '[class=Spotify] focus'"
+ player = "spotify"
+
+ [[block]]
+ block = "time"
+ format = "%a %d.%m %R"
+ interval = 60
+
+ [[block]]
+ block = "battery"
+
+ [theme]
+ name = "solarized-dark"
+
+ [theme.overrides]
+ idle_bg = "#123456"
+ idle_fg = "#abcdef"
+ ''
+ }
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/i3status-rust/with-multiple-bars.nix b/infra/libkookie/home-manager/tests/modules/programs/i3status-rust/with-multiple-bars.nix
new file mode 100644
index 000000000000..9cbbe2854a2d
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/i3status-rust/with-multiple-bars.nix
@@ -0,0 +1,106 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.i3status-rust = {
+ enable = true;
+
+ bars = {
+
+ top = {
+ blocks = [
+ {
+ block = "disk_space";
+ path = "/";
+ alias = "/";
+ info_type = "available";
+ unit = "GB";
+ interval = 60;
+ warning = 20.0;
+ alert = 10.0;
+ }
+ {
+ block = "memory";
+ display_type = "memory";
+ format_mem = "{Mug}GB ({Mup}%)";
+ format_swap = "{SUp}%";
+ }
+ ];
+ };
+
+ bottom = {
+ blocks = [
+ {
+ block = "cpu";
+ interval = 1;
+ format = "{barchart}";
+ }
+ {
+ block = "load";
+ interval = 1;
+ format = "{1m} {5m}";
+ }
+ ];
+ icons = "awesome5";
+
+ theme = "gruvbox-dark";
+ };
+
+ };
+
+ };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ i3status-rust = pkgs.writeScriptBin "dummy-i3status-rust" "";
+ })
+ ];
+
+ nmt.script = ''
+ assertFileExists home-files/.config/i3status-rust/config-top.toml
+ assertFileContent home-files/.config/i3status-rust/config-top.toml \
+ ${
+ pkgs.writeText "i3status-rust-expected-config" ''
+ icons = "none"
+ theme = "plain"
+ [[block]]
+ alert = 10
+ alias = "/"
+ block = "disk_space"
+ info_type = "available"
+ interval = 60
+ path = "/"
+ unit = "GB"
+ warning = 20
+
+ [[block]]
+ block = "memory"
+ display_type = "memory"
+ format_mem = "{Mug}GB ({Mup}%)"
+ format_swap = "{SUp}%"
+ ''
+ }
+
+ assertFileExists home-files/.config/i3status-rust/config-bottom.toml
+ assertFileContent \
+ home-files/.config/i3status-rust/config-bottom.toml \
+ ${
+ pkgs.writeText "i3status-rust-expected-config" ''
+ icons = "awesome5"
+ theme = "gruvbox-dark"
+ [[block]]
+ block = "cpu"
+ format = "{barchart}"
+ interval = 1
+
+ [[block]]
+ block = "load"
+ format = "{1m} {5m}"
+ interval = 1
+ ''
+ }
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/i3status/default.nix b/infra/libkookie/home-manager/tests/modules/programs/i3status/default.nix
new file mode 100644
index 000000000000..c8d557558618
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/i3status/default.nix
@@ -0,0 +1,4 @@
+{
+ i3status-with-custom = ./with-custom.nix;
+ i3status-with-default = ./with-default.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/i3status/with-custom.nix b/infra/libkookie/home-manager/tests/modules/programs/i3status/with-custom.nix
new file mode 100644
index 000000000000..4aa01773e4e3
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/i3status/with-custom.nix
@@ -0,0 +1,67 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.i3status = {
+ enable = true;
+ enableDefault = false;
+
+ general = {
+ colors = true;
+ color_good = "#e0e0e0";
+ color_degraded = "#d7ae00";
+ color_bad = "#f69d6a";
+ interval = 1;
+ };
+
+ modules = {
+ "volume master" = {
+ position = 1;
+ settings = {
+ format = "♪ %volume";
+ format_muted = "♪ muted (%volume)";
+ device = "pulse:1";
+ };
+ };
+ "disk /" = {
+ position = 2;
+ settings = { format = "/ %avail"; };
+ };
+ };
+ };
+
+ nixpkgs.overlays = [
+ (self: super: { i3status = pkgs.writeScriptBin "dummy-i3status" ""; })
+ ];
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/.config/i3status/config \
+ ${
+ pkgs.writeText "i3status-expected-config" ''
+ general {
+ color_bad = "#f69d6a"
+ color_degraded = "#d7ae00"
+ color_good = "#e0e0e0"
+ colors = true
+ interval = 1
+ }
+
+ order += "volume master"
+ order += "disk /"
+ disk / {
+ format = "/ %avail"
+ }
+
+ volume master {
+ device = "pulse:1"
+ format = "♪ %volume"
+ format_muted = "♪ muted (%volume)"
+ }
+ ''
+ }
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/i3status/with-default.nix b/infra/libkookie/home-manager/tests/modules/programs/i3status/with-default.nix
new file mode 100644
index 000000000000..0b7e4ee2fac7
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/i3status/with-default.nix
@@ -0,0 +1,73 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.i3status = {
+ enable = true;
+ enableDefault = true;
+ };
+
+ nixpkgs.overlays = [
+ (self: super: { i3status = pkgs.writeScriptBin "dummy-i3status" ""; })
+ ];
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/.config/i3status/config \
+ ${
+ pkgs.writeText "i3status-expected-config" ''
+ general {
+ colors = true
+ interval = 5
+ }
+
+ order += "ipv6"
+ order += "wireless _first_"
+ order += "ethernet _first_"
+ order += "battery all"
+ order += "disk /"
+ order += "load"
+ order += "memory"
+ order += "tztime local"
+ battery all {
+ format = "%status %percentage %remaining"
+ }
+
+ disk / {
+ format = "%avail"
+ }
+
+ ethernet _first_ {
+ format_down = "E: down"
+ format_up = "E: %ip (%speed)"
+ }
+
+ ipv6 {
+
+ }
+
+ load {
+ format = "%1min"
+ }
+
+ memory {
+ format = "%used | %available"
+ format_degraded = "MEMORY < %available"
+ threshold_degraded = "1G"
+ }
+
+ tztime local {
+ format = "%Y-%m-%d %H:%M:%S"
+ }
+
+ wireless _first_ {
+ format_down = "W: down"
+ format_up = "W: (%quality at %essid) %ip"
+ }
+ ''
+ }
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/kakoune/default.nix b/infra/libkookie/home-manager/tests/modules/programs/kakoune/default.nix
new file mode 100644
index 000000000000..1e6e077df1f8
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/kakoune/default.nix
@@ -0,0 +1,7 @@
+{
+ kakoune-no-plugins = ./no-plugins.nix;
+ kakoune-use-plugins = ./use-plugins.nix;
+ kakoune-whitespace-highlighter = ./whitespace-highlighter.nix;
+ kakoune-whitespace-highlighter-corner-cases =
+ ./whitespace-highlighter-corner-cases.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/kakoune/no-plugins.nix b/infra/libkookie/home-manager/tests/modules/programs/kakoune/no-plugins.nix
new file mode 100644
index 000000000000..67a7f30f5234
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/kakoune/no-plugins.nix
@@ -0,0 +1,13 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.kakoune = { enable = true; };
+
+ nmt.script = ''
+ assertPathNotExists home-path/share/kak/autoload/plugins
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/kakoune/use-plugins.nix b/infra/libkookie/home-manager/tests/modules/programs/kakoune/use-plugins.nix
new file mode 100644
index 000000000000..9d44483276fc
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/kakoune/use-plugins.nix
@@ -0,0 +1,16 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.kakoune = {
+ enable = true;
+ plugins = [ pkgs.kakounePlugins.kak-prelude ];
+ };
+
+ nmt.script = ''
+ assertDirectoryNotEmpty home-path/share/kak/autoload/plugins
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/kakoune/whitespace-highlighter-corner-cases.nix b/infra/libkookie/home-manager/tests/modules/programs/kakoune/whitespace-highlighter-corner-cases.nix
new file mode 100644
index 000000000000..142aaac8fe37
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/kakoune/whitespace-highlighter-corner-cases.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.kakoune = {
+ enable = true;
+ config.showWhitespace = {
+ enable = true;
+ lineFeed = ''"'';
+ space = " ";
+ nonBreakingSpace = "' '"; # backwards compat
+ tab = "'";
+ # tabStop = <default>
+ };
+ };
+
+ nmt.script = ''
+ assertFileExists home-files/.config/kak/kakrc
+ assertFileContains home-files/.config/kak/kakrc \
+ "add-highlighter global/ show-whitespaces -tab \"'\" -spc ' ' -nbsp ' ' -lf '\"'"
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/kakoune/whitespace-highlighter.nix b/infra/libkookie/home-manager/tests/modules/programs/kakoune/whitespace-highlighter.nix
new file mode 100644
index 000000000000..514c26a118b2
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/kakoune/whitespace-highlighter.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.kakoune = {
+ enable = true;
+ config.showWhitespace = {
+ enable = true;
+ lineFeed = "1";
+ space = "2";
+ nonBreakingSpace = "3";
+ tab = "4";
+ tabStop = "5";
+ };
+ };
+
+ nmt.script = ''
+ assertFileExists home-files/.config/kak/kakrc
+ assertFileContains home-files/.config/kak/kakrc \
+ "add-highlighter global/ show-whitespaces -tab '4' -tabpad '5' -spc '2' -nbsp '3' -lf '1'"
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/lf/all-options.nix b/infra/libkookie/home-manager/tests/modules/programs/lf/all-options.nix
new file mode 100644
index 000000000000..a25467a26b7a
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/lf/all-options.nix
@@ -0,0 +1,86 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ pvScript = builtins.toFile "pv.sh" "cat $1";
+ expected = builtins.toFile "settings-expected" ''
+ set icons
+ set noignorecase
+ set ratios "2:2:3"
+ set tabstop 4
+
+ cmd added :echo "foo"
+ cmd multiline :{{
+ push gg
+ echo "bar"
+ push i
+ }}
+ cmd removed
+
+ map aa should-be-added
+ map ab
+
+ cmap <c-a> should-be-added
+ cmap <c-b>
+
+ set previewer ${pvScript}
+ map i ${"$"}${pvScript} "$f" | less -R
+
+
+
+ # More config...
+
+ '';
+in {
+ config = {
+ programs.lf = {
+ enable = true;
+
+ cmdKeybindings = {
+ "<c-a>" = "should-be-added";
+ "<c-b>" = null;
+ };
+
+ commands = {
+ added = '':echo "foo"'';
+ removed = null;
+ multiline = ''
+ :{{
+ push gg
+ echo "bar"
+ push i
+ }}'';
+ };
+
+ extraConfig = ''
+ # More config...
+ '';
+
+ keybindings = {
+ aa = "should-be-added";
+ ab = null;
+ };
+
+ previewer = {
+ keybinding = "i";
+ source = pvScript;
+ };
+
+ settings = {
+ ignorecase = false;
+ icons = true;
+ tabstop = 4;
+ ratios = "2:2:3";
+ };
+ };
+
+ nixpkgs.overlays =
+ [ (self: super: { lf = pkgs.writeScriptBin "dummy-lf" ""; }) ];
+
+ nmt.script = ''
+ assertFileExists home-files/.config/lf/lfrc
+ assertFileContent home-files/.config/lf/lfrc ${expected}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/lf/default.nix b/infra/libkookie/home-manager/tests/modules/programs/lf/default.nix
new file mode 100644
index 000000000000..65cf24fcf60f
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/lf/default.nix
@@ -0,0 +1,5 @@
+{
+ lf-all-options = ./all-options.nix;
+ lf-minimal-options = ./minimal-options.nix;
+ lf-no-pv-keybind = ./no-pv-keybind.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/lf/minimal-options.nix b/infra/libkookie/home-manager/tests/modules/programs/lf/minimal-options.nix
new file mode 100644
index 000000000000..b3c26ba9b572
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/lf/minimal-options.nix
@@ -0,0 +1,18 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let expected = builtins.toFile "settings-expected" "\n\n\n\n\n\n\n\n\n\n\n";
+in {
+ config = {
+ programs.lf = { enable = true; };
+
+ nixpkgs.overlays =
+ [ (self: super: { lf = pkgs.writeScriptBin "dummy-lf" ""; }) ];
+
+ nmt.script = ''
+ assertFileExists home-files/.config/lf/lfrc
+ assertFileContent home-files/.config/lf/lfrc ${expected}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/lf/no-pv-keybind.nix b/infra/libkookie/home-manager/tests/modules/programs/lf/no-pv-keybind.nix
new file mode 100644
index 000000000000..524a41a36430
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/lf/no-pv-keybind.nix
@@ -0,0 +1,43 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ pvScript = builtins.toFile "pv.sh" "cat $1";
+ expected = builtins.toFile "settings-expected" ''
+
+
+
+
+
+
+
+
+ set previewer ${pvScript}
+
+
+
+ # More config...
+
+ '';
+in {
+ config = {
+ programs.lf = {
+ enable = true;
+
+ extraConfig = ''
+ # More config...
+ '';
+
+ previewer = { source = pvScript; };
+ };
+
+ nixpkgs.overlays =
+ [ (self: super: { lf = pkgs.writeScriptBin "dummy-lf" ""; }) ];
+
+ nmt.script = ''
+ assertFileExists home-files/.config/lf/lfrc
+ assertFileContent home-files/.config/lf/lfrc ${expected}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/lieer/default.nix b/infra/libkookie/home-manager/tests/modules/programs/lieer/default.nix
new file mode 100644
index 000000000000..16f8627cf0d5
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/lieer/default.nix
@@ -0,0 +1 @@
+{ lieer = ./lieer.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/programs/lieer/lieer-expected.json b/infra/libkookie/home-manager/tests/modules/programs/lieer/lieer-expected.json
new file mode 100644
index 000000000000..e7318f65dc3a
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/lieer/lieer-expected.json
@@ -0,0 +1 @@
+{"account":"hm@example.com","drop_non_existing_label":false,"ignore_remote_labels":["CATEGORY_FORUMS","CATEGORY_PROMOTIONS","CATEGORY_UPDATES","CATEGORY_SOCIAL","CATEGORY_PERSONAL"],"ignore_tags":[],"replace_slash_with_dot":false,"timeout":0}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/lieer/lieer.nix b/infra/libkookie/home-manager/tests/modules/programs/lieer/lieer.nix
new file mode 100644
index 000000000000..2ce4fb4e031c
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/lieer/lieer.nix
@@ -0,0 +1,23 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ imports = [ ../../accounts/email-test-accounts.nix ];
+
+ config = {
+ programs.lieer.enable = true;
+
+ accounts.email.accounts = { "hm@example.com".lieer.enable = true; };
+
+ nixpkgs.overlays = [
+ (self: super: { gmailieer = pkgs.writeScriptBin "dummy-gmailieer" ""; })
+ ];
+
+ nmt.script = ''
+ assertFileExists home-files/Mail/hm@example.com/.gmailieer.json
+ assertFileContent home-files/Mail/hm@example.com/.gmailieer.json \
+ ${./lieer-expected.json}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/man/apropos.nix b/infra/libkookie/home-manager/tests/modules/programs/man/apropos.nix
new file mode 100644
index 000000000000..a8ec35ead920
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/man/apropos.nix
@@ -0,0 +1,22 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.man = {
+ enable = true;
+ generateCaches = true;
+ };
+
+ nmt.script = ''
+ assertFileExists home-files/.manpath
+
+ CACHE_DIR=$(cat $TESTED/home-files/.manpath | cut --delimiter=' ' --fields=3)
+
+ if [[ ! -f "$CACHE_DIR/index.bt" ]]; then
+ fail "Expected man cache files to exist (in $CACHE_DIR) but they were not found."
+ fi
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/man/default.nix b/infra/libkookie/home-manager/tests/modules/programs/man/default.nix
new file mode 100644
index 000000000000..2e9d340f98d4
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/man/default.nix
@@ -0,0 +1,4 @@
+{
+ man-apropos = ./apropos.nix;
+ man-no-manpath = ./no-manpath.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/man/no-manpath.nix b/infra/libkookie/home-manager/tests/modules/programs/man/no-manpath.nix
new file mode 100644
index 000000000000..1b8cfb40c6eb
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/man/no-manpath.nix
@@ -0,0 +1,13 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.man = { enable = true; };
+
+ nmt.script = ''
+ assertPathNotExists home-files/.manpath
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/mbsync/default.nix b/infra/libkookie/home-manager/tests/modules/programs/mbsync/default.nix
new file mode 100644
index 000000000000..9c369fa50181
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/mbsync/default.nix
@@ -0,0 +1 @@
+{ mbsync = ./mbsync.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/programs/mbsync/mbsync-expected.conf b/infra/libkookie/home-manager/tests/modules/programs/mbsync/mbsync-expected.conf
new file mode 100644
index 000000000000..6722960476b5
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/mbsync/mbsync-expected.conf
@@ -0,0 +1,80 @@
+# Generated by Home Manager.
+
+IMAPAccount hm-account
+CertificateFile /etc/ssl/certs/ca-certificates.crt
+Host imap.example.org
+PassCmd "password-command 2"
+SSLType IMAPS
+User home.manager.jr
+
+IMAPStore hm-account-remote
+Account hm-account
+
+MaildirStore hm-account-local
+Inbox /home/hm-user/Mail/hm-account/Inbox
+Path /home/hm-user/Mail/hm-account/
+SubFolders Verbatim
+
+Channel emptyChannels-empty1
+Master :hm-account-remote:
+Slave :hm-account-local:
+
+Channel emptyChannels-empty2
+Master :hm-account-remote:
+Slave :hm-account-local:
+
+Channel hm-account-earlierPatternMatch
+Master :hm-account-remote:Label
+Slave :hm-account-local:SomethingUnderLabel
+Pattern ThingUnderLabel !NotThisMaildirThough "[Weird] Label?"
+
+Channel hm-account-inbox
+Master :hm-account-remote:Inbox
+Slave :hm-account-local:Inbox
+
+Channel hm-account-patternMatch
+Master :hm-account-remote:Label
+Slave :hm-account-local:SomethingUnderLabel
+Pattern ThingUnderLabel !NotThisMaildirThough "[Weird] Label?"
+
+Channel hm-account-strangeHostBoxName
+Master ":hm-account-remote:[Weird]/Label Mess"
+Slave :hm-account-local:[AnotherWeird]/Label
+
+Group emptyChannels
+Channel emptyChannels-empty1
+Channel emptyChannels-empty2
+
+Group hm-account
+Channel hm-account-earlierPatternMatch
+Channel hm-account-inbox
+Channel hm-account-patternMatch
+Channel hm-account-strangeHostBoxName
+
+
+IMAPAccount hm@example.com
+CertificateFile /etc/ssl/certs/ca-certificates.crt
+Host imap.example.com
+PassCmd password-command
+SSLType IMAPS
+User home.manager
+
+IMAPStore hm@example.com-remote
+Account hm@example.com
+
+MaildirStore hm@example.com-local
+Inbox /home/hm-user/Mail/hm@example.com/Inbox
+Path /home/hm-user/Mail/hm@example.com/
+SubFolders Verbatim
+
+Channel inboxes-inbox1
+Master :hm@example.com-remote:Inbox1
+Slave :hm@example.com-local:Inboxes
+
+Channel inboxes-inbox2
+Master :hm@example.com-remote:Inbox2
+Slave :hm@example.com-local:Inboxes
+
+Group inboxes
+Channel inboxes-inbox1
+Channel inboxes-inbox2
diff --git a/infra/libkookie/home-manager/tests/modules/programs/mbsync/mbsync.nix b/infra/libkookie/home-manager/tests/modules/programs/mbsync/mbsync.nix
new file mode 100644
index 000000000000..a6e555cd4c08
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/mbsync/mbsync.nix
@@ -0,0 +1,83 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ imports = [ ../../accounts/email-test-accounts.nix ];
+
+ config = {
+ programs.mbsync = {
+ enable = true;
+ # programs.mbsync.groups and
+ # accounts.email.accounts.<name>.mbsync.groups should NOT be used at the
+ # same time.
+ # If they are, then the new version will take precendence.
+ groups.inboxes = {
+ "hm@example.com" = [ "Inbox1" "Inbox2" ];
+ hm-account = [ "Inbox" ];
+ };
+ };
+
+ accounts.email.accounts = {
+ "hm@example.com".mbsync = {
+ enable = true;
+ groups.inboxes = {
+ channels = {
+ inbox1 = {
+ masterPattern = "Inbox1";
+ slavePattern = "Inboxes";
+ };
+ inbox2 = {
+ masterPattern = "Inbox2";
+ slavePattern = "Inboxes";
+ };
+ };
+ };
+ };
+
+ hm-account.mbsync = {
+ enable = true;
+ groups.hm-account = {
+ channels.earlierPatternMatch = {
+ masterPattern = "Label";
+ slavePattern = "SomethingUnderLabel";
+ patterns = [
+ "ThingUnderLabel"
+ "!NotThisMaildirThough"
+ ''"[Weird] Label?"''
+ ];
+ };
+ channels.inbox = {
+ masterPattern = "Inbox";
+ slavePattern = "Inbox";
+ };
+ channels.strangeHostBoxName = {
+ masterPattern = "[Weird]/Label Mess";
+ slavePattern = "[AnotherWeird]/Label";
+ };
+ channels.patternMatch = {
+ masterPattern = "Label";
+ slavePattern = "SomethingUnderLabel";
+ patterns = [
+ "ThingUnderLabel"
+ "!NotThisMaildirThough"
+ ''"[Weird] Label?"''
+ ];
+ };
+ };
+ # No group should be printed.
+ groups.emptyGroup = { };
+ # Group should be printed, but left with default channels.
+ groups.emptyChannels = {
+ channels.empty1 = { };
+ channels.empty2 = { };
+ };
+ };
+ };
+
+ nmt.script = ''
+ assertFileExists home-files/.mbsyncrc
+ assertFileContent home-files/.mbsyncrc ${./mbsync-expected.conf}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/ncmpcpp-linux/default.nix b/infra/libkookie/home-manager/tests/modules/programs/ncmpcpp-linux/default.nix
new file mode 100644
index 000000000000..b1185c852491
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/ncmpcpp-linux/default.nix
@@ -0,0 +1 @@
+{ ncmpcpp-use-mpd-config = ./ncmpcpp-use-mpd-config.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/programs/ncmpcpp-linux/ncmpcpp-use-mpd-config-expected-config b/infra/libkookie/home-manager/tests/modules/programs/ncmpcpp-linux/ncmpcpp-use-mpd-config-expected-config
new file mode 100644
index 000000000000..8aa57d08f16d
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/ncmpcpp-linux/ncmpcpp-use-mpd-config-expected-config
@@ -0,0 +1 @@
+mpd_music_dir=/home/user/music
diff --git a/infra/libkookie/home-manager/tests/modules/programs/ncmpcpp-linux/ncmpcpp-use-mpd-config.nix b/infra/libkookie/home-manager/tests/modules/programs/ncmpcpp-linux/ncmpcpp-use-mpd-config.nix
new file mode 100644
index 000000000000..5262f0314724
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/ncmpcpp-linux/ncmpcpp-use-mpd-config.nix
@@ -0,0 +1,25 @@
+{ pkgs, ... }:
+
+{
+ config = {
+ programs.ncmpcpp.enable = true;
+
+ services.mpd.enable = true;
+ services.mpd.musicDirectory = "/home/user/music";
+
+ nixpkgs.overlays = [
+ (self: super: {
+ ncmpcpp = pkgs.writeScriptBin "dummy-ncmpcpp" "";
+ mpd = pkgs.writeScriptBin "dummy-mpd" "";
+ })
+ ];
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/.config/ncmpcpp/config \
+ ${./ncmpcpp-use-mpd-config-expected-config}
+
+ assertPathNotExists home-files/.config/ncmpcpp/bindings
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/ncmpcpp/default.nix b/infra/libkookie/home-manager/tests/modules/programs/ncmpcpp/default.nix
new file mode 100644
index 000000000000..c150b0d82486
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/ncmpcpp/default.nix
@@ -0,0 +1,4 @@
+{
+ ncmpcpp-empty-settings = ./ncmpcpp-empty-settings.nix;
+ ncmpcpp-example-settings = ./ncmpcpp-example-settings.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/ncmpcpp/ncmpcpp-empty-settings.nix b/infra/libkookie/home-manager/tests/modules/programs/ncmpcpp/ncmpcpp-empty-settings.nix
new file mode 100644
index 000000000000..e5134002d3e8
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/ncmpcpp/ncmpcpp-empty-settings.nix
@@ -0,0 +1,16 @@
+{ pkgs, ... }:
+
+{
+ config = {
+ programs.ncmpcpp.enable = true;
+
+ nixpkgs.overlays =
+ [ (self: super: { ncmpcpp = pkgs.writeScriptBin "dummy-ncmpcpp" ""; }) ];
+
+ nmt.script = ''
+ assertPathNotExists home-files/.config/ncmpcpp/config
+
+ assertPathNotExists home-files/.config/ncmpcpp/bindings
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/ncmpcpp/ncmpcpp-example-settings-expected-bindings b/infra/libkookie/home-manager/tests/modules/programs/ncmpcpp/ncmpcpp-example-settings-expected-bindings
new file mode 100644
index 000000000000..a73bd129ffd6
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/ncmpcpp/ncmpcpp-example-settings-expected-bindings
@@ -0,0 +1,16 @@
+def_key "j"
+ scroll_down
+def_key "k"
+ scroll_up
+def_key "J"
+ select_item
+ scroll_down
+def_key "K"
+ select_item
+ scroll_up
+def_key "x"
+ delete_playlist_items
+def_key "x"
+ delete_browser_items
+def_key "x"
+ delete_stored_playlist
diff --git a/infra/libkookie/home-manager/tests/modules/programs/ncmpcpp/ncmpcpp-example-settings-expected-config b/infra/libkookie/home-manager/tests/modules/programs/ncmpcpp/ncmpcpp-example-settings-expected-config
new file mode 100644
index 000000000000..6aedb6110e4e
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/ncmpcpp/ncmpcpp-example-settings-expected-config
@@ -0,0 +1,4 @@
+display_volume_level=no
+mpd_music_dir=/home/user/music
+playlist_disable_highlight_delay=0
+user_interface=alternative
diff --git a/infra/libkookie/home-manager/tests/modules/programs/ncmpcpp/ncmpcpp-example-settings.nix b/infra/libkookie/home-manager/tests/modules/programs/ncmpcpp/ncmpcpp-example-settings.nix
new file mode 100644
index 000000000000..02a1f09c9027
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/ncmpcpp/ncmpcpp-example-settings.nix
@@ -0,0 +1,60 @@
+{ pkgs, ... }:
+
+{
+ config = {
+ programs.ncmpcpp = {
+ enable = true;
+ mpdMusicDir = "/home/user/music";
+
+ settings = {
+ user_interface = "alternative";
+ display_volume_level = false;
+ playlist_disable_highlight_delay = 0;
+ };
+
+ bindings = [
+ {
+ key = "j";
+ command = "scroll_down";
+ }
+ {
+ key = "k";
+ command = "scroll_up";
+ }
+ {
+ key = "J";
+ command = [ "select_item" "scroll_down" ];
+ }
+ {
+ key = "K";
+ command = [ "select_item" "scroll_up" ];
+ }
+ {
+ key = "x";
+ command = "delete_playlist_items";
+ }
+ {
+ key = "x";
+ command = "delete_browser_items";
+ }
+ {
+ key = "x";
+ command = "delete_stored_playlist";
+ }
+ ];
+ };
+
+ nixpkgs.overlays =
+ [ (self: super: { ncmpcpp = pkgs.writeScriptBin "dummy-ncmpcpp" ""; }) ];
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/.config/ncmpcpp/config \
+ ${./ncmpcpp-example-settings-expected-config}
+
+ assertFileContent \
+ home-files/.config/ncmpcpp/bindings \
+ ${./ncmpcpp-example-settings-expected-bindings}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/ne/default.nix b/infra/libkookie/home-manager/tests/modules/programs/ne/default.nix
new file mode 100644
index 000000000000..7a1c843d4322
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/ne/default.nix
@@ -0,0 +1,4 @@
+{
+ ne-defprefs = ./defprefs.nix;
+ ne-passthroughs = ./passthroughs.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/ne/defprefs.nix b/infra/libkookie/home-manager/tests/modules/programs/ne/defprefs.nix
new file mode 100644
index 000000000000..dce98b28648b
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/ne/defprefs.nix
@@ -0,0 +1,36 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ defpref = ''
+ defined through defaultPreferences
+ '';
+
+ autopref = ''
+ defined through automaticPreferences
+ '';
+
+in {
+ config = {
+ programs.ne = {
+ enable = true;
+ defaultPreferences = defpref;
+ automaticPreferences.".default" = autopref;
+ };
+
+ nixpkgs.overlays =
+ [ (self: super: { ne = pkgs.writeScriptBin "dummy-ne" ""; }) ];
+
+ nmt = {
+ description =
+ "Check that it gracefully handles the case of both defaultPreferences and automaticPreferences.'.default' being set, defaulting to the former.";
+ script = ''
+ assertFileExists home-files/.ne/.default#ap
+ assertFileContent home-files/.ne/.default#ap ${
+ builtins.toFile "defpref" defpref
+ }
+ '';
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/ne/passthroughs.nix b/infra/libkookie/home-manager/tests/modules/programs/ne/passthroughs.nix
new file mode 100644
index 000000000000..4c129e94489f
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/ne/passthroughs.nix
@@ -0,0 +1,73 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ # Samples taken from the ne manual.
+ keybindings = ''
+ SEQ "\x1b[1;5D" 14A
+ KEY 14A HELP
+ '';
+
+ menus = ''
+ MENU "File"
+ ITEM "Open... ^O" Open
+ ITEM "Close " Close
+ ITEM "DoIt " Macro DoIt
+ '';
+
+ virtualExtensions = ''
+ sh 1 ^#!\s*/.*\b(bash|sh|ksh|zsh)\s*
+ csh 1 ^#!\s*/.*\b(csh|tcsh)\s*
+ pl 1 ^#!\s*/.*\bperl\b
+ py 1 ^#!\s*/.*\bpython[0-9]*\s*
+ rb 1 ^#!\s*/.*\bruby\s*
+ xml 1 ^<\?xml
+ '';
+
+ automaticPreferences = {
+ nix = ''
+ TAB 0
+ TS 2
+ '';
+ js = ''
+ TS 4
+ '';
+ };
+
+ checkFile = filename: contents: ''
+ assertFileExists home-files/.ne/${filename}
+ assertFileContent home-files/.ne/${filename} ${
+ builtins.toFile "checkFile" contents
+ }
+ '';
+
+in {
+ config = {
+ programs.ne = {
+ enable = true;
+ inherit keybindings;
+ inherit menus;
+ inherit virtualExtensions;
+ inherit automaticPreferences;
+ };
+
+ nixpkgs.overlays =
+ [ (self: super: { ne = pkgs.writeScriptBin "dummy-ne" ""; }) ];
+
+ nmt = {
+ description = "Check that configuration files are correctly written";
+ script = concatStringsSep "\n" [
+ (checkFile ".keys" keybindings)
+ (checkFile ".extensions" virtualExtensions)
+ (checkFile ".menus" menus)
+
+ # Generates a check command for each entry in automaticPreferences.
+ (concatStringsSep "\n" (mapAttrsToList
+ (extension: contents: checkFile "${extension}#ap" contents)
+ automaticPreferences))
+ ];
+ };
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/neomutt/default.nix b/infra/libkookie/home-manager/tests/modules/programs/neomutt/default.nix
new file mode 100644
index 000000000000..aef9f37e02cc
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/neomutt/default.nix
@@ -0,0 +1,4 @@
+{
+ neomutt-simple = ./neomutt.nix;
+ neomutt-with-msmtp = ./neomutt-with-msmtp.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/neomutt/hm-example.com-expected b/infra/libkookie/home-manager/tests/modules/programs/neomutt/hm-example.com-expected
new file mode 100644
index 000000000000..fceae5dc1f96
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/neomutt/hm-example.com-expected
@@ -0,0 +1,35 @@
+# Generated by Home Manager.
+set ssl_force_tls = yes
+set certificate_file=/etc/ssl/certs/ca-certificates.crt
+
+# GPG section
+set crypt_use_gpgme = yes
+set crypt_autosign = no
+set pgp_use_gpg_agent = yes
+set mbox_type = Maildir
+set sort = "threads"
+
+# MTA section
+set smtp_pass='`password-command`'
+set smtp_url='smtps://home.manager@smtp.example.com'
+
+
+
+
+
+# MRA section
+set folder='/home/hm-user/Mail/hm@example.com'
+set from='hm@example.com'
+set postponed='+Drafts'
+set realname='H. M. Test'
+set record='+Sent'
+set spoolfile='+Inbox'
+set trash='+Trash'
+
+
+# Extra configuration
+color status cyan default
+
+# notmuch section
+set nm_default_uri = "notmuch:///home/hm-user/Mail"
+virtual-mailboxes "My INBOX" "notmuch://?query=tag:inbox"
diff --git a/infra/libkookie/home-manager/tests/modules/programs/neomutt/hm-example.com-msmtp-expected.conf b/infra/libkookie/home-manager/tests/modules/programs/neomutt/hm-example.com-msmtp-expected.conf
new file mode 100644
index 000000000000..925c70636a78
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/neomutt/hm-example.com-msmtp-expected.conf
@@ -0,0 +1,31 @@
+# Generated by Home Manager.
+set ssl_force_tls = yes
+set certificate_file=/etc/ssl/certs/ca-certificates.crt
+
+# GPG section
+set crypt_use_gpgme = yes
+set crypt_autosign = no
+set pgp_use_gpg_agent = yes
+set mbox_type = Maildir
+set sort = "threads"
+
+# MTA section
+set sendmail='msmtpq --read-envelope-from --read-recipients'
+
+
+
+
+
+# MRA section
+set folder='/home/hm-user/Mail/hm@example.com'
+set from='hm@example.com'
+set postponed='+Drafts'
+set realname='H. M. Test'
+set record='+Sent'
+set spoolfile='+Inbox'
+set trash='+Trash'
+
+
+# Extra configuration
+color status cyan default
+
diff --git a/infra/libkookie/home-manager/tests/modules/programs/neomutt/neomutt-expected.conf b/infra/libkookie/home-manager/tests/modules/programs/neomutt/neomutt-expected.conf
new file mode 100644
index 000000000000..7711aa5a6524
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/neomutt/neomutt-expected.conf
@@ -0,0 +1,27 @@
+# Generated by Home Manager.
+set header_cache = "/home/hm-user/.cache/neomutt/headers/"
+set message_cachedir = "/home/hm-user/.cache/neomutt/messages/"
+set editor = "$EDITOR"
+set implicit_autoview = yes
+
+alternative_order text/enriched text/plain text
+
+set delete = yes
+
+# Binds
+
+
+# Macros
+
+
+
+
+# Extra configuration
+
+
+
+# register account hm@example.com
+mailboxes "/home/hm-user/Mail/hm@example.com/Inbox"
+folder-hook /home/hm-user/Mail/hm@example.com/ " \
+ source /home/hm-user/.config/neomutt/hm@example.com "
+source /home/hm-user/.config/neomutt/hm@example.com \ No newline at end of file
diff --git a/infra/libkookie/home-manager/tests/modules/programs/neomutt/neomutt-with-msmtp.nix b/infra/libkookie/home-manager/tests/modules/programs/neomutt/neomutt-with-msmtp.nix
new file mode 100644
index 000000000000..22f65599cf4b
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/neomutt/neomutt-with-msmtp.nix
@@ -0,0 +1,39 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ imports = [ ../../accounts/email-test-accounts.nix ];
+
+ config = {
+ accounts.email.accounts = {
+ "hm@example.com" = {
+ primary = true;
+ msmtp.enable = true;
+ neomutt = {
+ enable = true;
+ extraConfig = ''
+ color status cyan default
+ '';
+ };
+ imap.port = 993;
+ };
+ };
+
+ programs.neomutt.enable = true;
+
+ nixpkgs.overlays =
+ [ (self: super: { neomutt = pkgs.writeScriptBin "dummy-neomutt" ""; }) ];
+
+ nmt.script = ''
+ assertFileExists home-files/.config/neomutt/neomuttrc
+ assertFileExists home-files/.config/neomutt/hm@example.com
+ assertFileContent home-files/.config/neomutt/neomuttrc ${
+ ./neomutt-expected.conf
+ }
+ assertFileContent home-files/.config/neomutt/hm@example.com ${
+ ./hm-example.com-msmtp-expected.conf
+ }
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/neomutt/neomutt.nix b/infra/libkookie/home-manager/tests/modules/programs/neomutt/neomutt.nix
new file mode 100644
index 000000000000..c0caa44af4ab
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/neomutt/neomutt.nix
@@ -0,0 +1,42 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ imports = [ ../../accounts/email-test-accounts.nix ];
+
+ config = {
+ accounts.email.accounts = {
+ "hm@example.com" = {
+ primary = true;
+ notmuch.enable = true;
+ neomutt = {
+ enable = true;
+ extraConfig = ''
+ color status cyan default
+ '';
+ };
+ imap.port = 993;
+ };
+ };
+
+ programs.neomutt = {
+ enable = true;
+ vimKeys = false;
+ };
+
+ nixpkgs.overlays =
+ [ (self: super: { neomutt = pkgs.writeScriptBin "dummy-neomutt" ""; }) ];
+
+ nmt.script = ''
+ assertFileExists home-files/.config/neomutt/neomuttrc
+ assertFileExists home-files/.config/neomutt/hm@example.com
+ assertFileContent home-files/.config/neomutt/neomuttrc ${
+ ./neomutt-expected.conf
+ }
+ assertFileContent home-files/.config/neomutt/hm@example.com ${
+ ./hm-example.com-expected
+ }
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/neovim/default.nix b/infra/libkookie/home-manager/tests/modules/programs/neovim/default.nix
new file mode 100644
index 000000000000..7d6e53aae36d
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/neovim/default.nix
@@ -0,0 +1 @@
+{ neovim-plugin-config = ./plugin-config.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/programs/neovim/plugin-config.nix b/infra/libkookie/home-manager/tests/modules/programs/neovim/plugin-config.nix
new file mode 100644
index 000000000000..60edaee82c22
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/neovim/plugin-config.nix
@@ -0,0 +1,37 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.neovim = {
+ enable = true;
+ extraConfig = ''
+ " This should be present in vimrc
+ '';
+ plugins = with pkgs.vimPlugins; [
+ vim-nix
+ {
+ plugin = vim-commentary;
+ config = ''
+ " This should be present too
+ autocmd FileType c setlocal commentstring=//\ %s
+ autocmd FileType c setlocal comments=://
+ '';
+ }
+ ];
+ };
+
+ nmt.script = ''
+ vimrc=$(grep -Po "(?<=-u )[^ ]*" < "${
+ builtins.toJSON config.programs.neovim.finalPackage
+ }/bin/nvim")
+ # We need to remove the unkown store paths in the config
+ TESTED="" assertFileContent \
+ <( ${pkgs.perl}/bin/perl -pe "s|\Q$NIX_STORE\E/[a-z0-9]{32}-|$NIX_STORE/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-|g" < "$vimrc"
+ ) \
+ "${./plugin-config.vim}"
+ '';
+ };
+}
+
diff --git a/infra/libkookie/home-manager/tests/modules/programs/neovim/plugin-config.vim b/infra/libkookie/home-manager/tests/modules/programs/neovim/plugin-config.vim
new file mode 100644
index 000000000000..a8d833f4e009
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/neovim/plugin-config.vim
@@ -0,0 +1,22 @@
+" configuration generated by NIX
+set nocompatible
+
+
+
+
+
+
+set packpath^=/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-vim-pack-dir
+set runtimepath^=/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-vim-pack-dir
+
+filetype indent plugin on | syn on
+
+
+" This should be present in vimrc
+" vim-commentary {{{
+" This should be present too
+autocmd FileType c setlocal commentstring=//\ %s
+autocmd FileType c setlocal comments=://
+
+" }}}
+
diff --git a/infra/libkookie/home-manager/tests/modules/programs/newsboat/default.nix b/infra/libkookie/home-manager/tests/modules/programs/newsboat/default.nix
new file mode 100644
index 000000000000..f12f640ef299
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/newsboat/default.nix
@@ -0,0 +1,4 @@
+{
+ newsboat-basics = ./newsboat-basics.nix;
+ newsboat-basics-2003 = ./newsboat-basics-2003.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/newsboat/newsboat-basics-2003.nix b/infra/libkookie/home-manager/tests/modules/programs/newsboat/newsboat-basics-2003.nix
new file mode 100644
index 000000000000..587a31bb255d
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/newsboat/newsboat-basics-2003.nix
@@ -0,0 +1,35 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ home.stateVersion = "20.03";
+
+ programs.newsboat = {
+ enable = true;
+
+ urls = [
+ {
+ url = "http://example.org/feed.xml";
+ tags = [ "tag1" "tag2" ];
+ title = "Cool feed";
+ }
+
+ { url = "http://example.org/feed2.xml"; }
+ ];
+
+ queries = { "foo" = ''rssurl =~ "example.com"''; };
+ };
+
+ nixpkgs.overlays = [
+ (self: super: { newsboat = pkgs.writeScriptBin "dummy-newsboat" ""; })
+ ];
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/.newsboat/urls \
+ ${./newsboat-basics-urls-2003.txt}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/newsboat/newsboat-basics-urls-2003.txt b/infra/libkookie/home-manager/tests/modules/programs/newsboat/newsboat-basics-urls-2003.txt
new file mode 100644
index 000000000000..68f811180531
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/newsboat/newsboat-basics-urls-2003.txt
@@ -0,0 +1,3 @@
+"query:foo:rssurl =~ \"example.com\""
+http://example.org/feed.xml "tag1" "tag2" "~Cool feed"
+http://example.org/feed2.xml
diff --git a/infra/libkookie/home-manager/tests/modules/programs/newsboat/newsboat-basics-urls.txt b/infra/libkookie/home-manager/tests/modules/programs/newsboat/newsboat-basics-urls.txt
new file mode 100644
index 000000000000..7f084961345a
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/newsboat/newsboat-basics-urls.txt
@@ -0,0 +1,3 @@
+http://example.org/feed.xml "tag1" "tag2" "~Cool feed"
+http://example.org/feed2.xml
+"query:foo:rssurl =~ \"example.com\""
diff --git a/infra/libkookie/home-manager/tests/modules/programs/newsboat/newsboat-basics.nix b/infra/libkookie/home-manager/tests/modules/programs/newsboat/newsboat-basics.nix
new file mode 100644
index 000000000000..e6eb4151776f
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/newsboat/newsboat-basics.nix
@@ -0,0 +1,33 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.newsboat = {
+ enable = true;
+
+ urls = [
+ {
+ url = "http://example.org/feed.xml";
+ tags = [ "tag1" "tag2" ];
+ title = "Cool feed";
+ }
+
+ { url = "http://example.org/feed2.xml"; }
+ ];
+
+ queries = { "foo" = ''rssurl =~ "example.com"''; };
+ };
+
+ nixpkgs.overlays = [
+ (self: super: { newsboat = pkgs.writeScriptBin "dummy-newsboat" ""; })
+ ];
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/.newsboat/urls \
+ ${./newsboat-basics-urls.txt}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/nushell/default.nix b/infra/libkookie/home-manager/tests/modules/programs/nushell/default.nix
new file mode 100644
index 000000000000..5b3bfa1a5bfc
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/nushell/default.nix
@@ -0,0 +1 @@
+{ nushell-settings = ./settings.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/programs/nushell/settings-expected.toml b/infra/libkookie/home-manager/tests/modules/programs/nushell/settings-expected.toml
new file mode 100644
index 000000000000..87c5de2500df
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/nushell/settings-expected.toml
@@ -0,0 +1,5 @@
+completion_mode = "circular"
+edit_mode = "vi"
+key_timeout = 10
+no_auto_pivot = true
+startup = ["alias la [] { ls -a }", "alias e [msg] { echo $msg }"]
diff --git a/infra/libkookie/home-manager/tests/modules/programs/nushell/settings.nix b/infra/libkookie/home-manager/tests/modules/programs/nushell/settings.nix
new file mode 100644
index 000000000000..09c192b3ba37
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/nushell/settings.nix
@@ -0,0 +1,34 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.nushell = {
+ enable = true;
+
+ settings = mkMerge [
+ {
+ edit_mode = "vi";
+ startup = [ "alias la [] { ls -a }" ];
+ completion_mode = "circular";
+ key_timeout = 10;
+ }
+
+ {
+ startup = [ "alias e [msg] { echo $msg }" ];
+ no_auto_pivot = true;
+ }
+ ];
+ };
+
+ nixpkgs.overlays =
+ [ (self: super: { nushell = pkgs.writeScriptBin "dummy-nushell" ""; }) ];
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/.config/nu/config.toml \
+ ${./settings-expected.toml}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/powerline-go/bash.nix b/infra/libkookie/home-manager/tests/modules/programs/powerline-go/bash.nix
new file mode 100644
index 000000000000..b01fcb1a75d2
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/powerline-go/bash.nix
@@ -0,0 +1,28 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs = {
+ bash.enable = true;
+
+ powerline-go = {
+ enable = true;
+ newline = true;
+ modules = [ "nix-shell" ];
+ pathAliases = { "\\~/project/foo" = "prj-foo"; };
+ settings = {
+ ignore-repos = [ "/home/me/project1" "/home/me/project2" ];
+ };
+ };
+ };
+
+ nmt.script = ''
+ assertFileExists home-files/.bashrc
+ assertFileContains \
+ home-files/.bashrc \
+ '/bin/powerline-go -error $old_exit_status -modules nix-shell -newline -path-aliases \~/project/foo=prj-foo -ignore-repos /home/me/project1,/home/me/project2'
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/powerline-go/default.nix b/infra/libkookie/home-manager/tests/modules/programs/powerline-go/default.nix
new file mode 100644
index 000000000000..e89d7c6cde05
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/powerline-go/default.nix
@@ -0,0 +1,4 @@
+{
+ powerline-go-bash = ./bash.nix;
+ powerline-go-zsh = ./zsh.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/powerline-go/zsh.nix b/infra/libkookie/home-manager/tests/modules/programs/powerline-go/zsh.nix
new file mode 100644
index 000000000000..702149528e49
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/powerline-go/zsh.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs = {
+ zsh.enable = true;
+
+ powerline-go = {
+ enable = true;
+ newline = true;
+ modules = [ "nix-shell" ];
+ pathAliases = { "\\~/project/foo" = "prj-foo"; };
+ settings = {
+ ignore-repos = [ "/home/me/project1" "/home/me/project2" ];
+ };
+ };
+ };
+
+ nixpkgs.overlays =
+ [ (self: super: { zsh = pkgs.writeScriptBin "dummy-zsh" ""; }) ];
+
+ nmt.script = ''
+ assertFileExists home-files/.zshrc
+ assertFileContains \
+ home-files/.zshrc \
+ '/bin/powerline-go -error $? -shell zsh -modules nix-shell -newline -path-aliases \~/project/foo=prj-foo -ignore-repos /home/me/project1,/home/me/project2'
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/qutebrowser/default.nix b/infra/libkookie/home-manager/tests/modules/programs/qutebrowser/default.nix
new file mode 100644
index 000000000000..581b4a5834ec
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/qutebrowser/default.nix
@@ -0,0 +1,4 @@
+{
+ qutebrowser-settings = ./settings.nix;
+ qutebrowser-keybindings = ./keybindings.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/qutebrowser/keybindings.nix b/infra/libkookie/home-manager/tests/modules/programs/qutebrowser/keybindings.nix
new file mode 100644
index 000000000000..e89e44b46d90
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/qutebrowser/keybindings.nix
@@ -0,0 +1,39 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.qutebrowser = {
+ enable = true;
+
+ enableDefaultBindings = false;
+
+ keyBindings = {
+ normal = {
+ "<Ctrl-v>" = "spawn mpv {url}";
+ ",l" = ''config-cycle spellcheck.languages ["en-GB"] ["en-US"]'';
+ };
+ prompt = { "<Ctrl-y>" = "prompt-yes"; };
+ };
+ };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ qutebrowser = pkgs.writeScriptBin "dummy-qutebrowser" "";
+ })
+ ];
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/.config/qutebrowser/config.py \
+ ${
+ pkgs.writeText "qutebrowser-expected-config.py" ''
+ c.bindings.default = {}
+ config.bind(",l", "config-cycle spellcheck.languages [\"en-GB\"] [\"en-US\"]", mode="normal")
+ config.bind("<Ctrl-v>", "spawn mpv {url}", mode="normal")
+ config.bind("<Ctrl-y>", "prompt-yes", mode="prompt")''
+ }
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/qutebrowser/settings.nix b/infra/libkookie/home-manager/tests/modules/programs/qutebrowser/settings.nix
new file mode 100644
index 000000000000..1f0f5db049b1
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/qutebrowser/settings.nix
@@ -0,0 +1,48 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.qutebrowser = {
+ enable = true;
+
+ settings = {
+ colors = {
+ hints = {
+ bg = "#000000";
+ fg = "#ffffff";
+ };
+ tabs.bar.bg = "#000000";
+ };
+ spellcheck.languages = [ "en-US" "sv-SE" ];
+ tabs.tabs_are_windows = true;
+ };
+
+ extraConfig = ''
+ # Extra qutebrowser configuration.
+ '';
+ };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ qutebrowser = pkgs.writeScriptBin "dummy-qutebrowser" "";
+ })
+ ];
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/.config/qutebrowser/config.py \
+ ${
+ pkgs.writeText "qutebrowser-expected-config.py" ''
+ c.colors.hints.bg = "#000000"
+ c.colors.hints.fg = "#ffffff"
+ c.colors.tabs.bar.bg = "#000000"
+ c.spellcheck.languages = ["en-US", "sv-SE"]
+ c.tabs.tabs_are_windows = True
+ # Extra qutebrowser configuration.
+ ''
+ }
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/readline/default.nix b/infra/libkookie/home-manager/tests/modules/programs/readline/default.nix
new file mode 100644
index 000000000000..c95745d19cd0
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/readline/default.nix
@@ -0,0 +1 @@
+{ readline-using-all-options = ./using-all-options.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/programs/readline/using-all-options.nix b/infra/libkookie/home-manager/tests/modules/programs/readline/using-all-options.nix
new file mode 100644
index 000000000000..ab851020c2eb
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/readline/using-all-options.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.readline = {
+ enable = true;
+
+ bindings = { "\\C-h" = "backward-kill-word"; };
+
+ variables = {
+ bell-style = "audible";
+ completion-map-case = true;
+ completion-prefix-display-length = 2;
+ };
+
+ extraConfig = ''
+ $if mode=emacs
+ "\e[1~": beginning-of-line
+ $endif
+ '';
+ };
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/.inputrc \
+ ${./using-all-options.txt}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/readline/using-all-options.txt b/infra/libkookie/home-manager/tests/modules/programs/readline/using-all-options.txt
new file mode 100644
index 000000000000..6b4aef51e69a
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/readline/using-all-options.txt
@@ -0,0 +1,11 @@
+# Generated by Home Manager.
+
+$include /etc/inputrc
+set bell-style audible
+set completion-map-case on
+set completion-prefix-display-length 2
+"\C-h": backward-kill-word
+$if mode=emacs
+"\e[1~": beginning-of-line
+$endif
+
diff --git a/infra/libkookie/home-manager/tests/modules/programs/rofi-pass/default.nix b/infra/libkookie/home-manager/tests/modules/programs/rofi-pass/default.nix
new file mode 100644
index 000000000000..181aef4e129c
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/rofi-pass/default.nix
@@ -0,0 +1,4 @@
+{
+ rofi-pass-root = ./rofi-pass-root.nix;
+ rofi-pass-config = ./rofi-pass-config.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/rofi-pass/rofi-pass-config.nix b/infra/libkookie/home-manager/tests/modules/programs/rofi-pass/rofi-pass-config.nix
new file mode 100644
index 000000000000..e70ad9ab1605
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/rofi-pass/rofi-pass-config.nix
@@ -0,0 +1,35 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.rofi = {
+ enable = true;
+
+ pass = {
+ enable = true;
+ extraConfig = ''
+ # Extra config for rofi-pass
+ xdotool_delay=12
+ '';
+ };
+ };
+
+ nixpkgs.overlays = [
+ (self: super: { rofi-pass = pkgs.writeScriptBin "dummy-rofi-pass" ""; })
+ ];
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/.config/rofi-pass/config \
+ ${
+ pkgs.writeText "rofi-pass-expected-config" ''
+ # Extra config for rofi-pass
+ xdotool_delay=12
+
+ ''
+ }
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/rofi-pass/rofi-pass-root.nix b/infra/libkookie/home-manager/tests/modules/programs/rofi-pass/rofi-pass-root.nix
new file mode 100644
index 000000000000..15c86ef6293c
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/rofi-pass/rofi-pass-root.nix
@@ -0,0 +1,30 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.rofi = {
+ enable = true;
+
+ pass = {
+ enable = true;
+ stores = [ "~/.local/share/password-store" ];
+ };
+ };
+
+ nixpkgs.overlays = [
+ (self: super: { rofi-pass = pkgs.writeScriptBin "dummy-rofi-pass" ""; })
+ ];
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/.config/rofi-pass/config \
+ ${
+ pkgs.writeText "rofi-pass-expected-config" ''
+ root=~/.local/share/password-store
+ ''
+ }
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/rofi/assert-on-both-theme-and-colors-expected.json b/infra/libkookie/home-manager/tests/modules/programs/rofi/assert-on-both-theme-and-colors-expected.json
new file mode 100644
index 000000000000..808288f41938
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/rofi/assert-on-both-theme-and-colors-expected.json
@@ -0,0 +1 @@
+["Cannot use the rofi options 'theme' and 'colors' simultaneously.\n"] \ No newline at end of file
diff --git a/infra/libkookie/home-manager/tests/modules/programs/rofi/assert-on-both-theme-and-colors.nix b/infra/libkookie/home-manager/tests/modules/programs/rofi/assert-on-both-theme-and-colors.nix
new file mode 100644
index 000000000000..f162a486b8be
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/rofi/assert-on-both-theme-and-colors.nix
@@ -0,0 +1,32 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.rofi = {
+ enable = true;
+ theme = "foo";
+ colors = {
+ window = {
+ background = "background";
+ border = "border";
+ separator = "separator";
+ };
+ rows = { };
+ };
+ };
+
+ home.file.result.text = builtins.toJSON
+ (map (a: a.message) (filter (a: !a.assertion) config.assertions));
+
+ nixpkgs.overlays =
+ [ (self: super: { rofi = pkgs.writeScriptBin "dummy-rofi" ""; }) ];
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/result \
+ ${./assert-on-both-theme-and-colors-expected.json}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/rofi/default.nix b/infra/libkookie/home-manager/tests/modules/programs/rofi/default.nix
new file mode 100644
index 000000000000..c18c3b0ed6a6
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/rofi/default.nix
@@ -0,0 +1,3 @@
+{
+ rofi-assert-on-both-theme-and-colors = ./assert-on-both-theme-and-colors.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/ssh/default-config-expected.conf b/infra/libkookie/home-manager/tests/modules/programs/ssh/default-config-expected.conf
new file mode 100644
index 000000000000..6885392b26c0
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/ssh/default-config-expected.conf
@@ -0,0 +1,16 @@
+
+
+
+
+Host *
+ ForwardAgent no
+ Compression no
+ ServerAliveInterval 0
+ ServerAliveCountMax 3
+ HashKnownHosts no
+ UserKnownHostsFile ~/.ssh/known_hosts
+ ControlMaster no
+ ControlPath ~/.ssh/master-%r@%n:%p
+ ControlPersist no
+
+
diff --git a/infra/libkookie/home-manager/tests/modules/programs/ssh/default-config.nix b/infra/libkookie/home-manager/tests/modules/programs/ssh/default-config.nix
new file mode 100644
index 000000000000..6d7e5508a2fc
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/ssh/default-config.nix
@@ -0,0 +1,18 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.ssh = { enable = true; };
+
+ home.file.assertions.text = builtins.toJSON
+ (map (a: a.message) (filter (a: !a.assertion) config.assertions));
+
+ nmt.script = ''
+ assertFileExists home-files/.ssh/config
+ assertFileContent home-files/.ssh/config ${./default-config-expected.conf}
+ assertFileContent home-files/assertions ${./no-assertions.json}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/ssh/default.nix b/infra/libkookie/home-manager/tests/modules/programs/ssh/default.nix
new file mode 100644
index 000000000000..507eef0bdb8b
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/ssh/default.nix
@@ -0,0 +1,17 @@
+{
+ ssh-defaults = ./default-config.nix;
+ ssh-match-blocks = ./match-blocks-attrs.nix;
+
+ ssh-forwards-dynamic-valid-bind-no-asserts =
+ ./forwards-dynamic-valid-bind-no-asserts.nix;
+ ssh-forwards-dynamic-bind-path-with-port-asserts =
+ ./forwards-dynamic-bind-path-with-port-asserts.nix;
+ ssh-forwards-local-bind-path-with-port-asserts =
+ ./forwards-local-bind-path-with-port-asserts.nix;
+ ssh-forwards-local-host-path-with-port-asserts =
+ ./forwards-local-host-path-with-port-asserts.nix;
+ ssh-forwards-remote-bind-path-with-port-asserts =
+ ./forwards-remote-bind-path-with-port-asserts.nix;
+ ssh-forwards-remote-host-path-with-port-asserts =
+ ./forwards-remote-host-path-with-port-asserts.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-dynamic-bind-path-with-port-asserts.nix b/infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-dynamic-bind-path-with-port-asserts.nix
new file mode 100644
index 000000000000..cf2efe5a5a5b
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-dynamic-bind-path-with-port-asserts.nix
@@ -0,0 +1,29 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.ssh = {
+ enable = true;
+ matchBlocks = {
+ dynamicBindPathWithPort = {
+ dynamicForwards = [{
+ # Error:
+ address = "/run/user/1000/gnupg/S.gpg-agent.extra";
+ port = 3000;
+ }];
+ };
+ };
+ };
+
+ home.file.result.text = builtins.toJSON
+ (map (a: a.message) (filter (a: !a.assertion) config.assertions));
+
+ nmt.script = ''
+ assertFileContent home-files/result ${
+ ./forwards-paths-with-ports-error.json
+ }
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts-expected.conf b/infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts-expected.conf
new file mode 100644
index 000000000000..02268e8cb8be
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts-expected.conf
@@ -0,0 +1,20 @@
+
+
+Host dynamicBindAddressWithPort
+ DynamicForward [127.0.0.1]:3000
+
+Host dynamicBindPathNoPort
+ DynamicForward /run/user/1000/gnupg/S.gpg-agent.extra
+
+Host *
+ ForwardAgent no
+ Compression no
+ ServerAliveInterval 0
+ ServerAliveCountMax 3
+ HashKnownHosts no
+ UserKnownHostsFile ~/.ssh/known_hosts
+ ControlMaster no
+ ControlPath ~/.ssh/master-%r@%n:%p
+ ControlPersist no
+
+
diff --git a/infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts.nix b/infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts.nix
new file mode 100644
index 000000000000..d0c3a732256c
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-dynamic-valid-bind-no-asserts.nix
@@ -0,0 +1,38 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.ssh = {
+ enable = true;
+ matchBlocks = {
+ dynamicBindPathNoPort = {
+ dynamicForwards = [{
+ # OK:
+ address = "/run/user/1000/gnupg/S.gpg-agent.extra";
+ }];
+ };
+
+ dynamicBindAddressWithPort = {
+ dynamicForwards = [{
+ # OK:
+ address = "127.0.0.1";
+ port = 3000;
+ }];
+ };
+ };
+ };
+
+ home.file.result.text = builtins.toJSON
+ (map (a: a.message) (filter (a: !a.assertion) config.assertions));
+
+ nmt.script = ''
+ assertFileExists home-files/.ssh/config
+ assertFileContent \
+ home-files/.ssh/config \
+ ${./forwards-dynamic-valid-bind-no-asserts-expected.conf}
+ assertFileContent home-files/result ${./no-assertions.json}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-local-bind-path-with-port-asserts.nix b/infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-local-bind-path-with-port-asserts.nix
new file mode 100644
index 000000000000..f9d8e2daf855
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-local-bind-path-with-port-asserts.nix
@@ -0,0 +1,33 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.ssh = {
+ enable = true;
+ matchBlocks = {
+ localBindPathWithPort = {
+ localForwards = [{
+ # OK:
+ host.address = "127.0.0.1";
+ host.port = 3000;
+
+ # Error:
+ bind.address = "/run/user/1000/gnupg/S.gpg-agent.extra";
+ bind.port = 3000;
+ }];
+ };
+ };
+ };
+
+ home.file.result.text = builtins.toJSON
+ (map (a: a.message) (filter (a: !a.assertion) config.assertions));
+
+ nmt.script = ''
+ assertFileContent home-files/result ${
+ ./forwards-paths-with-ports-error.json
+ }
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-local-host-path-with-port-asserts.nix b/infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-local-host-path-with-port-asserts.nix
new file mode 100644
index 000000000000..02a7e5b168d5
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-local-host-path-with-port-asserts.nix
@@ -0,0 +1,33 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.ssh = {
+ enable = true;
+ matchBlocks = {
+ localHostPathWithPort = {
+ localForwards = [{
+ # OK:
+ bind.address = "127.0.0.1";
+ bind.port = 3000;
+
+ # Error:
+ host.address = "/run/user/1000/gnupg/S.gpg-agent.extra";
+ host.port = 3000;
+ }];
+ };
+ };
+ };
+
+ home.file.result.text = builtins.toJSON
+ (map (a: a.message) (filter (a: !a.assertion) config.assertions));
+
+ nmt.script = ''
+ assertFileContent home-files/result ${
+ ./forwards-paths-with-ports-error.json
+ }
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-paths-with-ports-error.json b/infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-paths-with-ports-error.json
new file mode 100644
index 000000000000..e7e3a374ecc7
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-paths-with-ports-error.json
@@ -0,0 +1 @@
+["Forwarded paths cannot have ports."] \ No newline at end of file
diff --git a/infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-remote-bind-path-with-port-asserts.nix b/infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-remote-bind-path-with-port-asserts.nix
new file mode 100644
index 000000000000..61ce9ae03858
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-remote-bind-path-with-port-asserts.nix
@@ -0,0 +1,33 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.ssh = {
+ enable = true;
+ matchBlocks = {
+ remoteBindPathWithPort = {
+ remoteForwards = [{
+ # OK:
+ host.address = "127.0.0.1";
+ host.port = 3000;
+
+ # Error:
+ bind.address = "/run/user/1000/gnupg/S.gpg-agent.extra";
+ bind.port = 3000;
+ }];
+ };
+ };
+ };
+
+ home.file.result.text = builtins.toJSON
+ (map (a: a.message) (filter (a: !a.assertion) config.assertions));
+
+ nmt.script = ''
+ assertFileContent home-files/result ${
+ ./forwards-paths-with-ports-error.json
+ }
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-remote-host-path-with-port-asserts.nix b/infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-remote-host-path-with-port-asserts.nix
new file mode 100644
index 000000000000..71bdbcb70fd8
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/ssh/forwards-remote-host-path-with-port-asserts.nix
@@ -0,0 +1,33 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.ssh = {
+ enable = true;
+ matchBlocks = {
+ remoteHostPathWithPort = {
+ remoteForwards = [{
+ # OK:
+ bind.address = "127.0.0.1";
+ bind.port = 3000;
+
+ # Error:
+ host.address = "/run/user/1000/gnupg/S.gpg-agent.extra";
+ host.port = 3000;
+ }];
+ };
+ };
+ };
+
+ home.file.result.text = builtins.toJSON
+ (map (a: a.message) (filter (a: !a.assertion) config.assertions));
+
+ nmt.script = ''
+ assertFileContent home-files/result ${
+ ./forwards-paths-with-ports-error.json
+ }
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/ssh/match-blocks-attrs-expected.conf b/infra/libkookie/home-manager/tests/modules/programs/ssh/match-blocks-attrs-expected.conf
new file mode 100644
index 000000000000..e2c3290153db
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/ssh/match-blocks-attrs-expected.conf
@@ -0,0 +1,34 @@
+
+
+Host * !github.com
+ Port 516
+ IdentityFile file1
+ IdentityFile file2
+
+Host abc
+ ProxyJump jump-host
+
+Host xyz
+ ServerAliveInterval 60
+ ServerAliveCountMax 10
+ IdentityFile file
+ LocalForward [localhost]:8080 [10.0.0.1]:80
+ RemoteForward [localhost]:8081 [10.0.0.2]:80
+ RemoteForward /run/user/1000/gnupg/S.gpg-agent.extra /run/user/1000/gnupg/S.gpg-agent
+ DynamicForward [localhost]:2839
+
+Host ordered
+ Port 1
+
+Host *
+ ForwardAgent no
+ Compression no
+ ServerAliveInterval 0
+ ServerAliveCountMax 3
+ HashKnownHosts no
+ UserKnownHostsFile ~/.ssh/known_hosts
+ ControlMaster no
+ ControlPath ~/.ssh/master-%r@%n:%p
+ ControlPersist no
+
+
diff --git a/infra/libkookie/home-manager/tests/modules/programs/ssh/match-blocks-attrs.nix b/infra/libkookie/home-manager/tests/modules/programs/ssh/match-blocks-attrs.nix
new file mode 100644
index 000000000000..eaa20c6e32d9
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/ssh/match-blocks-attrs.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.ssh = {
+ enable = true;
+ matchBlocks = {
+ abc = {
+ identityFile = null;
+ proxyJump = "jump-host";
+ };
+
+ ordered = hm.dag.entryAfter [ "xyz" ] { port = 1; };
+
+ xyz = {
+ identityFile = "file";
+ serverAliveInterval = 60;
+ serverAliveCountMax = 10;
+ localForwards = [{
+ bind.port = 8080;
+ host.address = "10.0.0.1";
+ host.port = 80;
+ }];
+ remoteForwards = [
+ {
+ bind.port = 8081;
+ host.address = "10.0.0.2";
+ host.port = 80;
+ }
+ {
+ bind.address = "/run/user/1000/gnupg/S.gpg-agent.extra";
+ host.address = "/run/user/1000/gnupg/S.gpg-agent";
+ }
+ ];
+ dynamicForwards = [{ port = 2839; }];
+ };
+
+ "* !github.com" = {
+ identityFile = [ "file1" "file2" ];
+ port = 516;
+ };
+ };
+ };
+
+ home.file.assertions.text = builtins.toJSON
+ (map (a: a.message) (filter (a: !a.assertion) config.assertions));
+
+ nmt.script = ''
+ assertFileExists home-files/.ssh/config
+ assertFileContent \
+ home-files/.ssh/config \
+ ${./match-blocks-attrs-expected.conf}
+ assertFileContent home-files/assertions ${./no-assertions.json}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/ssh/no-assertions.json b/infra/libkookie/home-manager/tests/modules/programs/ssh/no-assertions.json
new file mode 100644
index 000000000000..0637a088a01e
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/ssh/no-assertions.json
@@ -0,0 +1 @@
+[] \ No newline at end of file
diff --git a/infra/libkookie/home-manager/tests/modules/programs/starship/default.nix b/infra/libkookie/home-manager/tests/modules/programs/starship/default.nix
new file mode 100644
index 000000000000..814aed658740
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/starship/default.nix
@@ -0,0 +1 @@
+{ starship-settings = ./settings.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/programs/starship/settings-expected.toml b/infra/libkookie/home-manager/tests/modules/programs/starship/settings-expected.toml
new file mode 100644
index 000000000000..a4fb0ee55deb
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/starship/settings-expected.toml
@@ -0,0 +1,27 @@
+add_newline = false
+prompt_order = ["line_break", "package", "line_break", "character"]
+scan_timeout = 10
+
+[aws]
+disabled = true
+style = "bold blue"
+
+[battery]
+charging_symbol = "⚡️"
+
+[[battery.display]]
+style = "bold red"
+threshold = 10
+
+[[battery.display]]
+style = "bold yellow"
+threshold = 30
+
+[character]
+symbol = "➜"
+
+[memory_usage]
+threshold = -1
+
+[package]
+disabled = true
diff --git a/infra/libkookie/home-manager/tests/modules/programs/starship/settings.nix b/infra/libkookie/home-manager/tests/modules/programs/starship/settings.nix
new file mode 100644
index 000000000000..e7a27733d900
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/starship/settings.nix
@@ -0,0 +1,49 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.starship = {
+ enable = true;
+
+ settings = mkMerge [
+ {
+ add_newline = false;
+ prompt_order = [ "line_break" "package" "line_break" "character" ];
+ scan_timeout = 10;
+ character.symbol = "➜";
+ package.disabled = true;
+ memory_usage.threshold = -1;
+ aws.style = "bold blue";
+ battery = {
+ charging_symbol = "⚡️";
+ display = [{
+ threshold = 10;
+ style = "bold red";
+ }];
+ };
+ }
+
+ {
+ aws.disabled = true;
+
+ battery.display = [{
+ threshold = 30;
+ style = "bold yellow";
+ }];
+ }
+ ];
+ };
+
+ nixpkgs.overlays = [
+ (self: super: { starship = pkgs.writeScriptBin "dummy-starship" ""; })
+ ];
+
+ nmt.script = ''
+ assertFileContent \
+ home-files/.config/starship.toml \
+ ${./settings-expected.toml}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/texlive/default.nix b/infra/libkookie/home-manager/tests/modules/programs/texlive/default.nix
new file mode 100644
index 000000000000..23bfe2daf0a9
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/texlive/default.nix
@@ -0,0 +1 @@
+{ texlive-minimal = ./texlive-minimal.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/programs/texlive/texlive-minimal.nix b/infra/libkookie/home-manager/tests/modules/programs/texlive/texlive-minimal.nix
new file mode 100644
index 000000000000..1b13936beac5
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/texlive/texlive-minimal.nix
@@ -0,0 +1,27 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.texlive.enable = true;
+
+ # Set up a minimal mocked texlive package set.
+ nixpkgs.overlays = [
+ (self: super: {
+ texlive = {
+ collection-basic = pkgs.writeTextDir "collection-basic" "";
+ combine = tpkgs:
+ pkgs.symlinkJoin {
+ name = "dummy-texlive-combine";
+ paths = attrValues tpkgs;
+ };
+ };
+ })
+ ];
+
+ nmt.script = ''
+ assertFileExists home-path/collection-basic
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/tmux/default-shell.conf b/infra/libkookie/home-manager/tests/modules/programs/tmux/default-shell.conf
new file mode 100644
index 000000000000..a33623630756
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/tmux/default-shell.conf
@@ -0,0 +1,30 @@
+# ============================================= #
+# Start with defaults from the Sensible plugin #
+# --------------------------------------------- #
+run-shell @sensible_rtp@
+# ============================================= #
+
+set -g default-terminal "screen"
+set -g base-index 0
+setw -g pane-base-index 0
+# We need to set default-shell before calling new-session
+set -g default-shell "/usr/bin/myshell"
+
+
+
+
+
+set -g status-keys emacs
+set -g mode-keys emacs
+
+
+
+
+
+
+
+setw -g aggressive-resize off
+setw -g clock-mode-style 12
+set -s escape-time 500
+set -g history-limit 2000
+
diff --git a/infra/libkookie/home-manager/tests/modules/programs/tmux/default-shell.nix b/infra/libkookie/home-manager/tests/modules/programs/tmux/default-shell.nix
new file mode 100644
index 000000000000..05091c6ab172
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/tmux/default-shell.nix
@@ -0,0 +1,27 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ substituteExpected = path:
+ pkgs.substituteAll {
+ src = path;
+
+ sensible_rtp = pkgs.tmuxPlugins.sensible.rtp;
+ };
+
+in {
+ config = {
+ programs.tmux = {
+ enable = true;
+ shell = "/usr/bin/myshell";
+ };
+
+ nmt.script = ''
+ assertFileExists home-files/.tmux.conf
+ assertFileContent home-files/.tmux.conf \
+ ${substituteExpected ./default-shell.conf}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/tmux/default.nix b/infra/libkookie/home-manager/tests/modules/programs/tmux/default.nix
new file mode 100644
index 000000000000..be78c2620a4a
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/tmux/default.nix
@@ -0,0 +1,10 @@
+{
+ tmux-emacs-with-plugins = ./emacs-with-plugins.nix;
+ tmux-not-enabled = ./not-enabled.nix;
+ tmux-vi-all-true = ./vi-all-true.nix;
+ tmux-secure-socket-enabled = ./secure-socket-enabled.nix;
+ tmux-disable-confirmation-prompt = ./disable-confirmation-prompt.nix;
+ tmux-default-shell = ./default-shell.nix;
+ tmux-shortcut-without-prefix = ./shortcut-without-prefix.nix;
+ tmux-prefix = ./prefix.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/tmux/disable-confirmation-prompt.conf b/infra/libkookie/home-manager/tests/modules/programs/tmux/disable-confirmation-prompt.conf
new file mode 100644
index 000000000000..222f733d5f3c
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/tmux/disable-confirmation-prompt.conf
@@ -0,0 +1,30 @@
+# ============================================= #
+# Start with defaults from the Sensible plugin #
+# --------------------------------------------- #
+run-shell @sensible_rtp@
+# ============================================= #
+
+set -g default-terminal "screen"
+set -g base-index 0
+setw -g pane-base-index 0
+
+
+
+
+
+set -g status-keys emacs
+set -g mode-keys emacs
+
+
+
+
+
+bind-key & kill-window
+bind-key x kill-pane
+
+
+setw -g aggressive-resize off
+setw -g clock-mode-style 12
+set -s escape-time 500
+set -g history-limit 2000
+
diff --git a/infra/libkookie/home-manager/tests/modules/programs/tmux/disable-confirmation-prompt.nix b/infra/libkookie/home-manager/tests/modules/programs/tmux/disable-confirmation-prompt.nix
new file mode 100644
index 000000000000..9c65ad6eee41
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/tmux/disable-confirmation-prompt.nix
@@ -0,0 +1,26 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.tmux = {
+ enable = true;
+ disableConfirmationPrompt = true;
+ };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ tmuxPlugins = super.tmuxPlugins // {
+ sensible = super.tmuxPlugins.sensible // { rtp = "@sensible_rtp@"; };
+ };
+ })
+ ];
+
+ nmt.script = ''
+ assertFileExists home-files/.tmux.conf
+ assertFileContent home-files/.tmux.conf \
+ ${./disable-confirmation-prompt.conf}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/tmux/emacs-with-plugins.conf b/infra/libkookie/home-manager/tests/modules/programs/tmux/emacs-with-plugins.conf
new file mode 100644
index 000000000000..97e226316f4a
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/tmux/emacs-with-plugins.conf
@@ -0,0 +1,53 @@
+# ============================================= #
+# Start with defaults from the Sensible plugin #
+# --------------------------------------------- #
+run-shell @tmuxplugin_sensible_rtp@
+# ============================================= #
+
+set -g default-terminal "screen"
+set -g base-index 0
+setw -g pane-base-index 0
+
+new-session
+
+bind v split-window -h
+bind s split-window -v
+
+
+set -g status-keys emacs
+set -g mode-keys emacs
+
+
+
+
+
+
+
+setw -g aggressive-resize on
+setw -g clock-mode-style 24
+set -s escape-time 500
+set -g history-limit 2000
+
+# ============================================= #
+# Load plugins with Home Manager #
+# --------------------------------------------- #
+
+# tmuxplugin-logging
+# ---------------------
+
+run-shell @tmuxplugin_logging_rtp@
+
+
+# tmuxplugin-prefix-highlight
+# ---------------------
+
+run-shell @tmuxplugin_prefix_highlight_rtp@
+
+
+# tmuxplugin-fzf-tmux-url
+# ---------------------
+
+run-shell @tmuxplugin_fzf_tmux_url_rtp@
+
+# ============================================= #
+
diff --git a/infra/libkookie/home-manager/tests/modules/programs/tmux/emacs-with-plugins.nix b/infra/libkookie/home-manager/tests/modules/programs/tmux/emacs-with-plugins.nix
new file mode 100644
index 000000000000..f9bccaa2ce49
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/tmux/emacs-with-plugins.nix
@@ -0,0 +1,49 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.tmux = {
+ aggressiveResize = true;
+ clock24 = true;
+ enable = true;
+ keyMode = "emacs";
+ newSession = true;
+ reverseSplit = true;
+
+ plugins = with pkgs.tmuxPlugins; [
+ logging
+ prefix-highlight
+ fzf-tmux-url
+ ];
+ };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ tmuxPlugins = super.tmuxPlugins // {
+ fzf-tmux-url = super.tmuxPlugins.fzf-tmux-url // {
+ rtp = "@tmuxplugin_fzf_tmux_url_rtp@";
+ };
+
+ logging = super.tmuxPlugins.logging // {
+ rtp = "@tmuxplugin_logging_rtp@";
+ };
+
+ prefix-highlight = super.tmuxPlugins.prefix-highlight // {
+ rtp = "@tmuxplugin_prefix_highlight_rtp@";
+ };
+
+ sensible = super.tmuxPlugins.sensible // {
+ rtp = "@tmuxplugin_sensible_rtp@";
+ };
+ };
+ })
+ ];
+
+ nmt.script = ''
+ assertFileExists home-files/.tmux.conf
+ assertFileContent home-files/.tmux.conf ${./emacs-with-plugins.conf}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/tmux/not-enabled.nix b/infra/libkookie/home-manager/tests/modules/programs/tmux/not-enabled.nix
new file mode 100644
index 000000000000..b7c675a83a2c
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/tmux/not-enabled.nix
@@ -0,0 +1,13 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.tmux = { enable = false; };
+
+ nmt.script = ''
+ assertPathNotExists home-files/.tmux.conf
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/tmux/prefix.conf b/infra/libkookie/home-manager/tests/modules/programs/tmux/prefix.conf
new file mode 100644
index 000000000000..318803002366
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/tmux/prefix.conf
@@ -0,0 +1,32 @@
+# ============================================= #
+# Start with defaults from the Sensible plugin #
+# --------------------------------------------- #
+run-shell @sensible_rtp@
+# ============================================= #
+
+set -g default-terminal "screen"
+set -g base-index 0
+setw -g pane-base-index 0
+
+
+
+
+
+set -g status-keys emacs
+set -g mode-keys emacs
+
+
+
+# rebind main key: C-a
+unbind C-b
+set -g prefix C-a
+bind C-a send-prefix
+
+
+
+
+setw -g aggressive-resize off
+setw -g clock-mode-style 12
+set -s escape-time 500
+set -g history-limit 2000
+
diff --git a/infra/libkookie/home-manager/tests/modules/programs/tmux/prefix.nix b/infra/libkookie/home-manager/tests/modules/programs/tmux/prefix.nix
new file mode 100644
index 000000000000..80ed6964075c
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/tmux/prefix.nix
@@ -0,0 +1,26 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.tmux = {
+ enable = true;
+ prefix = "C-a";
+ };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ tmuxPlugins = super.tmuxPlugins // {
+ sensible = super.tmuxPlugins.sensible // { rtp = "@sensible_rtp@"; };
+ };
+ })
+ ];
+
+ nmt.script = ''
+ assertFileExists home-files/.tmux.conf
+ assertFileContent home-files/.tmux.conf \
+ ${./prefix.conf}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/tmux/secure-socket-enabled.nix b/infra/libkookie/home-manager/tests/modules/programs/tmux/secure-socket-enabled.nix
new file mode 100644
index 000000000000..ca2de66310db
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/tmux/secure-socket-enabled.nix
@@ -0,0 +1,18 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.tmux = {
+ enable = true;
+ secureSocket = true;
+ };
+
+ nmt.script = ''
+ assertFileExists home-path/etc/profile.d/hm-session-vars.sh
+ assertFileContains home-path/etc/profile.d/hm-session-vars.sh \
+ 'export TMUX_TMPDIR="''${XDG_RUNTIME_DIR:-"/run/user/\$(id -u)"}"'
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/tmux/shortcut-without-prefix.conf b/infra/libkookie/home-manager/tests/modules/programs/tmux/shortcut-without-prefix.conf
new file mode 100644
index 000000000000..a8bc59cbb83a
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/tmux/shortcut-without-prefix.conf
@@ -0,0 +1,33 @@
+# ============================================= #
+# Start with defaults from the Sensible plugin #
+# --------------------------------------------- #
+run-shell @sensible_rtp@
+# ============================================= #
+
+set -g default-terminal "screen"
+set -g base-index 0
+setw -g pane-base-index 0
+
+
+
+
+
+set -g status-keys emacs
+set -g mode-keys emacs
+
+
+
+# rebind main key: C-a
+unbind C-b
+set -g prefix C-a
+bind a send-prefix
+bind C-a last-window
+
+
+
+
+setw -g aggressive-resize off
+setw -g clock-mode-style 12
+set -s escape-time 500
+set -g history-limit 2000
+
diff --git a/infra/libkookie/home-manager/tests/modules/programs/tmux/shortcut-without-prefix.nix b/infra/libkookie/home-manager/tests/modules/programs/tmux/shortcut-without-prefix.nix
new file mode 100644
index 000000000000..52290c4fc42b
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/tmux/shortcut-without-prefix.nix
@@ -0,0 +1,27 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.tmux = {
+ enable = true;
+ shortcut = "a";
+ prefix = null;
+ };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ tmuxPlugins = super.tmuxPlugins // {
+ sensible = super.tmuxPlugins.sensible // { rtp = "@sensible_rtp@"; };
+ };
+ })
+ ];
+
+ nmt.script = ''
+ assertFileExists home-files/.tmux.conf
+ assertFileContent home-files/.tmux.conf \
+ ${./shortcut-without-prefix.conf}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/tmux/vi-all-true.conf b/infra/libkookie/home-manager/tests/modules/programs/tmux/vi-all-true.conf
new file mode 100644
index 000000000000..de1cbe06ebca
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/tmux/vi-all-true.conf
@@ -0,0 +1,30 @@
+# ============================================= #
+# Start with defaults from the Sensible plugin #
+# --------------------------------------------- #
+run-shell @sensible_rtp@
+# ============================================= #
+
+set -g default-terminal "screen"
+set -g base-index 0
+setw -g pane-base-index 0
+
+new-session
+
+bind v split-window -h
+bind s split-window -v
+
+
+set -g status-keys vi
+set -g mode-keys vi
+
+
+
+
+
+
+
+setw -g aggressive-resize on
+setw -g clock-mode-style 24
+set -s escape-time 500
+set -g history-limit 2000
+
diff --git a/infra/libkookie/home-manager/tests/modules/programs/tmux/vi-all-true.nix b/infra/libkookie/home-manager/tests/modules/programs/tmux/vi-all-true.nix
new file mode 100644
index 000000000000..bce032fd654f
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/tmux/vi-all-true.nix
@@ -0,0 +1,29 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.tmux = {
+ aggressiveResize = true;
+ clock24 = true;
+ enable = true;
+ keyMode = "vi";
+ newSession = true;
+ reverseSplit = true;
+ };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ tmuxPlugins = super.tmuxPlugins // {
+ sensible = super.tmuxPlugins.sensible // { rtp = "@sensible_rtp@"; };
+ };
+ })
+ ];
+
+ nmt.script = ''
+ assertFileExists home-files/.tmux.conf
+ assertFileContent home-files/.tmux.conf ${./vi-all-true.conf}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/vscode/default.nix b/infra/libkookie/home-manager/tests/modules/programs/vscode/default.nix
new file mode 100644
index 000000000000..70f6d2e70608
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/vscode/default.nix
@@ -0,0 +1 @@
+{ vscode-keybindings = ./keybindings.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/programs/vscode/keybindings.nix b/infra/libkookie/home-manager/tests/modules/programs/vscode/keybindings.nix
new file mode 100644
index 000000000000..d1ab38d3b059
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/vscode/keybindings.nix
@@ -0,0 +1,71 @@
+# Test that keybdinings.json is created correctly.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ bindings = [
+ {
+ key = "ctrl+c";
+ command = "editor.action.clipboardCopyAction";
+ when = "textInputFocus && false";
+ }
+ {
+ key = "ctrl+c";
+ command = "deleteFile";
+ when = "";
+ }
+ {
+ key = "d";
+ command = "deleteFile";
+ when = "explorerViewletVisible";
+ }
+ ];
+
+ targetPath = if pkgs.stdenv.hostPlatform.isDarwin then
+ "Library/Application Support/Code/User/keybindings.json"
+ else
+ ".config/Code/User/keybindings.json";
+
+ expectedJson = pkgs.writeText "expected.json" ''
+ [
+ {
+ "command": "editor.action.clipboardCopyAction",
+ "key": "ctrl+c",
+ "when": "textInputFocus && false"
+ },
+ {
+ "command": "deleteFile",
+ "key": "ctrl+c",
+ "when": ""
+ },
+ {
+ "command": "deleteFile",
+ "key": "d",
+ "when": "explorerViewletVisible"
+ }
+ ]
+ '';
+in {
+ config = {
+ programs.vscode = {
+ enable = true;
+ keybindings = bindings;
+ };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ vscode = pkgs.runCommandLocal "vscode" { pname = "vscode"; } ''
+ mkdir -p $out/bin
+ touch $out/bin/code
+ chmod +x $out/bin/code;
+ '';
+ })
+ ];
+
+ nmt.script = ''
+ assertFileExists "home-files/${targetPath}"
+ assertFileContent "home-files/${targetPath}" "${expectedJson}"
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/waybar/broken-settings.nix b/infra/libkookie/home-manager/tests/modules/programs/waybar/broken-settings.nix
new file mode 100644
index 000000000000..f5ac39468e68
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/waybar/broken-settings.nix
@@ -0,0 +1,80 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ package = pkgs.writeScriptBin "dummy-waybar" "" // { outPath = "@waybar@"; };
+ expected = pkgs.writeText "expected-json" ''
+ [
+ {
+ "height": 26,
+ "layer": "top",
+ "modules-center": [
+ "sway/window"
+ ],
+ "modules-left": [
+ "sway/workspaces",
+ "sway/mode"
+ ],
+ "modules-right": [
+ "idle_inhibitor",
+ "pulseaudio",
+ "network",
+ "cpu",
+ "memory",
+ "backlight",
+ "tray",
+ "clock"
+ ],
+ "output": [
+ "DP-1",
+ "eDP-1",
+ "HEADLESS-1"
+ ],
+ "position": "top",
+ "sway/workspaces": {
+ "all-outputs": true
+ }
+ }
+ ]
+ '';
+in {
+ config = {
+ programs.waybar = {
+ inherit package;
+ enable = true;
+ systemd.enable = true;
+ settings = [{
+ layer = "top";
+ position = "top";
+ height = 26;
+ output = [ "DP-1" "eDP-1" "HEADLESS-1" ];
+ modules-left = [ "sway/workspaces" "sway/mode" ];
+ modules-center = [ "sway/window" ];
+ modules-right = [
+ "idle_inhibitor"
+ "pulseaudio"
+ "network"
+ "cpu"
+ "memory"
+ "backlight"
+ "tray"
+ "clock"
+ ];
+
+ modules = { "sway/workspaces".all-outputs = true; };
+ }];
+ };
+
+ nmt.description = ''
+ Test for the broken configuration
+ https://github.com/nix-community/home-manager/pull/1329#issuecomment-653253069
+ '';
+ nmt.script = ''
+ assertPathNotExists home-files/.config/waybar/style.css
+ assertFileContent \
+ home-files/.config/waybar/config \
+ ${expected}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/waybar/default.nix b/infra/libkookie/home-manager/tests/modules/programs/waybar/default.nix
new file mode 100644
index 000000000000..cac5de664c6f
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/waybar/default.nix
@@ -0,0 +1,8 @@
+{
+ waybar-systemd-with-graphical-session-target =
+ ./systemd-with-graphical-session-target.nix;
+ waybar-styling = ./styling.nix;
+ waybar-settings-complex = ./settings-complex.nix;
+ # Broken configuration from https://github.com/nix-community/home-manager/pull/1329#issuecomment-653253069
+ waybar-broken-settings = ./broken-settings.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/waybar/settings-complex-expected.json b/infra/libkookie/home-manager/tests/modules/programs/waybar/settings-complex-expected.json
new file mode 100644
index 000000000000..0d020c19c978
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/waybar/settings-complex-expected.json
@@ -0,0 +1,46 @@
+[
+ {
+ "custom/my-module": {
+ "exec": "@dummy@/bin/dummy",
+ "format": "hello from {}"
+ },
+ "height": 30,
+ "idle_inhibitor": {
+ "format": "{icon}"
+ },
+ "layer": "top",
+ "modules-center": [
+ "sway/window"
+ ],
+ "modules-left": [
+ "sway/workspaces",
+ "sway/mode",
+ "custom/my-module"
+ ],
+ "modules-right": [
+ "idle_inhibitor",
+ "pulseaudio",
+ "network",
+ "cpu",
+ "memory",
+ "backlight",
+ "tray",
+ "battery",
+ "clock"
+ ],
+ "output": [
+ "DP-1"
+ ],
+ "position": "top",
+ "sway/mode": {
+ "tooltip": false
+ },
+ "sway/window": {
+ "max-length": 120
+ },
+ "sway/workspaces": {
+ "all-outputs": true,
+ "disable-scroll": true
+ }
+ }
+]
diff --git a/infra/libkookie/home-manager/tests/modules/programs/waybar/settings-complex.nix b/infra/libkookie/home-manager/tests/modules/programs/waybar/settings-complex.nix
new file mode 100644
index 000000000000..750e52f4581c
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/waybar/settings-complex.nix
@@ -0,0 +1,59 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ package = pkgs.writeScriptBin "dummy-waybar" "" // { outPath = "@waybar@"; };
+in {
+ config = {
+ programs.waybar = {
+ inherit package;
+ enable = true;
+ settings = [{
+ layer = "top";
+ position = "top";
+ height = 30;
+ output = [ "DP-1" ];
+ modules-left = [ "sway/workspaces" "sway/mode" "custom/my-module" ];
+ modules-center = [ "sway/window" ];
+ modules-right = [
+ "idle_inhibitor"
+ "pulseaudio"
+ "network"
+ "cpu"
+ "memory"
+ "backlight"
+ "tray"
+ "battery"
+ "clock"
+ ];
+
+ modules = {
+ "sway/workspaces" = {
+ disable-scroll = true;
+ all-outputs = true;
+ };
+ "sway/mode" = { tooltip = false; };
+ "sway/window" = { max-length = 120; };
+ "idle_inhibitor" = { format = "{icon}"; };
+ "custom/my-module" = {
+ format = "hello from {}";
+ exec = let
+ dummyScript =
+ pkgs.writeShellScriptBin "dummy" "echo within waybar" // {
+ outPath = "@dummy@";
+ };
+ in "${dummyScript}/bin/dummy";
+ };
+ };
+ }];
+ };
+
+ nmt.script = ''
+ assertPathNotExists home-files/.config/waybar/style.css
+ assertFileContent \
+ home-files/.config/waybar/config \
+ ${./settings-complex-expected.json}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/waybar/styling-expected.css b/infra/libkookie/home-manager/tests/modules/programs/waybar/styling-expected.css
new file mode 100644
index 000000000000..dc779e58770d
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/waybar/styling-expected.css
@@ -0,0 +1,23 @@
+* {
+ border: none;
+ border-radius: 0;
+ font-family: Source Code Pro;
+ font-weight: bold;
+ color: #abb2bf;
+ font-size: 18px;
+ min-height: 0px;
+}
+window#waybar {
+ background: #16191C;
+ color: #aab2bf;
+}
+#window {
+ padding: 0 0px;
+}
+#workspaces button:hover {
+ box-shadow: inherit;
+ text-shadow: inherit;
+ background: #16191C;
+ border: #16191C;
+ padding: 0 3px;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/waybar/styling.nix b/infra/libkookie/home-manager/tests/modules/programs/waybar/styling.nix
new file mode 100644
index 000000000000..bd73f2aafd2e
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/waybar/styling.nix
@@ -0,0 +1,46 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ package = pkgs.writeScriptBin "dummy-waybar" "" // { outPath = "@waybar@"; };
+in {
+ config = {
+ programs.waybar = {
+ inherit package;
+ enable = true;
+ style = ''
+ * {
+ border: none;
+ border-radius: 0;
+ font-family: Source Code Pro;
+ font-weight: bold;
+ color: #abb2bf;
+ font-size: 18px;
+ min-height: 0px;
+ }
+ window#waybar {
+ background: #16191C;
+ color: #aab2bf;
+ }
+ #window {
+ padding: 0 0px;
+ }
+ #workspaces button:hover {
+ box-shadow: inherit;
+ text-shadow: inherit;
+ background: #16191C;
+ border: #16191C;
+ padding: 0 3px;
+ }
+ '';
+ };
+
+ nmt.script = ''
+ assertPathNotExists home-files/.config/waybar/config
+ assertFileContent \
+ home-files/.config/waybar/style.css \
+ ${./styling-expected.css}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/waybar/systemd-with-graphical-session-target.nix b/infra/libkookie/home-manager/tests/modules/programs/waybar/systemd-with-graphical-session-target.nix
new file mode 100644
index 000000000000..e751d804dabc
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/waybar/systemd-with-graphical-session-target.nix
@@ -0,0 +1,24 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ package = pkgs.writeScriptBin "dummy-waybar" "" // { outPath = "@waybar@"; };
+in {
+ config = {
+ programs.waybar = {
+ inherit package;
+ enable = true;
+ systemd.enable = true;
+ };
+
+ nmt.script = ''
+ assertPathNotExists home-files/.config/waybar/config
+ assertPathNotExists home-files/.config/waybar/style.css
+
+ assertFileContent \
+ home-files/.config/systemd/user/waybar.service \
+ ${./systemd-with-graphical-session-target.service}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/waybar/systemd-with-graphical-session-target.service b/infra/libkookie/home-manager/tests/modules/programs/waybar/systemd-with-graphical-session-target.service
new file mode 100644
index 000000000000..64c89f93d755
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/waybar/systemd-with-graphical-session-target.service
@@ -0,0 +1,14 @@
+[Install]
+WantedBy=graphical-session.target
+
+[Service]
+BusName=fr.arouillard.waybar
+ExecStart=@waybar@/bin/waybar
+Restart=always
+RestartSec=1sec
+Type=dbus
+
+[Unit]
+Description=Highly customizable Wayland bar for Sway and Wlroots based compositors.
+Documentation=https://github.com/Alexays/Waybar/wiki
+PartOf=graphical-session.target
diff --git a/infra/libkookie/home-manager/tests/modules/programs/zplug/default.nix b/infra/libkookie/home-manager/tests/modules/programs/zplug/default.nix
new file mode 100644
index 000000000000..172f7cd5981b
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/zplug/default.nix
@@ -0,0 +1 @@
+{ zplug-modules = ./modules.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/programs/zplug/modules.nix b/infra/libkookie/home-manager/tests/modules/programs/zplug/modules.nix
new file mode 100644
index 000000000000..704c5c5e2ef2
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/zplug/modules.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.zsh = {
+ enable = true;
+ zplug = {
+ enable = true;
+ plugins = [
+ {
+ name = "plugins/git";
+ tags = [ "from:oh-my-zsh" ];
+ }
+ {
+ name = "lib/clipboard";
+ tags = [ "from:oh-my-zsh" ''if:"[[ $OSTYPE == *darwin* ]]"'' ];
+ }
+ ];
+ };
+ };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ zsh = pkgs.writeScriptBin "dummy-zsh" "";
+ zplug = pkgs.writeScriptBin "dummy-zplug" "";
+ })
+ ];
+
+ nmt.script = ''
+ assertFileRegex home-files/.zshrc \
+ '^source ${builtins.storeDir}/.*zplug.*/init\.zsh$'
+
+ assertFileContains home-files/.zshrc \
+ 'zplug "plugins/git", from:oh-my-zsh'
+
+ assertFileContains home-files/.zshrc \
+ 'zplug "lib/clipboard", from:oh-my-zsh, if:"[[ $OSTYPE == *darwin* ]]"'
+
+ assertFileContains home-files/.zshrc \
+ 'if ! zplug check; then
+ zplug install
+ fi'
+
+ assertFileRegex home-files/.zshrc \
+ '^zplug load$'
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/zsh/default.nix b/infra/libkookie/home-manager/tests/modules/programs/zsh/default.nix
new file mode 100644
index 000000000000..274ba0942eaa
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/zsh/default.nix
@@ -0,0 +1,8 @@
+{
+ zsh-session-variables = ./session-variables.nix;
+ zsh-history-path-new-default = ./history-path-new-default.nix;
+ zsh-history-path-new-custom = ./history-path-new-custom.nix;
+ zsh-history-path-old-default = ./history-path-old-default.nix;
+ zsh-history-path-old-custom = ./history-path-old-custom.nix;
+ zsh-prezto = ./prezto.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/zsh/history-path-new-custom.nix b/infra/libkookie/home-manager/tests/modules/programs/zsh/history-path-new-custom.nix
new file mode 100644
index 000000000000..0c052d6949ec
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/zsh/history-path-new-custom.nix
@@ -0,0 +1,20 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ home.stateVersion = "20.03";
+ programs.zsh = {
+ enable = true;
+ history.path = "$HOME/some/directory/zsh_history";
+ };
+
+ nixpkgs.overlays =
+ [ (self: super: { zsh = pkgs.writeScriptBin "dummy-zsh" ""; }) ];
+
+ nmt.script = ''
+ assertFileRegex home-files/.zshrc '^HISTFILE="$HOME/some/directory/zsh_history"$'
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/zsh/history-path-new-default.nix b/infra/libkookie/home-manager/tests/modules/programs/zsh/history-path-new-default.nix
new file mode 100644
index 000000000000..6d1f58a29dcd
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/zsh/history-path-new-default.nix
@@ -0,0 +1,17 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ home.stateVersion = "20.03";
+ programs.zsh.enable = true;
+
+ nixpkgs.overlays =
+ [ (self: super: { zsh = pkgs.writeScriptBin "dummy-zsh" ""; }) ];
+
+ nmt.script = ''
+ assertFileRegex home-files/.zshrc '^HISTFILE="$HOME/.zsh_history"$'
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/zsh/history-path-old-custom.nix b/infra/libkookie/home-manager/tests/modules/programs/zsh/history-path-old-custom.nix
new file mode 100644
index 000000000000..f5b178b5e975
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/zsh/history-path-old-custom.nix
@@ -0,0 +1,20 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ home.stateVersion = "19.09";
+ programs.zsh = {
+ enable = true;
+ history.path = "some/directory/zsh_history";
+ };
+
+ nixpkgs.overlays =
+ [ (self: super: { zsh = pkgs.writeScriptBin "dummy-zsh" ""; }) ];
+
+ nmt.script = ''
+ assertFileRegex home-files/.zshrc '^HISTFILE="$HOME/some/directory/zsh_history"$'
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/zsh/history-path-old-default.nix b/infra/libkookie/home-manager/tests/modules/programs/zsh/history-path-old-default.nix
new file mode 100644
index 000000000000..d880d9664540
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/zsh/history-path-old-default.nix
@@ -0,0 +1,17 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ home.stateVersion = "19.03";
+ programs.zsh.enable = true;
+
+ nixpkgs.overlays =
+ [ (self: super: { zsh = pkgs.writeScriptBin "dummy-zsh" ""; }) ];
+
+ nmt.script = ''
+ assertFileRegex home-files/.zshrc '^HISTFILE="$HOME/.zsh_history"$'
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/zsh/prezto.nix b/infra/libkookie/home-manager/tests/modules/programs/zsh/prezto.nix
new file mode 100644
index 000000000000..cc173bab82f2
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/zsh/prezto.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.zsh.prezto.enable = true;
+
+ nixpkgs.overlays = [
+ (self: super: {
+ zsh-prezto = super.runCommandLocal "dummy-zsh-prezto" { } ''
+ mkdir -p $out/runcoms
+ echo '# zprofile' > $out/runcoms/zprofile
+ echo '# zlogin' > $out/runcoms/zlogin
+ echo '# zlogout' > $out/runcoms/zlogout
+ echo '# zshenv' > $out/runcoms/zshenv
+ '';
+ })
+ ];
+
+ nmt.script = ''
+ assertFileExists home-files/.zpreztorc
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/programs/zsh/session-variables.nix b/infra/libkookie/home-manager/tests/modules/programs/zsh/session-variables.nix
new file mode 100644
index 000000000000..ca903619d684
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/programs/zsh/session-variables.nix
@@ -0,0 +1,28 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ programs.zsh = {
+ enable = true;
+
+ sessionVariables = {
+ V1 = "v1";
+ V2 = "v2-${config.programs.zsh.sessionVariables.V1}";
+ };
+ };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ zsh = pkgs.writeScriptBin "dummy-zsh" "";
+ })
+ ];
+
+ nmt.script = ''
+ assertFileExists home-files/.zshrc
+ assertFileRegex home-files/.zshrc 'export V1="v1"'
+ assertFileRegex home-files/.zshrc 'export V2="v2-v1"'
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/services/dropbox/basic-configuration.nix b/infra/libkookie/home-manager/tests/modules/services/dropbox/basic-configuration.nix
new file mode 100644
index 000000000000..96a17cd73848
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/dropbox/basic-configuration.nix
@@ -0,0 +1,25 @@
+{ config, pkgs, ... }:
+
+{
+ config = {
+ services.dropbox = {
+ enable = true;
+ path = "${config.home.homeDirectory}/dropbox";
+ };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ dropbox-cli = pkgs.writeScriptBin "dummy-dropbox-cli" "" // {
+ outPath = "@dropbox-cli@";
+ };
+ })
+ ];
+
+ nmt.script = ''
+ serviceFile=home-files/.config/systemd/user/dropbox.service
+
+ assertFileExists $serviceFile
+ '';
+
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/services/dropbox/default.nix b/infra/libkookie/home-manager/tests/modules/services/dropbox/default.nix
new file mode 100644
index 000000000000..ad5197903553
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/dropbox/default.nix
@@ -0,0 +1 @@
+{ dropbox-basic-configuration = ./basic-configuration.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/services/emacs/default.nix b/infra/libkookie/home-manager/tests/modules/services/emacs/default.nix
new file mode 100644
index 000000000000..af27538d99d6
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/emacs/default.nix
@@ -0,0 +1,5 @@
+{
+ emacs-service = ./emacs-service.nix;
+ emacs-socket-26 = ./emacs-socket-26.nix;
+ emacs-socket-27 = ./emacs-socket-27.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-emacsclient.desktop b/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-emacsclient.desktop
new file mode 100644
index 000000000000..1dafb4e0f11c
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-emacsclient.desktop
@@ -0,0 +1,11 @@
+[Desktop Entry]
+Categories=Utility;TextEditor;
+Comment=Edit text
+Exec=@emacs@/bin/emacsclient -c %F
+GenericName=Text Editor
+Icon=emacs
+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++;
+Name=Emacs Client
+StartupWMClass=Emacs
+Terminal=false
+Type=Application
diff --git a/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-service-emacs.service b/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-service-emacs.service
new file mode 100644
index 000000000000..d8a618a26717
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-service-emacs.service
@@ -0,0 +1,12 @@
+[Install]
+WantedBy=default.target
+
+[Service]
+ExecStart=@runtimeShell@ -l -c "@emacs@/bin/emacs --fg-daemon"
+ExecStop=@emacs@/bin/emacsclient --eval '(kill-emacs 0)'
+Restart=on-failure
+
+[Unit]
+Description=Emacs: the extensible, self-documenting text editor
+Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/
+X-RestartIfChanged=false
diff --git a/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-service.nix b/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-service.nix
new file mode 100644
index 000000000000..be27e9ab33d2
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-service.nix
@@ -0,0 +1,37 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ nixpkgs.overlays = [
+ (self: super: rec {
+ emacs = pkgs.writeShellScriptBin "dummy-emacs" "" // {
+ outPath = "@emacs@";
+ };
+ emacsPackagesFor = _:
+ makeScope super.newScope (_: { emacsWithPackages = _: emacs; });
+ })
+ ];
+
+ programs.emacs.enable = true;
+ services.emacs.enable = true;
+ services.emacs.client.enable = true;
+
+ nmt.script = ''
+ assertPathNotExists home-files/.config/systemd/user/emacs.socket
+ assertFileExists home-files/.config/systemd/user/emacs.service
+ assertFileExists home-path/share/applications/emacsclient.desktop
+
+ assertFileContent home-files/.config/systemd/user/emacs.service \
+ ${
+ pkgs.substituteAll {
+ inherit (pkgs) runtimeShell;
+ src = ./emacs-service-emacs.service;
+ }
+ }
+ assertFileContent home-path/share/applications/emacsclient.desktop \
+ ${./emacs-emacsclient.desktop}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-socket-26-emacs.service b/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-socket-26-emacs.service
new file mode 100644
index 000000000000..2d731c7ee1a9
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-socket-26-emacs.service
@@ -0,0 +1,9 @@
+[Service]
+ExecStart=@runtimeShell@ -l -c "@emacs@/bin/emacs --fg-daemon='%T/emacs%U/server'"
+ExecStop=@emacs@/bin/emacsclient --eval '(kill-emacs 0)'
+Restart=on-failure
+
+[Unit]
+Description=Emacs: the extensible, self-documenting text editor
+Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/
+X-RestartIfChanged=false
diff --git a/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-socket-26-emacs.socket b/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-socket-26-emacs.socket
new file mode 100644
index 000000000000..d2fa78e22656
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-socket-26-emacs.socket
@@ -0,0 +1,12 @@
+[Install]
+WantedBy=sockets.target
+
+[Socket]
+DirectoryMode=0700
+FileDescriptorName=server
+ListenStream=%T/emacs%U/server
+SocketMode=0600
+
+[Unit]
+Description=Emacs: the extensible, self-documenting text editor
+Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/
diff --git a/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-socket-26.nix b/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-socket-26.nix
new file mode 100644
index 000000000000..65f06159e4c7
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-socket-26.nix
@@ -0,0 +1,40 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ nixpkgs.overlays = [
+ (self: super: rec {
+ emacs = pkgs.writeShellScriptBin "dummy-emacs-26.3" "" // {
+ outPath = "@emacs@";
+ };
+ emacsPackagesFor = _:
+ makeScope super.newScope (_: { emacsWithPackages = _: emacs; });
+ })
+ ];
+
+ programs.emacs.enable = true;
+ services.emacs.enable = true;
+ services.emacs.client.enable = true;
+ services.emacs.socketActivation.enable = true;
+
+ nmt.script = ''
+ assertFileExists home-files/.config/systemd/user/emacs.socket
+ assertFileExists home-files/.config/systemd/user/emacs.service
+ assertFileExists home-path/share/applications/emacsclient.desktop
+
+ assertFileContent home-files/.config/systemd/user/emacs.socket \
+ ${./emacs-socket-26-emacs.socket}
+ assertFileContent home-files/.config/systemd/user/emacs.service \
+ ${
+ pkgs.substituteAll {
+ inherit (pkgs) runtimeShell;
+ src = ./emacs-socket-26-emacs.service;
+ }
+ }
+ assertFileContent home-path/share/applications/emacsclient.desktop \
+ ${./emacs-emacsclient.desktop}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-socket-27-emacs.service b/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-socket-27-emacs.service
new file mode 100644
index 000000000000..408a5d24b5c3
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-socket-27-emacs.service
@@ -0,0 +1,9 @@
+[Service]
+ExecStart=@runtimeShell@ -l -c "@emacs@/bin/emacs --fg-daemon='%t/emacs/server'"
+ExecStop=@emacs@/bin/emacsclient --eval '(kill-emacs 0)'
+Restart=on-failure
+
+[Unit]
+Description=Emacs: the extensible, self-documenting text editor
+Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/
+X-RestartIfChanged=false
diff --git a/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-socket-27-emacs.socket b/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-socket-27-emacs.socket
new file mode 100644
index 000000000000..8fa68bf59110
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-socket-27-emacs.socket
@@ -0,0 +1,12 @@
+[Install]
+WantedBy=sockets.target
+
+[Socket]
+DirectoryMode=0700
+FileDescriptorName=server
+ListenStream=%t/emacs/server
+SocketMode=0600
+
+[Unit]
+Description=Emacs: the extensible, self-documenting text editor
+Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/
diff --git a/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-socket-27.nix b/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-socket-27.nix
new file mode 100644
index 000000000000..213dedca51cf
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/emacs/emacs-socket-27.nix
@@ -0,0 +1,42 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+in {
+ config = {
+ nixpkgs.overlays = [
+ (self: super: rec {
+ emacs = pkgs.writeShellScriptBin "dummy-emacs-27.0.91" "" // {
+ outPath = "@emacs@";
+ };
+ emacsPackagesFor = _:
+ makeScope super.newScope (_: { emacsWithPackages = _: emacs; });
+ })
+ ];
+
+ programs.emacs.enable = true;
+ services.emacs.enable = true;
+ services.emacs.client.enable = true;
+ services.emacs.socketActivation.enable = true;
+
+ nmt.script = ''
+ assertFileExists home-files/.config/systemd/user/emacs.socket
+ assertFileExists home-files/.config/systemd/user/emacs.service
+ assertFileExists home-path/share/applications/emacsclient.desktop
+
+ assertFileContent home-files/.config/systemd/user/emacs.socket \
+ ${./emacs-socket-27-emacs.socket}
+ assertFileContent home-files/.config/systemd/user/emacs.service \
+ ${
+ pkgs.substituteAll {
+ inherit (pkgs) runtimeShell;
+ src = ./emacs-socket-27-emacs.service;
+ }
+ }
+ assertFileContent home-path/share/applications/emacsclient.desktop \
+ ${./emacs-emacsclient.desktop}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/services/fluidsynth/default.nix b/infra/libkookie/home-manager/tests/modules/services/fluidsynth/default.nix
new file mode 100644
index 000000000000..58e9c5dcc330
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/fluidsynth/default.nix
@@ -0,0 +1 @@
+{ fluidsynth = import ./service.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/services/fluidsynth/service.nix b/infra/libkookie/home-manager/tests/modules/services/fluidsynth/service.nix
new file mode 100644
index 000000000000..8d53e75c0324
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/fluidsynth/service.nix
@@ -0,0 +1,24 @@
+{ config, pkgs, ... }: {
+ config = {
+ services.fluidsynth.enable = true;
+ services.fluidsynth.soundFont = "/path/to/soundFont";
+ services.fluidsynth.extraOptions = [ "--sample-rate 96000" ];
+
+ nixpkgs.overlays = [
+ (self: super: {
+ fluidsynth = pkgs.writeScriptBin "dummy-fluidsynth" "" // {
+ outPath = "@fluidsynth@";
+ };
+ })
+ ];
+
+ nmt.script = ''
+ serviceFile=home-files/.config/systemd/user/fluidsynth.service
+
+ assertFileExists $serviceFile
+
+ assertFileContains $serviceFile \
+ 'ExecStart=@fluidsynth@/bin/fluidsynth -a pulseaudio -si --sample-rate 96000 /path/to/soundFont'
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/services/kanshi/basic-configuration.conf b/infra/libkookie/home-manager/tests/modules/services/kanshi/basic-configuration.conf
new file mode 100644
index 000000000000..9d6442b985bb
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/kanshi/basic-configuration.conf
@@ -0,0 +1,15 @@
+profile desktop {
+ output "eDP-1" disable
+ output "Iiyama North America PLE2483H-DP" enable position 0,0
+ output "Iiyama North America PLE2483H-DP 1158765348486" enable mode 1920x1080 position 1920,0 scale 2.100000 transform flipped-270
+ exec echo "1 two 3"
+}
+
+profile nomad {
+ output "eDP-1" enable
+}
+
+profile test {
+ output "*" enable
+}
+
diff --git a/infra/libkookie/home-manager/tests/modules/services/kanshi/basic-configuration.nix b/infra/libkookie/home-manager/tests/modules/services/kanshi/basic-configuration.nix
new file mode 100644
index 000000000000..15fbbb9ceb12
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/kanshi/basic-configuration.nix
@@ -0,0 +1,52 @@
+{ config, pkgs, ... }: {
+ config = {
+ services.kanshi = {
+ enable = true;
+ package = pkgs.writeScriptBin "dummy-kanshi" "";
+ profiles = {
+ nomad = {
+ outputs = [{
+ criteria = "eDP-1";
+ status = "enable";
+ }];
+ };
+ desktop = {
+ exec = ''echo "1 two 3"'';
+ outputs = [
+ {
+ criteria = "eDP-1";
+ status = "disable";
+ }
+ {
+ criteria = "Iiyama North America PLE2483H-DP";
+ status = "enable";
+ position = "0,0";
+ }
+ {
+ criteria = "Iiyama North America PLE2483H-DP 1158765348486";
+ status = "enable";
+ position = "1920,0";
+ scale = 2.1;
+ mode = "1920x1080";
+ transform = "flipped-270";
+ }
+ ];
+ };
+ };
+ extraConfig = ''
+ profile test {
+ output "*" enable
+ }
+ '';
+ };
+
+ nmt.script = ''
+ serviceFile=home-files/.config/systemd/user/kanshi.service
+ assertFileExists $serviceFile
+
+ assertFileExists home-files/.config/kanshi/config
+ assertFileContent home-files/.config/kanshi/config \
+ ${./basic-configuration.conf}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/services/kanshi/default.nix b/infra/libkookie/home-manager/tests/modules/services/kanshi/default.nix
new file mode 100644
index 000000000000..cb6b2a6b79f9
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/kanshi/default.nix
@@ -0,0 +1 @@
+{ kanshi-basic-configuration = ./basic-configuration.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/services/lieer/default.nix b/infra/libkookie/home-manager/tests/modules/services/lieer/default.nix
new file mode 100644
index 000000000000..1d6b435a1cac
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/lieer/default.nix
@@ -0,0 +1 @@
+{ lieer-service = ./lieer-service.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/services/lieer/lieer-service-expected.service b/infra/libkookie/home-manager/tests/modules/services/lieer/lieer-service-expected.service
new file mode 100644
index 000000000000..1110e85c4756
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/lieer/lieer-service-expected.service
@@ -0,0 +1,8 @@
+[Service]
+ExecStart=@lieer@/bin/gmi sync
+Type=oneshot
+WorkingDirectory=/home/hm-user/Mail/hm@example.com
+
+[Unit]
+ConditionPathExists=/home/hm-user/Mail/hm@example.com/.gmailieer.json
+Description=lieer Gmail synchronization for hm@example.com
diff --git a/infra/libkookie/home-manager/tests/modules/services/lieer/lieer-service-expected.timer b/infra/libkookie/home-manager/tests/modules/services/lieer/lieer-service-expected.timer
new file mode 100644
index 000000000000..cb059ea47c4f
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/lieer/lieer-service-expected.timer
@@ -0,0 +1,9 @@
+[Install]
+WantedBy=timers.target
+
+[Timer]
+OnCalendar=*:0/5
+RandomizedDelaySec=30
+
+[Unit]
+Description=lieer Gmail synchronization for hm@example.com
diff --git a/infra/libkookie/home-manager/tests/modules/services/lieer/lieer-service.nix b/infra/libkookie/home-manager/tests/modules/services/lieer/lieer-service.nix
new file mode 100644
index 000000000000..03dcdc749b19
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/lieer/lieer-service.nix
@@ -0,0 +1,34 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ imports = [ ../../accounts/email-test-accounts.nix ];
+
+ config = {
+ services.lieer.enable = true;
+
+ accounts.email.accounts = {
+ "hm@example.com".lieer.enable = true;
+ "hm@example.com".lieer.sync.enable = true;
+ };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ gmailieer = pkgs.writeScriptBin "dummy-gmailieer" "" // {
+ outPath = "@lieer@";
+ };
+ })
+ ];
+
+ nmt.script = ''
+ assertFileExists home-files/.config/systemd/user/lieer-hm-example-com.service
+ assertFileExists home-files/.config/systemd/user/lieer-hm-example-com.timer
+
+ assertFileContent home-files/.config/systemd/user/lieer-hm-example-com.service \
+ ${./lieer-service-expected.service}
+ assertFileContent home-files/.config/systemd/user/lieer-hm-example-com.timer \
+ ${./lieer-service-expected.timer}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/services/pbgopy/default.nix b/infra/libkookie/home-manager/tests/modules/services/pbgopy/default.nix
new file mode 100644
index 000000000000..c2c9d485ddba
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/pbgopy/default.nix
@@ -0,0 +1 @@
+{ pbgopy = import ./service.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/services/pbgopy/service.nix b/infra/libkookie/home-manager/tests/modules/services/pbgopy/service.nix
new file mode 100644
index 000000000000..2212143949ab
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/pbgopy/service.nix
@@ -0,0 +1,22 @@
+{ config, pkgs, ... }: {
+ config = {
+ services.pbgopy.enable = true;
+
+ nixpkgs.overlays = [
+ (self: super: {
+ pbgopy = pkgs.writeScriptBin "dummy-pbgopy" "" // {
+ outPath = "@pbgopy@";
+ };
+ })
+ ];
+
+ nmt.script = ''
+ serviceFile=home-files/.config/systemd/user/pbgopy.service
+
+ assertFileExists $serviceFile
+
+ assertFileContains $serviceFile \
+ 'ExecStart=@pbgopy@/bin/pbgopy serve --ttl 24h'
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/services/polybar/basic-configuration.conf b/infra/libkookie/home-manager/tests/modules/services/polybar/basic-configuration.conf
new file mode 100644
index 000000000000..54448a705fcc
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/polybar/basic-configuration.conf
@@ -0,0 +1,21 @@
+[bar/top]
+height=3%
+modules-center=date
+monitor=${env:MONITOR:eDP1}
+radius=0
+width=100%
+
+[module/date]
+date=%d.%m.%y
+internal=5
+label=%time% %date%
+time=%H:%M
+type=internal/date
+
+[module/date]
+type = internal/date
+interval = 5
+date = "%d.%m.%y"
+time = %H:%M
+format-prefix-foreground = ${colors.foreground-alt}
+label = %time% %date%
diff --git a/infra/libkookie/home-manager/tests/modules/services/polybar/basic-configuration.nix b/infra/libkookie/home-manager/tests/modules/services/polybar/basic-configuration.nix
new file mode 100644
index 000000000000..a8886dab6d5c
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/polybar/basic-configuration.nix
@@ -0,0 +1,48 @@
+{ config, pkgs, ... }:
+
+{
+ config = {
+ services.polybar = {
+ enable = true;
+ package = pkgs.writeScriptBin "dummy-polybar" "";
+ script = "polybar bar &";
+ config = {
+ "bar/top" = {
+ monitor = "\${env:MONITOR:eDP1}";
+ width = "100%";
+ height = "3%";
+ radius = 0;
+ modules-center = "date";
+ };
+ "module/date" = {
+ type = "internal/date";
+ internal = 5;
+ date = "%d.%m.%y";
+ time = "%H:%M";
+ label = "%time% %date%";
+ };
+ };
+ extraConfig = ''
+ [module/date]
+ type = internal/date
+ interval = 5
+ date = "%d.%m.%y"
+ time = %H:%M
+ format-prefix-foreground = ''${colors.foreground-alt}
+ label = %time% %date%
+ '';
+ };
+
+ nmt.script = ''
+ serviceFile=home-files/.config/systemd/user/polybar.service
+
+ assertFileExists $serviceFile
+ assertFileRegex $serviceFile 'X-Restart-Triggers=.*polybar\.conf'
+ assertFileRegex $serviceFile 'ExecStart=.*/bin/polybar-start'
+
+ assertFileExists home-files/.config/polybar/config
+ assertFileContent home-files/.config/polybar/config \
+ ${./basic-configuration.conf}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/services/polybar/default.nix b/infra/libkookie/home-manager/tests/modules/services/polybar/default.nix
new file mode 100644
index 000000000000..94d5d3cde203
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/polybar/default.nix
@@ -0,0 +1 @@
+{ polybar-basic-configuration = ./basic-configuration.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/services/sxhkd/configuration.nix b/infra/libkookie/home-manager/tests/modules/services/sxhkd/configuration.nix
new file mode 100644
index 000000000000..03206a8d52b8
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/sxhkd/configuration.nix
@@ -0,0 +1,33 @@
+{ config, pkgs, ... }: {
+ config = {
+ services.sxhkd = {
+ enable = true;
+
+ keybindings = {
+ "super + a" = "run command a";
+ "super + b" = null;
+ "super + Shift + b" = "run command b";
+ };
+
+ extraConfig = ''
+ super + c
+ call command c
+
+ # comment
+ super + d
+ call command d
+ '';
+ };
+
+ nixpkgs.overlays =
+ [ (self: super: { sxhkd = pkgs.writeScriptBin "dummy-sxhkd" ""; }) ];
+
+ nmt.script = ''
+ sxhkdrc=home-files/.config/sxhkd/sxhkdrc
+
+ assertFileExists $sxhkdrc
+
+ assertFileContent $sxhkdrc ${./sxhkdrc}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/services/sxhkd/default.nix b/infra/libkookie/home-manager/tests/modules/services/sxhkd/default.nix
new file mode 100644
index 000000000000..ec25252cee01
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/sxhkd/default.nix
@@ -0,0 +1,4 @@
+{
+ sxhkd-configuration = ./configuration.nix;
+ sxhkd-service = ./service.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/services/sxhkd/service.nix b/infra/libkookie/home-manager/tests/modules/services/sxhkd/service.nix
new file mode 100644
index 000000000000..9b4fd70cc55f
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/sxhkd/service.nix
@@ -0,0 +1,20 @@
+{ config, ... }:
+{
+ config = {
+ services.sxhkd = {
+ enable = true;
+ extraPath = "/home/the-user/bin:/extra/path/bin";
+ };
+
+ nmt.script = ''
+ serviceFile=home-files/.config/systemd/user/sxhkd.service
+
+ assertFileExists $serviceFile
+
+ assertFileRegex $serviceFile 'ExecStart=.*/bin/sxhkd'
+
+ assertFileRegex $serviceFile \
+ 'Environment=PATH=.*\.nix-profile/bin:/home/the-user/bin:/extra/path/bin'
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/services/sxhkd/sxhkdrc b/infra/libkookie/home-manager/tests/modules/services/sxhkd/sxhkdrc
new file mode 100644
index 000000000000..c8883464b29c
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/sxhkd/sxhkdrc
@@ -0,0 +1,13 @@
+super + Shift + b
+ run command b
+
+super + a
+ run command a
+
+
+super + c
+ call command c
+
+# comment
+super + d
+ call command d
diff --git a/infra/libkookie/home-manager/tests/modules/services/window-managers/i3/default.nix b/infra/libkookie/home-manager/tests/modules/services/window-managers/i3/default.nix
new file mode 100644
index 000000000000..c523dfcd04b7
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/window-managers/i3/default.nix
@@ -0,0 +1,4 @@
+{
+ i3-followmouse = ./i3-followmouse.nix;
+ i3-keybindings = ./i3-keybindings.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/services/window-managers/i3/i3-followmouse-expected.conf b/infra/libkookie/home-manager/tests/modules/services/window-managers/i3/i3-followmouse-expected.conf
new file mode 100644
index 000000000000..729605be469d
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/window-managers/i3/i3-followmouse-expected.conf
@@ -0,0 +1,105 @@
+font pango:monospace 8
+floating_modifier Mod1
+new_window normal 2
+new_float normal 2
+hide_edge_borders none
+force_focus_wrapping no
+focus_follows_mouse no
+focus_on_window_activation smart
+mouse_warping output
+workspace_layout default
+workspace_auto_back_and_forth no
+
+client.focused #4c7899 #285577 #ffffff #2e9ef4 #285577
+client.focused_inactive #333333 #5f676a #ffffff #484e50 #5f676a
+client.unfocused #333333 #222222 #888888 #292d2e #222222
+client.urgent #2f343a #900000 #ffffff #900000 #900000
+client.placeholder #000000 #0c0c0c #ffffff #000000 #0c0c0c
+client.background #ffffff
+
+bindsym Mod1+0 workspace number 10
+bindsym Mod1+1 workspace number 1
+bindsym Mod1+2 workspace number 2
+bindsym Mod1+3 workspace number 3
+bindsym Mod1+4 workspace number 4
+bindsym Mod1+5 workspace number 5
+bindsym Mod1+6 workspace number 6
+bindsym Mod1+7 workspace number 7
+bindsym Mod1+8 workspace number 8
+bindsym Mod1+9 workspace number 9
+bindsym Mod1+Down focus down
+bindsym Mod1+Left focus left
+bindsym Mod1+Return exec i3-sensible-terminal
+bindsym Mod1+Right focus right
+bindsym Mod1+Shift+0 move container to workspace number 10
+bindsym Mod1+Shift+1 move container to workspace number 1
+bindsym Mod1+Shift+2 move container to workspace number 2
+bindsym Mod1+Shift+3 move container to workspace number 3
+bindsym Mod1+Shift+4 move container to workspace number 4
+bindsym Mod1+Shift+5 move container to workspace number 5
+bindsym Mod1+Shift+6 move container to workspace number 6
+bindsym Mod1+Shift+7 move container to workspace number 7
+bindsym Mod1+Shift+8 move container to workspace number 8
+bindsym Mod1+Shift+9 move container to workspace number 9
+bindsym Mod1+Shift+Down move down
+bindsym Mod1+Shift+Left move left
+bindsym Mod1+Shift+Right move right
+bindsym Mod1+Shift+Up move up
+bindsym Mod1+Shift+c reload
+bindsym Mod1+Shift+e exec i3-nagbar -t warning -m 'Do you want to exit i3?' -b 'Yes' 'i3-msg exit'
+bindsym Mod1+Shift+minus move scratchpad
+bindsym Mod1+Shift+q kill
+bindsym Mod1+Shift+r restart
+bindsym Mod1+Shift+space floating toggle
+bindsym Mod1+Up focus up
+bindsym Mod1+a focus parent
+bindsym Mod1+d exec @dmenu@/bin/dmenu_run
+bindsym Mod1+e layout toggle split
+bindsym Mod1+f fullscreen toggle
+bindsym Mod1+h split h
+bindsym Mod1+minus scratchpad show
+bindsym Mod1+r mode resize
+bindsym Mod1+s layout stacking
+bindsym Mod1+space focus mode_toggle
+bindsym Mod1+v split v
+bindsym Mod1+w layout tabbed
+
+mode "resize" {
+bindsym Down resize grow height 10 px or 10 ppt
+bindsym Escape mode default
+bindsym Left resize shrink width 10 px or 10 ppt
+bindsym Return mode default
+bindsym Right resize grow width 10 px or 10 ppt
+bindsym Up resize shrink height 10 px or 10 ppt
+}
+
+
+bar {
+
+ font pango:monospace 8
+ mode dock
+ hidden_state hide
+ position bottom
+ status_command @i3status@/bin/i3status
+ i3bar_command @i3@/bin/i3bar
+ workspace_buttons yes
+ strip_workspace_numbers no
+ tray_output primary
+ colors {
+ background #000000
+ statusline #ffffff
+ separator #666666
+ focused_workspace #4c7899 #285577 #ffffff
+ active_workspace #333333 #5f676a #ffffff
+ inactive_workspace #333333 #222222 #888888
+ urgent_workspace #2f343a #900000 #ffffff
+ binding_mode #2f343a #900000 #ffffff
+ }
+
+}
+
+
+
+
+
+
diff --git a/infra/libkookie/home-manager/tests/modules/services/window-managers/i3/i3-followmouse.nix b/infra/libkookie/home-manager/tests/modules/services/window-managers/i3/i3-followmouse.nix
new file mode 100644
index 000000000000..43e15cda3966
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/window-managers/i3/i3-followmouse.nix
@@ -0,0 +1,31 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+ config = {
+ xsession.windowManager.i3 = {
+ enable = true;
+
+ config.focus.followMouse = false;
+ };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ dmenu = super.dmenu // { outPath = "@dmenu@"; };
+
+ i3 = super.writeScriptBin "i3" "" // { outPath = "@i3@"; };
+
+ i3-gaps = super.writeScriptBin "i3" "" // { outPath = "@i3-gaps@"; };
+
+ i3status = super.i3status // { outPath = "@i3status@"; };
+ })
+ ];
+
+ nmt.script = ''
+ assertFileExists home-files/.config/i3/config
+ assertFileContent home-files/.config/i3/config \
+ ${./i3-followmouse-expected.conf}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/services/window-managers/i3/i3-keybindings-expected.conf b/infra/libkookie/home-manager/tests/modules/services/window-managers/i3/i3-keybindings-expected.conf
new file mode 100644
index 000000000000..1e385e8c7345
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/window-managers/i3/i3-keybindings-expected.conf
@@ -0,0 +1,106 @@
+font pango:monospace 8
+floating_modifier Mod1
+new_window normal 2
+new_float normal 2
+hide_edge_borders none
+force_focus_wrapping no
+focus_follows_mouse yes
+focus_on_window_activation smart
+mouse_warping output
+workspace_layout default
+workspace_auto_back_and_forth no
+
+client.focused #4c7899 #285577 #ffffff #2e9ef4 #285577
+client.focused_inactive #333333 #5f676a #ffffff #484e50 #5f676a
+client.unfocused #333333 #222222 #888888 #292d2e #222222
+client.urgent #2f343a #900000 #ffffff #900000 #900000
+client.placeholder #000000 #0c0c0c #ffffff #000000 #0c0c0c
+client.background #ffffff
+
+bindsym Mod1+0 workspace number 10
+bindsym Mod1+1 workspace number 1
+bindsym Mod1+2 workspace number 2
+bindsym Mod1+3 workspace number 3
+bindsym Mod1+4 workspace number 4
+bindsym Mod1+5 workspace number 5
+bindsym Mod1+6 workspace number 6
+bindsym Mod1+7 workspace number 7
+bindsym Mod1+8 workspace number 8
+bindsym Mod1+9 workspace number 9
+bindsym Mod1+Down focus down
+bindsym Mod1+Invented invented-key-command
+bindsym Mod1+Left overridden-command
+bindsym Mod1+Return exec i3-sensible-terminal
+
+bindsym Mod1+Shift+0 move container to workspace number 10
+bindsym Mod1+Shift+1 move container to workspace number 1
+bindsym Mod1+Shift+2 move container to workspace number 2
+bindsym Mod1+Shift+3 move container to workspace number 3
+bindsym Mod1+Shift+4 move container to workspace number 4
+bindsym Mod1+Shift+5 move container to workspace number 5
+bindsym Mod1+Shift+6 move container to workspace number 6
+bindsym Mod1+Shift+7 move container to workspace number 7
+bindsym Mod1+Shift+8 move container to workspace number 8
+bindsym Mod1+Shift+9 move container to workspace number 9
+bindsym Mod1+Shift+Down move down
+bindsym Mod1+Shift+Left move left
+bindsym Mod1+Shift+Right move right
+bindsym Mod1+Shift+Up move up
+bindsym Mod1+Shift+c reload
+bindsym Mod1+Shift+e exec i3-nagbar -t warning -m 'Do you want to exit i3?' -b 'Yes' 'i3-msg exit'
+bindsym Mod1+Shift+minus move scratchpad
+bindsym Mod1+Shift+q kill
+bindsym Mod1+Shift+r restart
+bindsym Mod1+Shift+space floating toggle
+bindsym Mod1+Up focus up
+bindsym Mod1+a focus parent
+bindsym Mod1+d exec @dmenu@/bin/dmenu_run
+bindsym Mod1+e layout toggle split
+bindsym Mod1+f fullscreen toggle
+bindsym Mod1+h split h
+bindsym Mod1+minus scratchpad show
+bindsym Mod1+r mode resize
+bindsym Mod1+s layout stacking
+bindsym Mod1+space focus mode_toggle
+bindsym Mod1+v split v
+bindsym Mod1+w layout tabbed
+
+mode "resize" {
+bindsym Down resize grow height 10 px or 10 ppt
+bindsym Escape mode default
+bindsym Left resize shrink width 10 px or 10 ppt
+bindsym Return mode default
+bindsym Right resize grow width 10 px or 10 ppt
+bindsym Up resize shrink height 10 px or 10 ppt
+}
+
+
+bar {
+
+ font pango:monospace 8
+ mode dock
+ hidden_state hide
+ position bottom
+ status_command @i3status@/bin/i3status
+ i3bar_command @i3@/bin/i3bar
+ workspace_buttons yes
+ strip_workspace_numbers no
+ tray_output primary
+ colors {
+ background #000000
+ statusline #ffffff
+ separator #666666
+ focused_workspace #4c7899 #285577 #ffffff
+ active_workspace #333333 #5f676a #ffffff
+ inactive_workspace #333333 #222222 #888888
+ urgent_workspace #2f343a #900000 #ffffff
+ binding_mode #2f343a #900000 #ffffff
+ }
+
+}
+
+
+
+
+
+
diff --git a/infra/libkookie/home-manager/tests/modules/services/window-managers/i3/i3-keybindings.nix b/infra/libkookie/home-manager/tests/modules/services/window-managers/i3/i3-keybindings.nix
new file mode 100644
index 000000000000..0458b8744a46
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/window-managers/i3/i3-keybindings.nix
@@ -0,0 +1,36 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+ config = {
+ xsession.windowManager.i3 = {
+ enable = true;
+
+ config.keybindings =
+ let modifier = config.xsession.windowManager.i3.config.modifier;
+ in lib.mkOptionDefault {
+ "${modifier}+Left" = "overridden-command";
+ "${modifier}+Right" = null;
+ "${modifier}+Invented" = "invented-key-command";
+ };
+ };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ dmenu = super.dmenu // { outPath = "@dmenu@"; };
+ i3 = super.writeScriptBin "i3" "" // { outPath = "@i3@"; };
+ i3-gaps = super.writeScriptBin "i3-gaps" "" // {
+ outPath = "@i3-gaps@";
+ };
+ i3status = super.i3status // { outPath = "@i3status@"; };
+ })
+ ];
+
+ nmt.script = ''
+ assertFileExists home-files/.config/i3/config
+ assertFileContent home-files/.config/i3/config \
+ ${./i3-keybindings-expected.conf}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/services/window-managers/sway/default.nix b/infra/libkookie/home-manager/tests/modules/services/window-managers/sway/default.nix
new file mode 100644
index 000000000000..b9c0ab5e099d
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/window-managers/sway/default.nix
@@ -0,0 +1,6 @@
+{
+ sway-default = ./sway-default.nix;
+ sway-post-2003 = ./sway-post-2003.nix;
+ sway-followmouse = ./sway-followmouse.nix;
+ sway-followmouse-legacy = ./sway-followmouse-legacy.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-default.conf b/infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-default.conf
new file mode 100644
index 000000000000..da5b1f47eef2
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-default.conf
@@ -0,0 +1,117 @@
+font pango:monospace 8
+floating_modifier Mod1
+default_border pixel 2
+default_floating_border pixel 2
+hide_edge_borders none
+focus_wrapping no
+focus_follows_mouse yes
+focus_on_window_activation smart
+mouse_warping output
+workspace_layout default
+workspace_auto_back_and_forth no
+
+client.focused #4c7899 #285577 #ffffff #2e9ef4 #285577
+client.focused_inactive #333333 #5f676a #ffffff #484e50 #5f676a
+client.unfocused #333333 #222222 #888888 #292d2e #222222
+client.urgent #2f343a #900000 #ffffff #900000 #900000
+client.placeholder #000000 #0c0c0c #ffffff #000000 #0c0c0c
+client.background #ffffff
+
+bindsym Mod1+1 workspace number 1
+bindsym Mod1+2 workspace number 2
+bindsym Mod1+3 workspace number 3
+bindsym Mod1+4 workspace number 4
+bindsym Mod1+5 workspace number 5
+bindsym Mod1+6 workspace number 6
+bindsym Mod1+7 workspace number 7
+bindsym Mod1+8 workspace number 8
+bindsym Mod1+9 workspace number 9
+bindsym Mod1+Down focus down
+bindsym Mod1+Left focus left
+bindsym Mod1+Return exec @rxvt-unicode-unwrapped@/bin/urxvt
+bindsym Mod1+Right focus right
+bindsym Mod1+Shift+1 move container to workspace number 1
+bindsym Mod1+Shift+2 move container to workspace number 2
+bindsym Mod1+Shift+3 move container to workspace number 3
+bindsym Mod1+Shift+4 move container to workspace number 4
+bindsym Mod1+Shift+5 move container to workspace number 5
+bindsym Mod1+Shift+6 move container to workspace number 6
+bindsym Mod1+Shift+7 move container to workspace number 7
+bindsym Mod1+Shift+8 move container to workspace number 8
+bindsym Mod1+Shift+9 move container to workspace number 9
+bindsym Mod1+Shift+Down move down
+bindsym Mod1+Shift+Left move left
+bindsym Mod1+Shift+Right move right
+bindsym Mod1+Shift+Up move up
+bindsym Mod1+Shift+c reload
+bindsym Mod1+Shift+e exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -b 'Yes, exit sway' 'swaymsg exit'
+bindsym Mod1+Shift+h move left
+bindsym Mod1+Shift+j move down
+bindsym Mod1+Shift+k move up
+bindsym Mod1+Shift+l move right
+bindsym Mod1+Shift+minus move scratchpad
+bindsym Mod1+Shift+q kill
+bindsym Mod1+Shift+space floating toggle
+bindsym Mod1+Up focus up
+bindsym Mod1+a focus parent
+bindsym Mod1+b splith
+bindsym Mod1+d exec @dmenu@/bin/dmenu_run
+bindsym Mod1+e layout toggle split
+bindsym Mod1+f fullscreen toggle
+bindsym Mod1+h focus left
+bindsym Mod1+j focus down
+bindsym Mod1+k focus up
+bindsym Mod1+l focus right
+bindsym Mod1+minus scratchpad show
+bindsym Mod1+r mode resize
+bindsym Mod1+s layout stacking
+bindsym Mod1+space focus mode_toggle
+bindsym Mod1+v splitv
+bindsym Mod1+w layout tabbed
+
+
+
+mode "resize" {
+bindsym Down resize grow height 10 px
+bindsym Escape mode default
+bindsym Left resize shrink width 10 px
+bindsym Return mode default
+bindsym Right resize grow width 10 px
+bindsym Up resize shrink height 10 px
+bindsym h resize shrink width 10 px
+bindsym j resize grow height 10 px
+bindsym k resize shrink height 10 px
+bindsym l resize grow width 10 px
+}
+
+
+bar {
+
+ font pango:monospace 8
+ mode dock
+ hidden_state hide
+ position bottom
+ status_command @i3status@/bin/i3status
+ swaybar_command @sway/bin/swaybar
+ workspace_buttons yes
+ strip_workspace_numbers no
+ tray_output primary
+ colors {
+ background #000000
+ statusline #ffffff
+ separator #666666
+ focused_workspace #4c7899 #285577 #ffffff
+ active_workspace #333333 #5f676a #ffffff
+ inactive_workspace #333333 #222222 #888888
+ urgent_workspace #2f343a #900000 #ffffff
+ binding_mode #2f343a #900000 #ffffff
+ }
+
+}
+
+
+
+
+
+
+exec "systemctl --user import-environment; systemctl --user start sway-session.target"
diff --git a/infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-default.nix b/infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-default.nix
new file mode 100644
index 000000000000..0ccedbfda8ac
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-default.nix
@@ -0,0 +1,36 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ dummy-package = pkgs.runCommandLocal "dummy-package" { } "mkdir $out";
+
+in {
+ config = {
+ wayland.windowManager.sway = {
+ enable = true;
+ package = dummy-package // { outPath = "@sway"; };
+ # overriding findutils causes issues
+ config.menu = "${pkgs.dmenu}/bin/dmenu_run";
+ };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ dmenu = dummy-package // { outPath = "@dmenu@"; };
+ rxvt-unicode-unwrapped = dummy-package // {
+ outPath = "@rxvt-unicode-unwrapped@";
+ };
+ i3status = dummy-package // { outPath = "@i3status@"; };
+ sway = dummy-package // { outPath = "@sway@"; };
+ xwayland = dummy-package // { outPath = "@xwayland@"; };
+ })
+ ];
+
+ nmt.script = ''
+ assertFileExists home-files/.config/sway/config
+ assertFileContent home-files/.config/sway/config \
+ ${./sway-default.conf}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-followmouse-expected.conf b/infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-followmouse-expected.conf
new file mode 100644
index 000000000000..198ce4bd37fa
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-followmouse-expected.conf
@@ -0,0 +1,94 @@
+font pango:monospace 8
+floating_modifier Mod1
+default_border pixel 2
+default_floating_border pixel 2
+hide_edge_borders none
+focus_wrapping no
+focus_follows_mouse always
+focus_on_window_activation smart
+mouse_warping output
+workspace_layout default
+workspace_auto_back_and_forth no
+
+client.focused #4c7899 #285577 #ffffff #2e9ef4 #285577
+client.focused_inactive #333333 #5f676a #ffffff #484e50 #5f676a
+client.unfocused #333333 #222222 #888888 #292d2e #222222
+client.urgent #2f343a #900000 #ffffff #900000 #900000
+client.placeholder #000000 #0c0c0c #ffffff #000000 #0c0c0c
+client.background #ffffff
+
+bindsym Mod1+1 workspace number 1
+bindsym Mod1+2 workspace number 2
+bindsym Mod1+3 workspace number 3
+bindsym Mod1+4 workspace number 4
+bindsym Mod1+5 workspace number 5
+bindsym Mod1+6 workspace number 6
+bindsym Mod1+7 workspace number 7
+bindsym Mod1+8 workspace number 8
+bindsym Mod1+9 workspace number 9
+bindsym Mod1+Down focus down
+bindsym Mod1+Left focus left
+bindsym Mod1+Return exec @rxvt-unicode-unwrapped@/bin/urxvt
+bindsym Mod1+Right focus right
+bindsym Mod1+Shift+1 move container to workspace number 1
+bindsym Mod1+Shift+2 move container to workspace number 2
+bindsym Mod1+Shift+3 move container to workspace number 3
+bindsym Mod1+Shift+4 move container to workspace number 4
+bindsym Mod1+Shift+5 move container to workspace number 5
+bindsym Mod1+Shift+6 move container to workspace number 6
+bindsym Mod1+Shift+7 move container to workspace number 7
+bindsym Mod1+Shift+8 move container to workspace number 8
+bindsym Mod1+Shift+9 move container to workspace number 9
+bindsym Mod1+Shift+Down move down
+bindsym Mod1+Shift+Left move left
+bindsym Mod1+Shift+Right move right
+bindsym Mod1+Shift+Up move up
+bindsym Mod1+Shift+c reload
+bindsym Mod1+Shift+e exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -b 'Yes, exit sway' 'swaymsg exit'
+bindsym Mod1+Shift+h move left
+bindsym Mod1+Shift+j move down
+bindsym Mod1+Shift+k move up
+bindsym Mod1+Shift+l move right
+bindsym Mod1+Shift+minus move scratchpad
+bindsym Mod1+Shift+q kill
+bindsym Mod1+Shift+space floating toggle
+bindsym Mod1+Up focus up
+bindsym Mod1+a focus parent
+bindsym Mod1+b splith
+bindsym Mod1+d exec @dmenu@/bin/dmenu_run
+bindsym Mod1+e layout toggle split
+bindsym Mod1+f fullscreen toggle
+bindsym Mod1+h focus left
+bindsym Mod1+j focus down
+bindsym Mod1+k focus up
+bindsym Mod1+l focus right
+bindsym Mod1+minus scratchpad show
+bindsym Mod1+r mode resize
+bindsym Mod1+s layout stacking
+bindsym Mod1+space focus mode_toggle
+bindsym Mod1+v splitv
+bindsym Mod1+w layout tabbed
+
+
+
+mode "resize" {
+bindsym Down resize grow height 10 px
+bindsym Escape mode default
+bindsym Left resize shrink width 10 px
+bindsym Return mode default
+bindsym Right resize grow width 10 px
+bindsym Up resize shrink height 10 px
+bindsym h resize shrink width 10 px
+bindsym j resize grow height 10 px
+bindsym k resize shrink height 10 px
+bindsym l resize grow width 10 px
+}
+
+
+
+
+
+
+
+
+exec "systemctl --user import-environment; systemctl --user start sway-session.target"
diff --git a/infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-followmouse-legacy-expected.conf b/infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-followmouse-legacy-expected.conf
new file mode 100644
index 000000000000..cbc55722a012
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-followmouse-legacy-expected.conf
@@ -0,0 +1,94 @@
+font pango:monospace 8
+floating_modifier Mod1
+default_border pixel 2
+default_floating_border pixel 2
+hide_edge_borders none
+focus_wrapping no
+focus_follows_mouse no
+focus_on_window_activation smart
+mouse_warping output
+workspace_layout default
+workspace_auto_back_and_forth no
+
+client.focused #4c7899 #285577 #ffffff #2e9ef4 #285577
+client.focused_inactive #333333 #5f676a #ffffff #484e50 #5f676a
+client.unfocused #333333 #222222 #888888 #292d2e #222222
+client.urgent #2f343a #900000 #ffffff #900000 #900000
+client.placeholder #000000 #0c0c0c #ffffff #000000 #0c0c0c
+client.background #ffffff
+
+bindsym Mod1+1 workspace number 1
+bindsym Mod1+2 workspace number 2
+bindsym Mod1+3 workspace number 3
+bindsym Mod1+4 workspace number 4
+bindsym Mod1+5 workspace number 5
+bindsym Mod1+6 workspace number 6
+bindsym Mod1+7 workspace number 7
+bindsym Mod1+8 workspace number 8
+bindsym Mod1+9 workspace number 9
+bindsym Mod1+Down focus down
+bindsym Mod1+Left focus left
+bindsym Mod1+Return exec @rxvt-unicode-unwrapped@/bin/urxvt
+bindsym Mod1+Right focus right
+bindsym Mod1+Shift+1 move container to workspace number 1
+bindsym Mod1+Shift+2 move container to workspace number 2
+bindsym Mod1+Shift+3 move container to workspace number 3
+bindsym Mod1+Shift+4 move container to workspace number 4
+bindsym Mod1+Shift+5 move container to workspace number 5
+bindsym Mod1+Shift+6 move container to workspace number 6
+bindsym Mod1+Shift+7 move container to workspace number 7
+bindsym Mod1+Shift+8 move container to workspace number 8
+bindsym Mod1+Shift+9 move container to workspace number 9
+bindsym Mod1+Shift+Down move down
+bindsym Mod1+Shift+Left move left
+bindsym Mod1+Shift+Right move right
+bindsym Mod1+Shift+Up move up
+bindsym Mod1+Shift+c reload
+bindsym Mod1+Shift+e exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -b 'Yes, exit sway' 'swaymsg exit'
+bindsym Mod1+Shift+h move left
+bindsym Mod1+Shift+j move down
+bindsym Mod1+Shift+k move up
+bindsym Mod1+Shift+l move right
+bindsym Mod1+Shift+minus move scratchpad
+bindsym Mod1+Shift+q kill
+bindsym Mod1+Shift+space floating toggle
+bindsym Mod1+Up focus up
+bindsym Mod1+a focus parent
+bindsym Mod1+b splith
+bindsym Mod1+d exec @dmenu@/bin/dmenu_run
+bindsym Mod1+e layout toggle split
+bindsym Mod1+f fullscreen toggle
+bindsym Mod1+h focus left
+bindsym Mod1+j focus down
+bindsym Mod1+k focus up
+bindsym Mod1+l focus right
+bindsym Mod1+minus scratchpad show
+bindsym Mod1+r mode resize
+bindsym Mod1+s layout stacking
+bindsym Mod1+space focus mode_toggle
+bindsym Mod1+v splitv
+bindsym Mod1+w layout tabbed
+
+
+
+mode "resize" {
+bindsym Down resize grow height 10 px
+bindsym Escape mode default
+bindsym Left resize shrink width 10 px
+bindsym Return mode default
+bindsym Right resize grow width 10 px
+bindsym Up resize shrink height 10 px
+bindsym h resize shrink width 10 px
+bindsym j resize grow height 10 px
+bindsym k resize shrink height 10 px
+bindsym l resize grow width 10 px
+}
+
+
+
+
+
+
+
+
+exec "systemctl --user import-environment; systemctl --user start sway-session.target"
diff --git a/infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-followmouse-legacy.nix b/infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-followmouse-legacy.nix
new file mode 100644
index 000000000000..9ed0ceaf042c
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-followmouse-legacy.nix
@@ -0,0 +1,44 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ dummy-package = pkgs.runCommandLocal "dummy-package" { } "mkdir $out";
+
+in {
+ config = {
+ wayland.windowManager.sway = {
+ enable = true;
+ package = dummy-package // { outPath = "@sway"; };
+
+ config = {
+ focus.followMouse = false;
+ menu = "${pkgs.dmenu}/bin/dmenu_run";
+ bars = [ ];
+ };
+ };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ dmenu = dummy-package // { outPath = "@dmenu@"; };
+ rxvt-unicode-unwrapped = dummy-package // {
+ outPath = "@rxvt-unicode-unwrapped@";
+ };
+ sway = dummy-package // { outPath = "@sway@"; };
+ sway-unwrapped = dummy-package // {
+ outPath = "@sway-unwrapped@";
+ version = "1";
+ };
+ swaybg = dummy-package // { outPath = "@swaybg@"; };
+ xwayland = dummy-package // { outPath = "@xwayland@"; };
+ })
+ ];
+
+ nmt.script = ''
+ assertFileExists home-files/.config/sway/config
+ assertFileContent home-files/.config/sway/config \
+ ${./sway-followmouse-legacy-expected.conf}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-followmouse.nix b/infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-followmouse.nix
new file mode 100644
index 000000000000..8d54eccf73ff
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-followmouse.nix
@@ -0,0 +1,41 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ dummy-package = pkgs.runCommandLocal "dummy-package" { } "mkdir $out";
+
+in {
+ config = {
+ wayland.windowManager.sway = {
+ enable = true;
+ package = dummy-package // { outPath = "@sway"; };
+
+ config = {
+ focus.followMouse = "always";
+ menu = "${pkgs.dmenu}/bin/dmenu_run";
+ bars = [ ];
+ };
+ };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ dmenu = dummy-package // { outPath = "@dmenu@"; };
+ rxvt-unicode-unwrapped = dummy-package // {
+ outPath = "@rxvt-unicode-unwrapped@";
+ };
+ sway = dummy-package // { outPath = "@sway@"; };
+ sway-unwrapped = dummy-package // { version = "1"; };
+ swaybg = dummy-package // { outPath = "@swaybg@"; };
+ xwayland = dummy-package // { outPath = "@xwayland@"; };
+ })
+ ];
+
+ nmt.script = ''
+ assertFileExists home-files/.config/sway/config
+ assertFileContent home-files/.config/sway/config \
+ ${./sway-followmouse-expected.conf}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-post-2003.nix b/infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-post-2003.nix
new file mode 100644
index 000000000000..740e7e526f02
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/window-managers/sway/sway-post-2003.nix
@@ -0,0 +1,38 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ dummy-package = pkgs.runCommandLocal "dummy-package" { } "mkdir $out";
+
+in {
+ config = {
+ home.stateVersion = "20.09";
+
+ wayland.windowManager.sway = {
+ enable = true;
+ package = dummy-package // { outPath = "@sway"; };
+ # overriding findutils causes issues
+ config.menu = "${pkgs.dmenu}/bin/dmenu_run";
+ };
+
+ nixpkgs.overlays = [
+ (self: super: {
+ dmenu = dummy-package // { outPath = "@dmenu@"; };
+ rxvt-unicode-unwrapped = dummy-package // {
+ outPath = "@rxvt-unicode-unwrapped@";
+ };
+ sway = dummy-package // { outPath = "@sway@"; };
+ i3status = dummy-package // { outPath = "@i3status@"; };
+ xwayland = dummy-package // { outPath = "@xwayland@"; };
+ })
+ ];
+
+ nmt.script = ''
+ assertFileExists home-files/.config/sway/config
+ assertFileContent home-files/.config/sway/config \
+ ${./sway-default.conf}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/services/wlsunset/default.nix b/infra/libkookie/home-manager/tests/modules/services/wlsunset/default.nix
new file mode 100644
index 000000000000..d59af701535b
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/wlsunset/default.nix
@@ -0,0 +1 @@
+{ wlsunset-service = ./wlsunset-service.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/services/wlsunset/wlsunset-service-expected.service b/infra/libkookie/home-manager/tests/modules/services/wlsunset/wlsunset-service-expected.service
new file mode 100644
index 000000000000..f0cf96f3ad0a
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/wlsunset/wlsunset-service-expected.service
@@ -0,0 +1,9 @@
+[Install]
+WantedBy=test.target
+
+[Service]
+ExecStart=@wlsunset@/bin/wlsunset -l 12.3 -L 128.8 -t 3500 -T 6000 -g 0.6
+
+[Unit]
+Description=Day/night gamma adjustments for Wayland compositors.
+PartOf=graphical-session.target
diff --git a/infra/libkookie/home-manager/tests/modules/services/wlsunset/wlsunset-service.nix b/infra/libkookie/home-manager/tests/modules/services/wlsunset/wlsunset-service.nix
new file mode 100644
index 000000000000..de32a8270be0
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/services/wlsunset/wlsunset-service.nix
@@ -0,0 +1,25 @@
+{ config, pkgs, ... }:
+
+{
+ config = {
+ services.wlsunset = {
+ enable = true;
+ package = pkgs.writeScriptBin "dummy-wlsunset" "" // {
+ outPath = "@wlsunset@";
+ };
+ latitude = "12.3";
+ longitude = "128.8";
+ temperature.day = 6000;
+ temperature.night = 3500;
+ gamma = "0.6";
+ systemdTarget = "test.target";
+ };
+
+ nmt.script = ''
+ serviceFile=home-files/.config/systemd/user/wlsunset.service
+
+ assertFileExists $serviceFile
+ assertFileContent $serviceFile ${./wlsunset-service-expected.service}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/systemd/default.nix b/infra/libkookie/home-manager/tests/modules/systemd/default.nix
new file mode 100644
index 000000000000..c1779ac59fb3
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/systemd/default.nix
@@ -0,0 +1,5 @@
+{
+ systemd-services = ./services.nix;
+ systemd-session-variables = ./session-variables.nix;
+ systemd-timers = ./timers.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/systemd/services.nix b/infra/libkookie/home-manager/tests/modules/systemd/services.nix
new file mode 100644
index 000000000000..4f73c5568fb8
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/systemd/services.nix
@@ -0,0 +1,33 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ systemd.user.services."test-service@" = {
+ Unit = {
+ Description = "A basic test service";
+ };
+
+ Service = {
+ Environment = [ "VAR1=1" "VAR2=2" ];
+ ExecStart = ''/some/exec/start/command --with-arguments "%i"'';
+ };
+ };
+
+ nmt.script = ''
+ serviceFile=home-files/.config/systemd/user/test-service@.service
+ assertFileExists $serviceFile
+ assertFileContent $serviceFile \
+ ${builtins.toFile "services-expected.conf" ''
+ [Service]
+ Environment=VAR1=1
+ Environment=VAR2=2
+ ExecStart=/some/exec/start/command --with-arguments "%i"
+
+ [Unit]
+ Description=A basic test service
+ ''}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/systemd/session-variables-expected.conf b/infra/libkookie/home-manager/tests/modules/systemd/session-variables-expected.conf
new file mode 100644
index 000000000000..5b6e80bc32ba
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/systemd/session-variables-expected.conf
@@ -0,0 +1,2 @@
+V_int=1
+V_str=2
diff --git a/infra/libkookie/home-manager/tests/modules/systemd/session-variables.nix b/infra/libkookie/home-manager/tests/modules/systemd/session-variables.nix
new file mode 100644
index 000000000000..b725827ce69c
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/systemd/session-variables.nix
@@ -0,0 +1,18 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ systemd.user.sessionVariables = {
+ V_int = 1;
+ V_str = "2";
+ };
+
+ nmt.script = ''
+ envFile=home-files/.config/environment.d/10-home-manager.conf
+ assertFileExists $envFile
+ assertFileContent $envFile ${./session-variables-expected.conf}
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/systemd/timers-expected.conf b/infra/libkookie/home-manager/tests/modules/systemd/timers-expected.conf
new file mode 100644
index 000000000000..b19f044cc0b0
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/systemd/timers-expected.conf
@@ -0,0 +1,8 @@
+[Install]
+WantedBy=timers.target
+
+[Timer]
+OnUnitActiveSec=1h 30m
+
+[Unit]
+Description=A basic test timer
diff --git a/infra/libkookie/home-manager/tests/modules/systemd/timers.nix b/infra/libkookie/home-manager/tests/modules/systemd/timers.nix
new file mode 100644
index 000000000000..1c0e27222993
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/systemd/timers.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ systemd.user.timers.test-timer = {
+ Unit = { Description = "A basic test timer"; };
+
+ Timer = { OnUnitActiveSec = "1h 30m"; };
+
+ Install = { WantedBy = [ "timers.target" ]; };
+ };
+
+ nmt.script = ''
+ unitDir=home-files/.config/systemd/user
+ timerFile=$unitDir/test-timer.timer
+
+ assertFileExists $timerFile
+ assertFileContent $timerFile ${./timers-expected.conf}
+
+ assertFileExists $unitDir/timers.target.wants/test-timer.timer
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/targets-darwin/darwin.nix b/infra/libkookie/home-manager/tests/modules/targets-darwin/darwin.nix
new file mode 100644
index 000000000000..511ae87fd98c
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/targets-darwin/darwin.nix
@@ -0,0 +1,20 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ darwinTestApp = pkgs.runCommandLocal "target-darwin-example-app" { } ''
+ mkdir -p $out/Applications
+ touch $out/Applications/example-app
+ '';
+
+in {
+ config = {
+ home.packages = [ darwinTestApp ];
+
+ nmt.script = ''
+ assertFileExists 'home-files/Applications/Home Manager Apps/example-app'
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/targets-darwin/default.nix b/infra/libkookie/home-manager/tests/modules/targets-darwin/default.nix
new file mode 100644
index 000000000000..10e4111f779b
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/targets-darwin/default.nix
@@ -0,0 +1,5 @@
+{
+ # Disabled for now due to conflicting behavior with nix-darwin. See
+ # https://github.com/nix-community/home-manager/issues/1341#issuecomment-687286866
+ #targets-darwin = ./darwin.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/targets-linux/default.nix b/infra/libkookie/home-manager/tests/modules/targets-linux/default.nix
new file mode 100644
index 000000000000..e13617ccb745
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/targets-linux/default.nix
@@ -0,0 +1 @@
+{ targets-generic-linux = ./generic-linux.nix; }
diff --git a/infra/libkookie/home-manager/tests/modules/targets-linux/generic-linux.nix b/infra/libkookie/home-manager/tests/modules/targets-linux/generic-linux.nix
new file mode 100644
index 000000000000..c7b60b3268e0
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/targets-linux/generic-linux.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ config = {
+ targets.genericLinux = {
+ enable = true;
+ extraXdgDataDirs = [ "/foo" ];
+ };
+
+ nmt.script = ''
+ assertFileExists home-path/etc/profile.d/hm-session-vars.sh
+ assertFileContains \
+ home-path/etc/profile.d/hm-session-vars.sh \
+ 'export XDG_DATA_DIRS="''${NIX_STATE_DIR:-/nix/var/nix}/profiles/default/share:/home/hm-user/.nix-profile/share:/foo''${XDG_DATA_DIRS:+:}$XDG_DATA_DIRS"'
+ assertFileContains \
+ home-path/etc/profile.d/hm-session-vars.sh \
+ '. "${pkgs.nix}/etc/profile.d/nix.sh"'
+ assertFileContains \
+ home-path/etc/profile.d/hm-session-vars.sh \
+ 'export LOCALE_ARCHIVE_2_27="${pkgs.glibcLocales}/lib/locale/locale-archive"'
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/xresources/default.nix b/infra/libkookie/home-manager/tests/modules/xresources/default.nix
new file mode 100644
index 000000000000..70b3e6b4a9c2
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/xresources/default.nix
@@ -0,0 +1,4 @@
+{
+ xresources = ./xresources.nix;
+ xresources-empty-properties = ./empty.nix;
+}
diff --git a/infra/libkookie/home-manager/tests/modules/xresources/empty.nix b/infra/libkookie/home-manager/tests/modules/xresources/empty.nix
new file mode 100644
index 000000000000..9dd80176ff3d
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/xresources/empty.nix
@@ -0,0 +1,13 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+ config = {
+ xresources.properties = { };
+
+ nmt.script = ''
+ assertPathNotExists home-files/.Xresources
+ '';
+ };
+}
diff --git a/infra/libkookie/home-manager/tests/modules/xresources/xresources-expected.conf b/infra/libkookie/home-manager/tests/modules/xresources/xresources-expected.conf
new file mode 100644
index 000000000000..20b47e5080bb
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/xresources/xresources-expected.conf
@@ -0,0 +1,5 @@
+Test*boolean1: true
+Test*boolean2: false
+Test*int: 10
+Test*list: list-str, true, false, 10
+Test*string: test-string
diff --git a/infra/libkookie/home-manager/tests/modules/xresources/xresources.nix b/infra/libkookie/home-manager/tests/modules/xresources/xresources.nix
new file mode 100644
index 000000000000..f73e326f31e4
--- /dev/null
+++ b/infra/libkookie/home-manager/tests/modules/xresources/xresources.nix
@@ -0,0 +1,22 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+ config = {
+ xresources = {
+ properties = {
+ "Test*string" = "test-string";
+ "Test*boolean1" = true;
+ "Test*boolean2" = false;
+ "Test*int" = 10;
+ "Test*list" = [ "list-str" true false 10 ];
+ };
+ };
+
+ nmt.script = ''
+ assertFileExists home-files/.Xresources
+ assertFileContent home-files/.Xresources ${./xresources-expected.conf}
+ '';
+ };
+}