aboutsummaryrefslogtreecommitdiff
path: root/home-manager/modules/programs
diff options
context:
space:
mode:
Diffstat (limited to 'home-manager/modules/programs')
-rw-r--r--home-manager/modules/programs/afew.nix52
-rw-r--r--home-manager/modules/programs/alacritty.nix52
-rw-r--r--home-manager/modules/programs/alot-accounts.nix58
-rw-r--r--home-manager/modules/programs/alot.nix173
-rw-r--r--home-manager/modules/programs/astroid-accounts.nix32
-rw-r--r--home-manager/modules/programs/astroid-config-template.json113
-rw-r--r--home-manager/modules/programs/astroid.nix123
-rw-r--r--home-manager/modules/programs/autorandr.nix355
-rw-r--r--home-manager/modules/programs/bash.nix219
-rw-r--r--home-manager/modules/programs/bat.nix37
-rw-r--r--home-manager/modules/programs/beets.nix58
-rw-r--r--home-manager/modules/programs/broot.nix256
-rw-r--r--home-manager/modules/programs/browserpass.nix76
-rw-r--r--home-manager/modules/programs/chromium.nix92
-rw-r--r--home-manager/modules/programs/command-not-found/command-not-found.nix59
-rw-r--r--home-manager/modules/programs/command-not-found/command-not-found.pl44
-rw-r--r--home-manager/modules/programs/direnv.nix99
-rw-r--r--home-manager/modules/programs/eclipse.nix50
-rw-r--r--home-manager/modules/programs/emacs.nix77
-rw-r--r--home-manager/modules/programs/feh.nix70
-rw-r--r--home-manager/modules/programs/firefox.nix305
-rw-r--r--home-manager/modules/programs/fish.nix191
-rw-r--r--home-manager/modules/programs/fzf.nix134
-rw-r--r--home-manager/modules/programs/getmail-accounts.nix49
-rw-r--r--home-manager/modules/programs/getmail.nix57
-rw-r--r--home-manager/modules/programs/git.nix303
-rw-r--r--home-manager/modules/programs/gnome-terminal.nix216
-rw-r--r--home-manager/modules/programs/go.nix89
-rw-r--r--home-manager/modules/programs/gpg.nix61
-rw-r--r--home-manager/modules/programs/home-manager.nix37
-rw-r--r--home-manager/modules/programs/htop.nix370
-rw-r--r--home-manager/modules/programs/info.nix75
-rw-r--r--home-manager/modules/programs/irssi.nix211
-rw-r--r--home-manager/modules/programs/jq.nix76
-rw-r--r--home-manager/modules/programs/kakoune.nix615
-rw-r--r--home-manager/modules/programs/keychain.nix115
-rw-r--r--home-manager/modules/programs/lesspipe.nix19
-rw-r--r--home-manager/modules/programs/lsd.nix41
-rw-r--r--home-manager/modules/programs/man.nix22
-rw-r--r--home-manager/modules/programs/matplotlib.nix59
-rw-r--r--home-manager/modules/programs/mbsync-accounts.nix105
-rw-r--r--home-manager/modules/programs/mbsync.nix164
-rw-r--r--home-manager/modules/programs/mercurial.nix100
-rw-r--r--home-manager/modules/programs/mpv.nix143
-rw-r--r--home-manager/modules/programs/msmtp-accounts.nix48
-rw-r--r--home-manager/modules/programs/msmtp.nix71
-rw-r--r--home-manager/modules/programs/neomutt-accounts.nix34
-rw-r--r--home-manager/modules/programs/neomutt.nix302
-rw-r--r--home-manager/modules/programs/neovim.nix207
-rw-r--r--home-manager/modules/programs/newsboat.nix119
-rw-r--r--home-manager/modules/programs/noti.nix50
-rw-r--r--home-manager/modules/programs/notmuch-accounts.nix5
-rw-r--r--home-manager/modules/programs/notmuch.nix191
-rw-r--r--home-manager/modules/programs/obs-studio.nix47
-rw-r--r--home-manager/modules/programs/offlineimap-accounts.nix51
-rw-r--r--home-manager/modules/programs/offlineimap.nix173
-rw-r--r--home-manager/modules/programs/opam.nix50
-rw-r--r--home-manager/modules/programs/password-store.nix62
-rw-r--r--home-manager/modules/programs/pazi.nix55
-rw-r--r--home-manager/modules/programs/pidgin.nix34
-rw-r--r--home-manager/modules/programs/readline.nix77
-rw-r--r--home-manager/modules/programs/rofi.nix327
-rw-r--r--home-manager/modules/programs/rtorrent.nix34
-rw-r--r--home-manager/modules/programs/skim.nix124
-rw-r--r--home-manager/modules/programs/ssh.nix461
-rw-r--r--home-manager/modules/programs/starship.nix94
-rw-r--r--home-manager/modules/programs/taskwarrior.nix112
-rw-r--r--home-manager/modules/programs/termite.nix387
-rw-r--r--home-manager/modules/programs/texlive.nix46
-rw-r--r--home-manager/modules/programs/tmux.nix320
-rw-r--r--home-manager/modules/programs/urxvt.nix156
-rw-r--r--home-manager/modules/programs/vim.nix171
-rw-r--r--home-manager/modules/programs/vscode.nix101
-rw-r--r--home-manager/modules/programs/vscode/haskell.nix62
-rw-r--r--home-manager/modules/programs/z-lua.nix90
-rw-r--r--home-manager/modules/programs/zathura.nix58
-rw-r--r--home-manager/modules/programs/zsh.nix456
77 files changed, 10227 insertions, 0 deletions
diff --git a/home-manager/modules/programs/afew.nix b/home-manager/modules/programs/afew.nix
new file mode 100644
index 00000000000..99bae88c0ee
--- /dev/null
+++ b/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/home-manager/modules/programs/alacritty.nix b/home-manager/modules/programs/alacritty.nix
new file mode 100644
index 00000000000..69b9ea9673d
--- /dev/null
+++ b/home-manager/modules/programs/alacritty.nix
@@ -0,0 +1,52 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.alacritty;
+
+in {
+ options = {
+ programs.alacritty = {
+ enable = mkEnableOption "Alacritty";
+
+ settings = mkOption {
+ type = types.attrs;
+ 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 = [ pkgs.alacritty ];
+
+ xdg.configFile."alacritty/alacritty.yml" = mkIf (cfg.settings != { }) {
+ text =
+ replaceStrings [ "\\\\" ] [ "\\" ] (builtins.toJSON cfg.settings);
+ };
+ })
+ ];
+}
diff --git a/home-manager/modules/programs/alot-accounts.nix b/home-manager/modules/programs/alot-accounts.nix
new file mode 100644
index 00000000000..89ae28f9c8e
--- /dev/null
+++ b/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/home-manager/modules/programs/alot.nix b/home-manager/modules/programs/alot.nix
new file mode 100644
index 00000000000..2b28f34caa3
--- /dev/null
+++ b/home-manager/modules/programs/alot.nix
@@ -0,0 +1,173 @@
+# 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";
+
+ 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
+
+ ${cfg.extraConfig}
+
+ [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.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = ''
+ auto_remove_unread = True
+ ask_subject = False
+ handle_mouse = True
+ initial_command = "search tag:inbox AND NOT tag:killed"
+ input_timeout = 0.3
+ prefer_plaintext = True
+ thread_indent_replies = 4
+ '';
+ description = ''
+ Extra lines added to alot configuration file.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.alot ];
+
+ xdg.configFile."alot/config".text = configFile;
+
+ xdg.configFile."alot/hooks.py".text =
+ ''
+ # Generated by Home Manager.
+ ''
+ + cfg.hooks;
+ };
+}
diff --git a/home-manager/modules/programs/astroid-accounts.nix b/home-manager/modules/programs/astroid-accounts.nix
new file mode 100644
index 00000000000..17544ff7899
--- /dev/null
+++ b/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.attrs;
+ 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/home-manager/modules/programs/astroid-config-template.json b/home-manager/modules/programs/astroid-config-template.json
new file mode 100644
index 00000000000..87e3f764f9c
--- /dev/null
+++ b/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/home-manager/modules/programs/astroid.nix b/home-manager/modules/programs/astroid.nix
new file mode 100644
index 00000000000..8b3762fac0b
--- /dev/null
+++ b/home-manager/modules/programs/astroid.nix
@@ -0,0 +1,123 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+with builtins;
+
+let
+
+ cfg = config.programs.astroid;
+
+ 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
+ configFile = mailAccounts:
+ 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 builtins.toJSON 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 = types.attrs;
+ default = { };
+ example = { poll.interval = 0; };
+ description = ''
+ JSON config that will override the default Astroid configuration.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.astroid ];
+
+ xdg.configFile."astroid/config".source = pkgs.runCommand "out.json" {
+ json = configFile astroidAccounts;
+ preferLocalBuild = true;
+ allowSubstitutes = false;
+ } ''
+ echo -n "$json" | ${pkgs.jq}/bin/jq . > $out
+ '';
+
+ xdg.configFile."astroid/poll.sh" = {
+ executable = true;
+ text = ''
+ # Generated by Home Manager
+
+ ${cfg.pollScript}
+ '';
+ };
+ };
+}
diff --git a/home-manager/modules/programs/autorandr.nix b/home-manager/modules/programs/autorandr.nix
new file mode 100644
index 00000000000..02fc77c1e58
--- /dev/null
+++ b/home-manager/modules/programs/autorandr.nix
@@ -0,0 +1,355 @@
+{ 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;
+ };
+
+ 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 ''
+ output ${name}
+ ${optionalString (config.position != "") "pos ${config.position}"}
+ ${optionalString config.primary "primary"}
+ ${optionalString (config.dpi != null) "dpi ${toString config.dpi}"}
+ ${optionalString (config.gamma != "") "gamma ${config.gamma}"}
+ ${optionalString (config.mode != "") "mode ${config.mode}"}
+ ${optionalString (config.rate != "") "rate ${config.rate}"}
+ ${optionalString (config.rotate != null) "rotate ${config.rotate}"}
+ ${optionalString (config.scale != null)
+ ((if config.scale.method == "factor" then "scale" else "scale-from")
+ + " ${toString config.scale.x}x${toString config.scale.y}")}
+ ${optionalString (config.transform != null) ("transform "
+ + concatMapStringsSep "," toString (flatten config.transform))}
+ '' 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;
+ 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/home-manager/modules/programs/bash.nix b/home-manager/modules/programs/bash.nix
new file mode 100644
index 00000000000..82a9fbe8f8b
--- /dev/null
+++ b/home-manager/modules/programs/bash.nix
@@ -0,0 +1,219 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.bash;
+
+in
+
+{
+ meta.maintainers = [ maintainers.rycee ];
+
+ 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 = { 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.
+ '';
+ };
+
+ enableAutojump = mkOption {
+ default = false;
+ type = types.bool;
+ description = "Enable the autojump navigation tool.";
+ };
+
+ 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}
+
+ ${optionalString cfg.enableAutojump
+ ". ${pkgs.autojump}/share/autojump/autojump.bash"}
+ 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}
+ '';
+ };
+
+ home.packages =
+ optional (cfg.enableAutojump) pkgs.autojump;
+ }
+ );
+}
diff --git a/home-manager/modules/programs/bat.nix b/home-manager/modules/programs/bat.nix
new file mode 100644
index 00000000000..aa0df9abd45
--- /dev/null
+++ b/home-manager/modules/programs/bat.nix
@@ -0,0 +1,37 @@
+{ 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.
+ '';
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.bat ];
+
+ xdg.configFile."bat/config" = mkIf (cfg.config != { }) {
+ text = concatStringsSep "\n"
+ (mapAttrsToList (n: v: ''--${n}="${v}"'') cfg.config);
+ };
+ };
+}
diff --git a/home-manager/modules/programs/beets.nix b/home-manager/modules/programs/beets.nix
new file mode 100644
index 00000000000..1a45bbea1c7
--- /dev/null
+++ b/home-manager/modules/programs/beets.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.beets;
+
+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 = types.attrs;
+ default = { };
+ description = ''
+ Configuration written to
+ <filename>~/.config/beets/config.yaml</filename>
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ cfg.package ];
+
+ xdg.configFile."beets/config.yaml".text =
+ builtins.toJSON config.programs.beets.settings;
+ };
+}
diff --git a/home-manager/modules/programs/broot.nix b/home-manager/modules/programs/broot.nix
new file mode 100644
index 00000000000..eac31b56801
--- /dev/null
+++ b/home-manager/modules/programs/broot.nix
@@ -0,0 +1,256 @@
+{ 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 =
+ mapAttrsToList (name: value: value // { invocation = name; }) 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; attrsOf (attrsOf (either bool str));
+ default = {
+ "p" = { execution = ":parent"; };
+ "edit" = {
+ shortcut = "e";
+ execution = "$EDITOR {file}";
+ };
+ "create {subpath}" = { execution = "$EDITOR {directory}/{subpath}"; };
+ "view" = { execution = "less {file}"; };
+ };
+ example = literalExample ''
+ {
+ "p" = { execution = ":parent"; };
+ "edit" = { shortcut = "e"; execution = "$EDITOR {file}" ; };
+ "create {subpath}" = { execution = "$EDITOR {directory}/{subpath}"; };
+ "view" = { execution = "less {file}"; };
+ "blop {name}\\.{type}" = {
+ execution = "/bin/mkdir {parent}/{type} && /usr/bin/nvim {parent}/{type}/{name}.{type}";
+ from_shell = true;
+ };
+ }
+ '';
+ description = ''
+ Define new verbs. The attribute name indicates how the verb is
+ called by the user, with placeholders for arguments.
+ </para><para>
+ The possible attributes are:
+ </para>
+
+ <para>
+ <variablelist>
+ <varlistentry>
+ <term><literal>execution</literal> (mandatory)</term>
+ <listitem><para>how the verb is executed</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".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/home-manager/modules/programs/browserpass.nix b/home-manager/modules/programs/browserpass.nix
new file mode 100644
index 00000000000..10a2883c871
--- /dev/null
+++ b/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/home-manager/modules/programs/chromium.nix b/home-manager/modules/programs/chromium.nix
new file mode 100644
index 00000000000..4e35c07b90c
--- /dev/null
+++ b/home-manager/modules/programs/chromium.nix
@@ -0,0 +1,92 @@
+{ 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";
+ };
+
+ 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;
+ };
+
+ config = mkMerge [
+ (browserConfig config.programs.chromium)
+ (browserConfig config.programs.google-chrome)
+ (browserConfig config.programs.google-chrome-beta)
+ (browserConfig config.programs.google-chrome-dev)
+ ];
+}
diff --git a/home-manager/modules/programs/command-not-found/command-not-found.nix b/home-manager/modules/programs/command-not-found/command-not-found.nix
new file mode 100644
index 00000000000..b79fde0f619
--- /dev/null
+++ b/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/home-manager/modules/programs/command-not-found/command-not-found.pl b/home-manager/modules/programs/command-not-found/command-not-found.pl
new file mode 100644
index 00000000000..997dfec649b
--- /dev/null
+++ b/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/home-manager/modules/programs/direnv.nix b/home-manager/modules/programs/direnv.nix
new file mode 100644
index 00000000000..beb40a96261
--- /dev/null
+++ b/home-manager/modules/programs/direnv.nix
@@ -0,0 +1,99 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.direnv;
+ 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.rycee ];
+
+ options.programs.direnv = {
+ enable = mkEnableOption "direnv, the environment switcher";
+
+ config = mkOption {
+ type = types.attrs;
+ 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.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.direnv ];
+
+ xdg.configFile."direnv/config.toml" =
+ mkIf (cfg.config != { }) { source = configFile cfg.config; };
+
+ xdg.configFile."direnv/direnvrc" =
+ mkIf (cfg.stdlib != "") { text = cfg.stdlib; };
+
+ 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/home-manager/modules/programs/eclipse.nix b/home-manager/modules/programs/eclipse.nix
new file mode 100644
index 00000000000..8ce605b106a
--- /dev/null
+++ b/home-manager/modules/programs/eclipse.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.eclipse;
+
+in {
+ meta.maintainers = [ maintainers.rycee ];
+
+ options = {
+ programs.eclipse = {
+ enable = mkEnableOption "Eclipse";
+
+ 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 = pkgs.eclipses.eclipse-platform;
+ jvmArgs = cfg.jvmArgs ++ optional cfg.enableLombok
+ "-javaagent:${pkgs.lombok}/share/java/lombok.jar";
+ plugins = cfg.plugins;
+ })
+ ];
+ };
+}
diff --git a/home-manager/modules/programs/emacs.nix b/home-manager/modules/programs/emacs.nix
new file mode 100644
index 00000000000..987a9f2431e
--- /dev/null
+++ b/home-manager/modules/programs/emacs.nix
@@ -0,0 +1,77 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.emacs;
+
+ # Copied from all-packages.nix, with modifications to support
+ # overrides.
+ emacsPackages =
+ let
+ epkgs = pkgs.emacsPackagesGen 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/home-manager/modules/programs/feh.nix b/home-manager/modules/programs/feh.nix
new file mode 100644
index 00000000000..b1b33697e95
--- /dev/null
+++ b/home-manager/modules/programs/feh.nix
@@ -0,0 +1,70 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.feh;
+
+ disableBinding = func: key: func;
+ enableBinding = func: key: "${func} ${toString key}";
+
+in {
+ options.programs.feh = {
+ enable = mkEnableOption "feh - a fast and light image viewer";
+
+ buttons = mkOption {
+ default = { };
+ type = with types; attrsOf (nullOr (either str int));
+ example = {
+ zoom_in = 4;
+ zoom_out = "C-4";
+ };
+ description = ''
+ Override feh's default mouse button mapping. If you want to disable an
+ action, set its value to null.
+ See <link xlink:href="https://man.finalrewind.org/1/feh/#x425554544f4e53"/> for
+ default bindings and available commands.
+ '';
+ };
+
+ keybindings = mkOption {
+ default = { };
+ type = types.attrsOf (types.nullOr types.str);
+ example = {
+ zoom_in = "plus";
+ zoom_out = "minus";
+ };
+ description = ''
+ Override feh's default keybindings. If you want to disable a keybinding
+ set its value to null.
+ 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".text = ''
+ ${concatStringsSep "\n" (mapAttrsToList disableBinding
+ (filterAttrs (n: v: v == null) cfg.buttons))}
+ ${concatStringsSep "\n" (mapAttrsToList enableBinding
+ (filterAttrs (n: v: v != null) cfg.buttons))}
+ '';
+
+ xdg.configFile."feh/keys".text = ''
+ ${concatStringsSep "\n" (mapAttrsToList disableBinding
+ (filterAttrs (n: v: v == null) cfg.keybindings))}
+ ${concatStringsSep "\n" (mapAttrsToList enableBinding
+ (filterAttrs (n: v: v != null) cfg.keybindings))}
+ '';
+ };
+}
diff --git a/home-manager/modules/programs/firefox.nix b/home-manager/modules/programs/firefox.nix
new file mode 100644
index 00000000000..17c64752d66
--- /dev/null
+++ b/home-manager/modules/programs/firefox.nix
@@ -0,0 +1,305 @@
+{ 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;
+
+ extensionPath = "extensions/{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
+
+ 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 ];
+
+ 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. Note, it is
+ necessary to manually enable these extensions inside Firefox
+ after the first installation.
+ '';
+ };
+
+ 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 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;
+ }
+ }
+ '';
+ };
+
+ 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.";
+ };
+
+ enableGoogleTalk = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the unfree Google Talk plugin. This option
+ is <emphasis>deprecated</emphasis> and will only work if
+
+ <programlisting language="nix">
+ programs.firefox.package = pkgs.firefox-esr-52-unwrapped;
+ </programlisting>
+
+ and the <option>plugin.load_flash_only</option> Firefox
+ option has been disabled.
+ '';
+ };
+
+ enableIcedTea = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to enable the Java applet plugin. This option is
+ <emphasis>deprecated</emphasis> and will only work if
+
+ <programlisting language="nix">
+ programs.firefox.package = pkgs.firefox-esr-52-unwrapped;
+ </programlisting>
+
+ and the <option>plugin.load_flash_only</option> Firefox
+ option has been disabled.
+ '';
+ };
+ };
+ };
+
+ 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;
+ enableGoogleTalkPlugin = cfg.enableGoogleTalk;
+ icedtea = cfg.enableIcedTea;
+ };
+
+ # 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 != []) (
+ let
+ extensionsEnv = pkgs.buildEnv {
+ name = "hm-firefox-extensions";
+ paths = cfg.extensions;
+ };
+ in {
+ source = "${extensionsEnv}/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}/user.js" =
+ mkIf (profile.settings != {} || profile.extraConfig != "") {
+ text = mkUserJs profile.settings profile.extraConfig;
+ };
+ })
+ );
+ };
+}
diff --git a/home-manager/modules/programs/fish.nix b/home-manager/modules/programs/fish.nix
new file mode 100644
index 00000000000..87a17b85507
--- /dev/null
+++ b/home-manager/modules/programs/fish.nix
@@ -0,0 +1,191 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.fish;
+
+ abbrsStr = concatStringsSep "\n" (
+ mapAttrsToList (k: v: "abbr --add --global ${k} '${v}'") cfg.shellAbbrs
+ );
+
+ aliasesStr = concatStringsSep "\n" (
+ mapAttrsToList (k: v: "alias ${k}='${v}'") cfg.shellAliases
+ );
+
+in
+
+{
+ options = {
+ programs.fish = {
+ enable = mkEnableOption "fish friendly interactive shell";
+
+ package = mkOption {
+ default = pkgs.fish;
+ defaultText = literalExample "pkgs.fish";
+ description = ''
+ The fish package to install. May be used to change the version.
+ '';
+ type = types.package;
+ };
+
+ shellAliases = mkOption {
+ default = {};
+ description = ''
+ Set of aliases for fish shell. See
+ <option>environment.shellAliases</option> for an option
+ format description.
+ '';
+ type = types.attrs;
+ };
+
+ shellAbbrs = mkOption {
+ default = {};
+ description = ''
+ Set of abbreviations for fish shell.
+ '';
+ type = types.attrs;
+ };
+
+ shellInit = mkOption {
+ default = "";
+ description = ''
+ Shell script code called during fish shell initialisation.
+ '';
+ type = types.lines;
+ };
+
+ loginShellInit = mkOption {
+ default = "";
+ description = ''
+ Shell script code called during fish login shell initialisation.
+ '';
+ type = types.lines;
+ };
+
+ interactiveShellInit = mkOption {
+ default = "";
+ description = ''
+ Shell script code called during interactive fish shell initialisation.
+ '';
+ type = types.lines;
+ };
+
+ promptInit = mkOption {
+ default = "";
+ description = ''
+ Shell script code used to initialise fish prompt.
+ '';
+ type = types.lines;
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ 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" ]
+ // { inherit preferLocalBuild allowSubstitutes; }; # pass the defaults
+ 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.
+ # if we haven't sourced the general config, do it
+ if not set -q __fish_general_config_sourced
+ set fish_function_path ${pkgs.fish-foreign-env}/share/fish-foreign-env/functions $fish_function_path
+ 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
+
+ ${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
+ # Abbrs
+ ${abbrsStr}
+
+ # Aliases
+ ${aliasesStr}
+
+ ${cfg.promptInit}
+ ${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
+ '';
+ };
+}
diff --git a/home-manager/modules/programs/fzf.nix b/home-manager/modules/programs/fzf.nix
new file mode 100644
index 00000000000..36eb3a1cdba
--- /dev/null
+++ b/home-manager/modules/programs/fzf.nix
@@ -0,0 +1,134 @@
+{ 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.
+ '';
+ };
+ };
+
+ 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
+ '';
+ };
+}
diff --git a/home-manager/modules/programs/getmail-accounts.nix b/home-manager/modules/programs/getmail-accounts.nix
new file mode 100644
index 00000000000..24eb4fb588a
--- /dev/null
+++ b/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/home-manager/modules/programs/getmail.nix b/home-manager/modules/programs/getmail.nix
new file mode 100644
index 00000000000..2c3919dcf2f
--- /dev/null
+++ b/home-manager/modules/programs/getmail.nix
@@ -0,0 +1,57 @@
+{ 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 {
+ config = mkIf getmailEnabled {
+ home.file = foldl' (a: b: a // b) { }
+ (map (a: { "${renderConfigFilepath a}".text = renderAccountConfig a; })
+ accounts);
+ };
+}
diff --git a/home-manager/modules/programs/git.nix b/home-manager/modules/programs/git.nix
new file mode 100644
index 00000000000..a56aa10d50e
--- /dev/null
+++ b/home-manager/modules/programs/git.nix
@@ -0,0 +1,303 @@
+{ 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}"'';
+
+ # generation for multiple ini values
+ mkKeyValue = k: v:
+ let mkKeyValue = generators.mkKeyValueDefault { } "=" k;
+ in concatStringsSep "\n" (map mkKeyValue (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.attrs;
+ 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.
+ '';
+ };
+ };
+ };
+ };
+
+ 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 "tls" 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" ]);
+ };
+ })
+ ]);
+}
diff --git a/home-manager/modules/programs/gnome-terminal.nix b/home-manager/modules/programs/gnome-terminal.nix
new file mode 100644
index 00000000000..570a1fc7df0
--- /dev/null
+++ b/home-manager/modules/programs/gnome-terminal.nix
@@ -0,0 +1,216 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.gnome-terminal;
+
+ vteInitStr = ''
+ # gnome-terminal: Show current directory in the terminal window title.
+ . ${pkgs.gnome3.vte}/etc/profile.d/vte.sh
+ '';
+
+ 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.";
+ };
+
+ 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.
+ '';
+ };
+ };
+ });
+
+ buildProfileSet = pcfg:
+ {
+ visible-name = pcfg.visibleName;
+ scrollbar-policy = if pcfg.showScrollbar then "always" else "never";
+ scrollback-lines = pcfg.scrollbackLines;
+ cursor-shape = pcfg.cursorShape;
+ } // (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" ];
+ 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.initExtra = mkBefore vteInitStr;
+ programs.zsh.initExtra = vteInitStr;
+ };
+}
diff --git a/home-manager/modules/programs/go.nix b/home-manager/modules/programs/go.nix
new file mode 100644
index 00000000000..983769d26af
--- /dev/null
+++ b/home-manager/modules/programs/go.nix
@@ -0,0 +1,89 @@
+{ 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";
+ };
+ };
+ };
+
+ 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}";
+ })
+ ]);
+}
diff --git a/home-manager/modules/programs/gpg.nix b/home-manager/modules/programs/gpg.nix
new file mode 100644
index 00000000000..4588c59c882
--- /dev/null
+++ b/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/home-manager/modules/programs/home-manager.nix b/home-manager/modules/programs/home-manager.nix
new file mode 100644
index 00000000000..9039a59d7c5
--- /dev/null
+++ b/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/home-manager/modules/programs/htop.nix b/home-manager/modules/programs/htop.nix
new file mode 100644
index 00000000000..84966040534
--- /dev/null
+++ b/home-manager/modules/programs/htop.nix
@@ -0,0 +1,370 @@
+{ 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;
+ };
+
+ # 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;
+ LeftCPUs = 1;
+ RightCPUs = 1;
+ LeftCPUs2 = 1;
+ RightCPUs2 = 1;
+ Blank = 2;
+ CPU = 1;
+ "CPU(1)" = 1;
+ "CPU(2)" = 1;
+ "CPU(3)" = 1;
+ "CPU(4)" = 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.";
+ };
+
+ 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.";
+ };
+
+ 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;
+ };
+
+ };
+
+ 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}
+ update_process_names=${bool cfg.updateProcessNames}
+ account_guest_in_cpu_meter=${bool cfg.accountGuestInCpuMeter}
+ color_scheme=${toString cfg.colorScheme}
+ delay=${toString cfg.delay}
+ left_meters=${list leftMeters}
+ left_meter_modes=${list leftModes}
+ right_meters=${list rightMeters}
+ right_meter_modes=${list rightModes}
+ '';
+ };
+}
diff --git a/home-manager/modules/programs/info.nix b/home-manager/modules/programs/info.nix
new file mode 100644
index 00000000000..9e4a5d4aaff
--- /dev/null
+++ b/home-manager/modules/programs/info.nix
@@ -0,0 +1,75 @@
+# info.nix -- install texinfo, set INFOPATH, 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.
+
+# We set $INFOPATH to include `/run/current-system/sw/share/info` and
+# `~/.nix-profile/share/info` but it's not enough. 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 home-installed packages.
+
+# So this module contains an activation script that generates 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;
+
+ # Indexes info files found in this location
+ homeInfoPath = "${config.home.profileDirectory}/share/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 {
+ options = {
+ programs.info = {
+ enable = mkEnableOption "GNU Info";
+
+ homeInfoDirLocation = mkOption {
+ default = "\${XDG_CACHE_HOME:-$HOME/.cache}/info";
+ description = ''
+ Directory in which to store the info <filename>dir</filename>
+ file within your home.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.sessionVariables.INFOPATH =
+ "${cfg.homeInfoDirLocation}\${INFOPATH:+:}\${INFOPATH}";
+
+ home.activation.createHomeInfoDir =
+ hm.dag.entryAfter [ "installPackages" ] ''
+ oPATH=$PATH
+ export PATH="${lib.makeBinPath [ pkgs.gzip ]}''${PATH:+:}$PATH"
+ $DRY_RUN_CMD mkdir -p "${cfg.homeInfoDirLocation}"
+ $DRY_RUN_CMD rm -f "${cfg.homeInfoDirLocation}/dir"
+ if [[ -d "${homeInfoPath}" ]]; then
+ find -L "${homeInfoPath}" \( -name '*.info' -o -name '*.info.gz' \) \
+ -exec $DRY_RUN_CMD ${infoPkg}/bin/install-info '{}' \
+ "${cfg.homeInfoDirLocation}/dir" \;
+ fi
+ export PATH="$oPATH"
+ unset oPATH
+ '';
+
+ home.packages = [ infoPkg ];
+
+ home.extraOutputsToInstall = [ "info" ];
+ };
+}
diff --git a/home-manager/modules/programs/irssi.nix b/home-manager/modules/programs/irssi.nix
new file mode 100644
index 00000000000..fc8fa8e6132
--- /dev/null
+++ b/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/home-manager/modules/programs/jq.nix b/home-manager/modules/programs/jq.nix
new file mode 100644
index 00000000000..6c89df0df93
--- /dev/null
+++ b/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/home-manager/modules/programs/kakoune.nix b/home-manager/modules/programs/kakoune.nix
new file mode 100644
index 00000000000..faf2542dc70
--- /dev/null
+++ b/home-manager/modules/programs/kakoune.nix
@@ -0,0 +1,615 @@
+{ 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"
+ ];
+ 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.enum [
+ "insert"
+ "normal"
+ "prompt"
+ "menu"
+ "user"
+ "goto"
+ "view"
+ "object"
+ ];
+ 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"/>.
+ '';
+ };
+ };
+ };
+
+ 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}"}"
+ ];
+
+ 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"
+ }"
+ ];
+
+ 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 (scrollOff != null)
+ "set-option global scrolloff ${toString scrollOff.lines},${
+ toString scrollOff.columns
+ }"
+
+ ++ [ "# UI options" ]
+ ++ optional (ui != null) "set-option global ui_options ${uiOptions}"
+
+ ++ [ "# 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>.
+ '';
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ home.packages = [ pkgs.kakoune ];
+ xdg.configFile."kak/kakrc".source = configFile;
+ };
+}
diff --git a/home-manager/modules/programs/keychain.nix b/home-manager/modules/programs/keychain.nix
new file mode 100644
index 00000000000..6e26bd232ce
--- /dev/null
+++ b/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/home-manager/modules/programs/lesspipe.nix b/home-manager/modules/programs/lesspipe.nix
new file mode 100644
index 00000000000..a7a51ffe2a2
--- /dev/null
+++ b/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/home-manager/modules/programs/lsd.nix b/home-manager/modules/programs/lsd.nix
new file mode 100644
index 00000000000..ab1880ff828
--- /dev/null
+++ b/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/home-manager/modules/programs/man.nix b/home-manager/modules/programs/man.nix
new file mode 100644
index 00000000000..0ed376780d4
--- /dev/null
+++ b/home-manager/modules/programs/man.nix
@@ -0,0 +1,22 @@
+{ 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>.
+ '';
+ };
+ };
+
+ config = mkIf config.programs.man.enable {
+ home.packages = [ pkgs.man ];
+ home.extraOutputsToInstall = [ "man" ];
+ };
+}
diff --git a/home-manager/modules/programs/matplotlib.nix b/home-manager/modules/programs/matplotlib.nix
new file mode 100644
index 00000000000..da80c116770
--- /dev/null
+++ b/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.attrs;
+ 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/home-manager/modules/programs/mbsync-accounts.nix b/home-manager/modules/programs/mbsync-accounts.nix
new file mode 100644
index 00000000000..4de1965fe3f
--- /dev/null
+++ b/home-manager/modules/programs/mbsync-accounts.nix
@@ -0,0 +1,105 @@
+{ lib, ... }:
+
+with lib;
+
+let
+
+ extraConfigType = with lib.types; attrsOf (either (either str int) bool);
+
+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.
+ '';
+ };
+
+ 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/home-manager/modules/programs/mbsync.nix b/home-manager/modules/programs/mbsync.nix
new file mode 100644
index 00000000000..6ade10986fe
--- /dev/null
+++ b/home-manager/modules/programs/mbsync.nix
@@ -0,0 +1,164 @@
+{ 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" + 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";
+
+ genGroupConfig = name: channels:
+ let
+ genGroupChannel = n: boxes: "Channel ${n}:${concatStringsSep "," boxes}";
+ in concatStringsSep "\n"
+ ([ "Group ${name}" ] ++ mapAttrsToList genGroupChannel channels);
+
+in {
+ 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.
+ '';
+ };
+ };
+ };
+
+ 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;
+ groupsConfig = mapAttrsToList genGroupConfig cfg.groups;
+ in concatStringsSep "\n" ([''
+ # Generated by Home Manager.
+ ''] ++ optional (cfg.extraConfig != "") cfg.extraConfig ++ accountsConfig
+ ++ groupsConfig) + "\n";
+
+ 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/home-manager/modules/programs/mercurial.nix b/home-manager/modules/programs/mercurial.nix
new file mode 100644
index 00000000000..8e9a3befbaf
--- /dev/null
+++ b/home-manager/modules/programs/mercurial.nix
@@ -0,0 +1,100 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.mercurial;
+
+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.attrs;
+ default = { };
+ description = "Mercurial aliases to define.";
+ };
+
+ extraConfig = mkOption {
+ type = types.either types.attrs types.lines;
+ default = { };
+ description = "Additional configuration to add.";
+ };
+
+ iniContent = mkOption {
+ type = types.attrsOf types.attrs;
+ 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".text = generators.toINI { } 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/home-manager/modules/programs/mpv.nix b/home-manager/modules/programs/mpv.nix
new file mode 100644
index 00000000000..3a4e5092f9a
--- /dev/null
+++ b/home-manager/modules/programs/mpv.nix
@@ -0,0 +1,143 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ inherit (builtins) typeOf stringLength;
+
+ cfg = config.programs.mpv;
+
+ mpvOption = with types; either str (either int (either bool float));
+ mpvOptions = with types; attrsOf mpvOption;
+ 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};
+
+ renderOptions = options:
+ concatStringsSep "\n" (mapAttrsToList (name: value:
+ let
+ rendered = renderOption value;
+ length = toString (stringLength rendered);
+ in "${name}=%${length}%${rendered}") options);
+
+ renderProfiles = profiles:
+ concatStringsSep "\n" (mapAttrsToList (name: value: ''
+ [${name}]
+ ${renderOptions value}
+ '') profiles);
+
+ renderBindings = bindings:
+ concatStringsSep "\n"
+ (mapAttrsToList (name: value: "${name} ${value}") bindings);
+
+in {
+ options = {
+ programs.mpv = {
+ enable = mkEnableOption "mpv";
+
+ 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 = [
+ (if cfg.scripts == [ ] then
+ pkgs.mpv
+ else
+ pkgs.mpv-with-scripts.override { scripts = cfg.scripts; })
+ ];
+ }
+ (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/home-manager/modules/programs/msmtp-accounts.nix b/home-manager/modules/programs/msmtp-accounts.nix
new file mode 100644
index 00000000000..894cef51742
--- /dev/null
+++ b/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/home-manager/modules/programs/msmtp.nix b/home-manager/modules/programs/msmtp.nix
new file mode 100644
index 00000000000..f34fd72f8b1
--- /dev/null
+++ b/home-manager/modules/programs/msmtp.nix
@@ -0,0 +1,71 @@
+{ 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) {
+ # msmtp requires the password to finish with a newline.
+ passwordeval =
+ ''${pkgs.bash}/bin/bash -c "${toString passwordCommand}; echo"'';
+ } // 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.
+ '';
+ };
+ };
+ };
+
+ 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/home-manager/modules/programs/neomutt-accounts.nix b/home-manager/modules/programs/neomutt-accounts.nix
new file mode 100644
index 00000000000..033db38eb0a
--- /dev/null
+++ b/home-manager/modules/programs/neomutt-accounts.nix
@@ -0,0 +1,34 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+ options.neomutt = {
+ enable = mkEnableOption "NeoMutt";
+
+ sendMailCommand = mkOption {
+ type = types.nullOr types.str;
+ default = 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.
+ '';
+ };
+ };
+
+ config = mkIf config.neomutt.enable {
+ neomutt.sendMailCommand = mkOptionDefault (if config.msmtp.enable then
+ "msmtpq --read-envelope-from --read-recipients"
+ else
+ null);
+ };
+}
diff --git a/home-manager/modules/programs/neomutt.nix b/home-manager/modules/programs/neomutt.nix
new file mode 100644
index 00000000000..85af0353b6c
--- /dev/null
+++ b/home-manager/modules/programs/neomutt.nix
@@ -0,0 +1,302 @@
+{ 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.
+ '';
+ };
+ };
+ };
+
+ 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";
+ smtpBaseUrl = "${smtpProto}://${escape userName}@${smtp.host}";
+ in {
+ smtp_url = "'${smtpBaseUrl}'";
+ smtp_pass = "'`${passCmd}`'";
+ };
+
+ genMaildirAccountConfig = account:
+ with account;
+ let
+ folderHook = mapAttrsToList setOption (genCommonFolderHooks account // {
+ folder = "'${account.maildir.absPath}'";
+ }) ++ optional (neomutt.extraConfig != "") neomutt.extraConfig;
+ 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 {
+ type = types.enum [
+ "date"
+ "date-received"
+ "from"
+ "mailbox-order"
+ "score"
+ "size"
+ "spam"
+ "subject"
+ "threads"
+ "to"
+ ];
+ 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.";
+ };
+ };
+ };
+
+ 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/home-manager/modules/programs/neovim.nix b/home-manager/modules/programs/neovim.nix
new file mode 100644
index 00000000000..4101dc0f4e7
--- /dev/null
+++ b/home-manager/modules/programs/neovim.nix
@@ -0,0 +1,207 @@
+{ 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;
+ };
+
+ moduleConfigure =
+ optionalAttrs (cfg.extraConfig != "") {
+ customRC = cfg.extraConfig;
+ }
+ // optionalAttrs (cfg.plugins != []) {
+ packages.home-manager.start = cfg.plugins;
+ };
+
+in
+
+{
+ options = {
+ programs.neovim = {
+ enable = mkEnableOption "Neovim";
+
+ viAlias = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Symlink `vi` to `nvim` binary.
+ '';
+ };
+
+ vimAlias = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Symlink `vim` to `nvim` binary.
+ '';
+ };
+
+ 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.attrs;
+ 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>.
+ '';
+ };
+
+ plugins = mkOption {
+ type = with types; listOf package;
+ default = [ ];
+ example = literalExample "[ pkgs.vimPlugins.yankring ]";
+ description = ''
+ List of vim plugins to install.
+
+ </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;
+
+ configure = cfg.configure // moduleConfigure;
+ };
+ };
+}
diff --git a/home-manager/modules/programs/newsboat.nix b/home-manager/modules/programs/newsboat.nix
new file mode 100644
index 00000000000..6b59ed713d8
--- /dev/null
+++ b/home-manager/modules/programs/newsboat.nix
@@ -0,0 +1,119 @@
+{ 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" (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/home-manager/modules/programs/noti.nix b/home-manager/modules/programs/noti.nix
new file mode 100644
index 00000000000..348555eef51
--- /dev/null
+++ b/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/home-manager/modules/programs/notmuch-accounts.nix b/home-manager/modules/programs/notmuch-accounts.nix
new file mode 100644
index 00000000000..fd4a811d73d
--- /dev/null
+++ b/home-manager/modules/programs/notmuch-accounts.nix
@@ -0,0 +1,5 @@
+{ lib, ... }:
+
+{
+ options.notmuch = { enable = lib.mkEnableOption "notmuch indexing"; };
+}
diff --git a/home-manager/modules/programs/notmuch.nix b/home-manager/modules/programs/notmuch.nix
new file mode 100644
index 00000000000..7e7a140b20c
--- /dev/null
+++ b/home-manager/modules/programs/notmuch.nix
@@ -0,0 +1,191 @@
+{ 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.
+ '';
+ };
+ };
+ };
+ };
+
+ 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/home-manager/modules/programs/obs-studio.nix b/home-manager/modules/programs/obs-studio.nix
new file mode 100644
index 00000000000..6df5978384c
--- /dev/null
+++ b/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/home-manager/modules/programs/offlineimap-accounts.nix b/home-manager/modules/programs/offlineimap-accounts.nix
new file mode 100644
index 00000000000..afc7a019972
--- /dev/null
+++ b/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/home-manager/modules/programs/offlineimap.nix b/home-manager/modules/programs/offlineimap.nix
new file mode 100644
index 00000000000..4ce12ec0a61
--- /dev/null
+++ b/home-manager/modules/programs/offlineimap.nix
@@ -0,0 +1,173 @@
+{ 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.
+ '';
+ };
+ };
+ };
+
+ 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/home-manager/modules/programs/opam.nix b/home-manager/modules/programs/opam.nix
new file mode 100644
index 00000000000..a61ff7878df
--- /dev/null
+++ b/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/home-manager/modules/programs/password-store.nix b/home-manager/modules/programs/password-store.nix
new file mode 100644
index 00000000000..db31146a1ba
--- /dev/null
+++ b/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/home-manager/modules/programs/pazi.nix b/home-manager/modules/programs/pazi.nix
new file mode 100644
index 00000000000..e1a08eb615a
--- /dev/null
+++ b/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/home-manager/modules/programs/pidgin.nix b/home-manager/modules/programs/pidgin.nix
new file mode 100644
index 00000000000..a375fd1b2bd
--- /dev/null
+++ b/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/home-manager/modules/programs/readline.nix b/home-manager/modules/programs/readline.nix
new file mode 100644
index 00000000000..2f79df6e103
--- /dev/null
+++ b/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/home-manager/modules/programs/rofi.nix b/home-manager/modules/programs/rofi.nix
new file mode 100644
index 00000000000..f344e88e2ff
--- /dev/null
+++ b/home-manager/modules/programs/rofi.nix
@@ -0,0 +1,327 @@
+{ 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";
+
+ 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 = [ pkgs.rofi ];
+
+ 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/home-manager/modules/programs/rtorrent.nix b/home-manager/modules/programs/rtorrent.nix
new file mode 100644
index 00000000000..7beeb2e4221
--- /dev/null
+++ b/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/home-manager/modules/programs/skim.nix b/home-manager/modules/programs/skim.nix
new file mode 100644
index 00000000000..c90fe1b1a35
--- /dev/null
+++ b/home-manager/modules/programs/skim.nix
@@ -0,0 +1,124 @@
+{ 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.
+ '';
+ };
+ };
+
+ 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
+ '';
+ };
+}
diff --git a/home-manager/modules/programs/ssh.nix b/home-manager/modules/programs/ssh.nix
new file mode 100644
index 00000000000..6b0747dd9b1
--- /dev/null
+++ b/home-manager/modules/programs/ssh.nix
@@ -0,0 +1,461 @@
+{ 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 ({ name, ... }: {
+ 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.";
+ };
+
+ 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 name;
+ });
+
+ 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.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.
+ '';
+ };
+
+ 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 = types.loaOf matchBlockModule;
+ default = {};
+ example = literalExample ''
+ {
+ "john.example.com" = {
+ hostname = "example.com";
+ user = "john";
+ };
+ foo = {
+ hostname = "example.com";
+ identityFile = "/home/john/.ssh/foo_rsa";
+ };
+ };
+ '';
+ description = ''
+ Specify per-host settings. Note, if the order of rules matter
+ then this must be a list. See
+ <citerefentry>
+ <refentrytitle>ssh_config</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </citerefentry>.
+ '';
+ };
+ };
+
+ 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 (builtins.attrValues cfg.matchBlocks);
+ message = "Forwarded paths cannot have ports.";
+ }
+ ];
+
+ home.file.".ssh/config".text = ''
+ ${concatStringsSep "\n" (
+ mapAttrsToList (n: v: "${n} ${v}") cfg.extraOptionOverrides)}
+
+ ${concatStringsSep "\n\n" (
+ map matchBlockStr (
+ builtins.attrValues cfg.matchBlocks))}
+
+ Host *
+ ForwardAgent ${yn cfg.forwardAgent}
+ Compression ${yn cfg.compression}
+ ServerAliveInterval ${toString cfg.serverAliveInterval}
+ HashKnownHosts ${yn cfg.hashKnownHosts}
+ UserKnownHostsFile ${cfg.userKnownHostsFile}
+ ControlMaster ${cfg.controlMaster}
+ ControlPath ${cfg.controlPath}
+ ControlPersist ${cfg.controlPersist}
+
+ ${replaceStrings ["\n"] ["\n "] cfg.extraConfig}
+ '';
+ };
+}
diff --git a/home-manager/modules/programs/starship.nix b/home-manager/modules/programs/starship.nix
new file mode 100644
index 00000000000..7c7819865f7
--- /dev/null
+++ b/home-manager/modules/programs/starship.nix
@@ -0,0 +1,94 @@
+{ 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 = types.attrs;
+ default = { };
+ 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 [[ -z $INSIDE_EMACS ]]; 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.shellInit = mkIf cfg.enableFishIntegration ''
+ if test -z "$INSIDE_EMACS"
+ eval (${cfg.package}/bin/starship init fish)
+ end
+ '';
+ };
+}
diff --git a/home-manager/modules/programs/taskwarrior.nix b/home-manager/modules/programs/taskwarrior.nix
new file mode 100644
index 00000000000..cf95511f8ef
--- /dev/null
+++ b/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.attrs;
+ 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/home-manager/modules/programs/termite.nix b/home-manager/modules/programs/termite.nix
new file mode 100644
index 00000000000..8a05db03558
--- /dev/null
+++ b/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 "foregroundBold" 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/home-manager/modules/programs/texlive.nix b/home-manager/modules/programs/texlive.nix
new file mode 100644
index 00000000000..08a376d654a
--- /dev/null
+++ b/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/home-manager/modules/programs/tmux.nix b/home-manager/modules/programs/tmux.nix
new file mode 100644
index 00000000000..766bc6238ba
--- /dev/null
+++ b/home-manager/modules/programs/tmux.nix
@@ -0,0 +1,320 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.tmux;
+
+ pluginName = p: if types.package.check p then p.name else p.plugin.name;
+
+ 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";
+
+ boolToStr = value: if value then "on" else "off";
+
+ tmuxConf = ''
+ set -g default-terminal "${cfg.terminal}"
+ set -g base-index ${toString cfg.baseIndex}
+ setw -g pane-base-index ${toString cfg.baseIndex}
+
+ ${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}
+ ''}
+
+ ${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}
+
+ ${cfg.extraConfig}
+ '';
+
+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.
+ '';
+ };
+
+ 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.";
+ };
+
+ secureSocket = mkOption {
+ default = true;
+ 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;
+
+ home.file.".tmux.conf".text = tmuxConf;
+ }
+
+ (mkIf cfg.sensibleOnTop {
+ home.file.".tmux.conf".text = mkBefore ''
+ # ============================================= #
+ # Start with defaults from the Sensible plugin #
+ # --------------------------------------------- #
+ run-shell ${pkgs.tmuxPlugins.sensible.rtp}
+ # ============================================= #
+ '';
+ })
+
+ (mkIf cfg.secureSocket {
+ home.sessionVariables = {
+ TMUX_TMPDIR = ''''${XDG_RUNTIME_DIR:-"/run/user/\$(id -u)"}'';
+ };
+ })
+
+ (mkIf (cfg.plugins != []) {
+ 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 = mkAfter ''
+ # ============================================= #
+ # 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)}
+ # ============================================= #
+ '';
+ })
+ ]
+ );
+}
diff --git a/home-manager/modules/programs/urxvt.nix b/home-manager/modules/programs/urxvt.nix
new file mode 100644
index 00000000000..e4c72bfe272
--- /dev/null
+++ b/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.attrs;
+ 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/home-manager/modules/programs/vim.nix b/home-manager/modules/programs/vim.nix
new file mode 100644
index 00000000000..39826a9a5d6
--- /dev/null
+++ b/home-manager/modules/programs/vim.nix
@@ -0,0 +1,171 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.vim;
+ defaultPlugins = [ pkgs.vimPlugins.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/home-manager/modules/programs/vscode.nix b/home-manager/modules/programs/vscode.nix
new file mode 100644
index 00000000000..cf7ac722210
--- /dev/null
+++ b/home-manager/modules/programs/vscode.nix
@@ -0,0 +1,101 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.programs.vscode;
+
+ vscodePname = cfg.package.pname;
+
+ configDir = {
+ "vscode" = "Code";
+ "vscode-insiders" = "Code - Insiders";
+ "vscodium" = "VSCodium";
+ }.${vscodePname};
+
+ extensionDir = {
+ "vscode" = "vscode";
+ "vscode-insiders" = "vscode-insiders";
+ "vscodium" = "vscode-oss";
+ }.${vscodePname};
+
+ configFilePath =
+ if pkgs.stdenv.hostPlatform.isDarwin then
+ "Library/Application Support/${configDir}/User/settings.json"
+ else
+ "${config.xdg.configHome}/${configDir}/User/settings.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 = types.attrs;
+ default = {};
+ example = literalExample ''
+ {
+ "update.channel" = "none";
+ "[nix]"."editor.tabSize" = 2;
+ }
+ '';
+ description = ''
+ Configuration written to Visual Studio Code's
+ <filename>settings.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
+ toPaths = path:
+ let
+ p = "${path}/share/vscode/extensions";
+ in
+ # Links every dir in p to the extension path.
+ mapAttrsToList (k: v:
+ {
+ "${extensionPath}/${k}".source = "${p}/${k}";
+ }) (builtins.readDir p);
+ toSymlink = concatMap toPaths cfg.extensions;
+ in
+ foldr
+ (a: b: a // b)
+ {
+ "${configFilePath}" =
+ mkIf (cfg.userSettings != {}) {
+ text = builtins.toJSON cfg.userSettings;
+ };
+ }
+ toSymlink;
+ };
+}
diff --git a/home-manager/modules/programs/vscode/haskell.nix b/home-manager/modules/programs/vscode/haskell.nix
new file mode 100644
index 00000000000..ee84e707102
--- /dev/null
+++ b/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/home-manager/modules/programs/z-lua.nix b/home-manager/modules/programs/z-lua.nix
new file mode 100644
index 00000000000..d722ac6a2f0
--- /dev/null
+++ b/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/home-manager/modules/programs/zathura.nix b/home-manager/modules/programs/zathura.nix
new file mode 100644
index 00000000000..d9f3c1af1fd
--- /dev/null
+++ b/home-manager/modules/programs/zathura.nix
@@ -0,0 +1,58 @@
+{ 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'';
+
+ 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 = [ pkgs.zathura ];
+
+ xdg.configFile."zathura/zathurarc".text = concatStringsSep "\n" ([ ]
+ ++ optional (cfg.extraConfig != "") cfg.extraConfig
+ ++ mapAttrsToList formatLine cfg.options) + "\n";
+ };
+}
diff --git a/home-manager/modules/programs/zsh.nix b/home-manager/modules/programs/zsh.nix
new file mode 100644
index 00000000000..c5694e1f704
--- /dev/null
+++ b/home-manager/modules/programs/zsh.nix
@@ -0,0 +1,456 @@
+{ 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
+ );
+
+ 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.
+ '';
+ };
+ };
+ };
+
+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;
+ };
+
+ 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 = { 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;
+ };
+
+ 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>.";
+ };
+
+ 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 = ''
+ typeset -U path cdpath fpath manpath
+
+ 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 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)
+ "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 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
+ ''}
+
+ ${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/rycee/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}
+ '';
+ }
+
+ (mkIf cfg.oh-my-zsh.enable {
+ # Make sure we create a cache directory since some plugins expect it to exist
+ # See: https://github.com/rycee/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);
+ })
+ ]);
+}