diff options
Diffstat (limited to 'modules')
| -rw-r--r-- | modules/communication/default.nix | 1 | ||||
| -rw-r--r-- | modules/communication/email/default.nix | 30 | ||||
| -rw-r--r-- | modules/communication/neomutt/account/default.nix | 106 | ||||
| -rw-r--r-- | modules/communication/neomutt/account/flavors/gmail.nix | 45 | ||||
| -rw-r--r-- | modules/communication/neomutt/account/options.nix | 328 | ||||
| -rw-r--r-- | modules/communication/neomutt/assertions.nix | 7 | ||||
| -rw-r--r-- | modules/communication/neomutt/default.nix | 429 | ||||
| -rw-r--r-- | modules/communication/neomutt/neomutt.nix | 423 | ||||
| -rw-r--r-- | modules/communication/neomutt/sync/default.nix | 8 | ||||
| -rw-r--r-- | modules/communication/neomutt/sync/mbsync.nix | 9 | ||||
| -rw-r--r-- | modules/communication/neomutt/sync/notmuch.nix | 9 | ||||
| -rw-r--r-- | modules/communication/neomutt/theme.nix | 4 | ||||
| -rw-r--r-- | modules/private/email.example.nix | 49 |
13 files changed, 942 insertions, 506 deletions
diff --git a/modules/communication/default.nix b/modules/communication/default.nix index ade90bb..cd8f0d6 100644 --- a/modules/communication/default.nix +++ b/modules/communication/default.nix @@ -2,7 +2,6 @@ { imports = [ - ./email ./neomutt ]; } diff --git a/modules/communication/email/default.nix b/modules/communication/email/default.nix deleted file mode 100644 index 969403d..0000000 --- a/modules/communication/email/default.nix +++ /dev/null @@ -1,30 +0,0 @@ -{ - config, - lib, - ... -}: - -let - mailDir = "${config.home.homeDirectory}/.local/share/mail"; -in -{ - options.communication.email = { - enable = lib.mkEnableOption "Email suite (mbsync + notmuch)"; - - accounts = lib.mkOption { - type = lib.types.attrsOf lib.types.anything; - default = { }; - description = "Email accounts. Each attribute name is an account identifier."; - }; - }; - - config = lib.mkIf config.communication.email.enable { - programs.mbsync.enable = true; - programs.notmuch.enable = true; - - accounts.email.maildirBasePath = mailDir; - accounts.email.accounts = config.communication.email.accounts; - - home.sessionVariables.MAILDIR = mailDir; - }; -} diff --git a/modules/communication/neomutt/account/default.nix b/modules/communication/neomutt/account/default.nix new file mode 100644 index 0000000..183e5ff --- /dev/null +++ b/modules/communication/neomutt/account/default.nix @@ -0,0 +1,106 @@ +{ config, lib, ... }: + +with lib; + +let + inherit (lib) types; +in +{ + options.communication.neomutt.accounts = mkOption { + type = types.attrsOf ( + types.submodule { + imports = [ + ./options.nix + ./flavors/gmail.nix + ]; + } + ); + default = { }; + description = "Specification of email accounts."; + }; + + config = mkIf config.communication.neomutt.enable { + accounts.email.accounts = mapAttrs ( + name: account: + let + a = account; + in + { + inherit (a) realName address primary; + userName = if a.userName != null then a.userName else a.address; + + passwordCommand = + if a.passwordGopassSecret != null then + "${lib.getExe config.security.gopass.package} -o ${a.passwordGopassSecret}" + else + null; + + flavor = a.flavor; + inherit (a) aliases; + + folders = { + inherit (a.folders) inbox trash; + } + // optionalAttrs (a.folders.drafts != null) { drafts = a.folders.drafts; } + // optionalAttrs (a.folders.sent != null) { sent = a.folders.sent; }; + + neomutt = { + enable = true; + mailboxType = a.neomutt.mailboxType; + extraConfig = a.neomutt.extraConfig; + }; + + mbsync = { + enable = true; + create = a.mbsync.create; + patterns = a.mbsync.patterns; + flatten = a.mbsync.flatten; + extraConfig = { + account = a.mbsync.extraConfig.account; + channel = a.mbsync.extraConfig.channel; + local = a.mbsync.extraConfig.local; + remote = a.mbsync.extraConfig.remote; + }; + }; + + notmuch = { + enable = true; + neomutt = { + enable = true; + virtualMailboxes = a.notmuch.neomutt.virtualMailboxes; + }; + }; + + imap = optionalAttrs (a.imap.host != null) { + host = a.imap.host; + port = a.imap.port; + tls = { + enable = a.imap.tls.enable; + useStartTls = a.imap.tls.useStartTls; + }; + }; + + smtp = optionalAttrs (a.smtp.host != null) { + host = a.smtp.host; + port = a.smtp.port; + tls = { + enable = a.smtp.tls.enable; + useStartTls = a.smtp.tls.useStartTls; + }; + }; + + gpg = optionalAttrs (a.gpg.key != null) { + key = a.gpg.key; + signByDefault = a.gpg.signByDefault; + encryptByDefault = a.gpg.encryptByDefault; + }; + + signature = optionalAttrs (a.signature.text != null || a.signature.command != null) { + text = a.signature.text; + command = a.signature.command; + showSignature = a.signature.showSignature; + }; + } + ) (filterAttrs (n: a: a.enable) config.communication.neomutt.accounts); + }; +} diff --git a/modules/communication/neomutt/account/flavors/gmail.nix b/modules/communication/neomutt/account/flavors/gmail.nix new file mode 100644 index 0000000..5ccceed --- /dev/null +++ b/modules/communication/neomutt/account/flavors/gmail.nix @@ -0,0 +1,45 @@ +{ + config, + lib, + name, + ... +}: + +{ + config = lib.mkIf (config.flavor == "gmail.com") { + folders = { + inbox = lib.mkDefault "Inbox"; + drafts = lib.mkDefault "[Gmail]/Drafts"; + sent = lib.mkDefault "[Gmail]/Sent Mail"; + trash = lib.mkDefault "[Gmail]/Trash"; + }; + + mbsync = { + create = lib.mkDefault "maildir"; + patterns = lib.mkDefault [ + "INBOX" + "[Gmail]/Drafts" + "[Gmail]/Sent Mail" + "[Gmail]/Spam" + "[Gmail]/Trash" + ]; + extraConfig.channel = { + Sync = lib.mkDefault "All"; + CopyArrivalDate = lib.mkDefault "yes"; + Expunge = lib.mkDefault "Both"; + SyncState = lib.mkDefault "*"; + }; + }; + + neomutt.extraConfig = lib.mkDefault '' + mailboxes =Inbox ="[Gmail]/Drafts" ="[Gmail]/Sent Mail" ="[Gmail]/Spam" ="[Gmail]/Trash" + ''; + + notmuch.neomutt.virtualMailboxes = lib.mkDefault [ + { + name = "All Mail"; + query = "folder:${name}/Inbox or folder:\"${name}/[Gmail]/Sent Mail\""; + } + ]; + }; +} diff --git a/modules/communication/neomutt/account/options.nix b/modules/communication/neomutt/account/options.nix new file mode 100644 index 0000000..bb53231 --- /dev/null +++ b/modules/communication/neomutt/account/options.nix @@ -0,0 +1,328 @@ +{ lib, ... }: + +{ + options = { + enable = lib.mkEnableOption "this email account" // { + default = true; + }; + + realName = lib.mkOption { + type = lib.types.str; + description = "Name displayed when sending mails."; + }; + + address = lib.mkOption { + type = lib.types.str; + description = "Email address of this account."; + }; + + userName = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Server username. Defaults to address if null."; + }; + + passwordGopassSecret = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Gopass secret path (e.g. mail/user@gmail.com). Constructs passwordCommand automatically."; + }; + + primary = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Whether this is the primary account."; + }; + + flavor = lib.mkOption { + type = lib.types.nullOr ( + lib.types.enum [ + "gmail.com" + "fastmail.com" + "outlook.office365.com" + "outlook.office365.com-ews" + "mailbox.org" + "migadu.com" + "posteo.de" + "runbox.com" + "yandex.com" + "davmail" + "plain" + ] + ); + default = null; + description = "Email provider flavor for automatic configuration."; + }; + + folders = { + inbox = lib.mkOption { + type = lib.types.str; + default = "Inbox"; + description = "Inbox folder."; + }; + + drafts = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Drafts folder."; + }; + + sent = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Sent mail folder."; + }; + + trash = lib.mkOption { + type = lib.types.str; + default = "Trash"; + description = "Trash folder."; + }; + }; + + neomutt = { + mailboxType = lib.mkOption { + type = lib.types.enum [ + "maildir" + "imap" + ]; + default = "maildir"; + description = "Whether this account uses maildir folders or IMAP mailboxes."; + }; + + extraConfig = lib.mkOption { + type = lib.types.lines; + default = ""; + description = "Extra neomutt config lines for this account."; + }; + }; + + mbsync = { + create = lib.mkOption { + type = lib.types.enum [ + "none" + "maildir" + "imap" + "both" + ]; + default = "none"; + description = "Automatically create missing mailboxes."; + }; + + patterns = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ "INBOX" ]; + description = "Mailbox patterns to synchronize."; + }; + + flatten = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Flatten hierarchy delimiter."; + }; + + extraConfig = { + account = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.oneOf [ + lib.types.str + lib.types.int + lib.types.bool + (lib.types.listOf lib.types.str) + ] + ); + default = { }; + description = "Account section extra configuration."; + }; + + channel = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.oneOf [ + lib.types.str + lib.types.int + lib.types.bool + (lib.types.listOf lib.types.str) + ] + ); + default = { }; + description = "Channel extra configuration."; + }; + + local = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.oneOf [ + lib.types.str + lib.types.int + lib.types.bool + (lib.types.listOf lib.types.str) + ] + ); + default = { }; + description = "Local store extra configuration."; + }; + + remote = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.oneOf [ + lib.types.str + lib.types.int + lib.types.bool + (lib.types.listOf lib.types.str) + ] + ); + default = { }; + description = "Remote store extra configuration."; + }; + }; + }; + + notmuch = { + neomutt = { + virtualMailboxes = lib.mkOption { + type = lib.types.listOf ( + lib.types.submodule { + options = { + name = lib.mkOption { + type = lib.types.str; + description = "Display name."; + }; + + query = lib.mkOption { + type = lib.types.str; + description = "Notmuch query."; + }; + + limit = lib.mkOption { + type = lib.types.nullOr lib.types.int; + default = null; + description = "Result limit."; + }; + + type = lib.mkOption { + type = lib.types.nullOr ( + lib.types.enum [ + "threads" + "messages" + ] + ); + default = null; + description = "Result type."; + }; + }; + } + ); + default = [ ]; + description = "Virtual mailboxes from notmuch queries."; + }; + }; + }; + + imap = { + host = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "IMAP server hostname."; + }; + + port = lib.mkOption { + type = lib.types.nullOr lib.types.port; + default = null; + description = "IMAP server port."; + }; + + tls = { + enable = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Enable TLS."; + }; + + useStartTls = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Use STARTTLS."; + }; + }; + }; + + smtp = { + host = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "SMTP server hostname."; + }; + + port = lib.mkOption { + type = lib.types.nullOr lib.types.port; + default = null; + description = "SMTP server port."; + }; + + tls = { + enable = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Enable TLS."; + }; + + useStartTls = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Use STARTTLS."; + }; + }; + }; + + gpg = { + key = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "GPG key to use."; + }; + + signByDefault = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Sign messages by default."; + }; + + encryptByDefault = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Encrypt messages by default."; + }; + }; + + signature = { + text = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + description = "Signature text."; + }; + + command = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + description = "Command that generates a signature."; + }; + + showSignature = lib.mkOption { + type = lib.types.nullOr ( + lib.types.enum [ + "append" + "attach" + "none" + ] + ); + default = null; + description = "Signature display method."; + }; + }; + + aliases = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + description = "Alternative email addresses for this account."; + }; + }; +} diff --git a/modules/communication/neomutt/assertions.nix b/modules/communication/neomutt/assertions.nix index 22434a4..b85280e 100644 --- a/modules/communication/neomutt/assertions.nix +++ b/modules/communication/neomutt/assertions.nix @@ -17,13 +17,6 @@ ''; } { - assertion = !config.communication.neomutt.enable || config.communication.email.enable; - message = '' - communication.neomutt is enabled but communication.email.enable is not. - neomutt requires the email module (mbsync + notmuch) to be enabled. - ''; - } - { assertion = !config.communication.neomutt.enable || config.terminal.kitty.enable; message = '' communication.neomutt is enabled but terminal.kitty.enable is not. diff --git a/modules/communication/neomutt/default.nix b/modules/communication/neomutt/default.nix index be3045d..34147fd 100644 --- a/modules/communication/neomutt/default.nix +++ b/modules/communication/neomutt/default.nix @@ -1,431 +1,20 @@ -# neomutt - Terminal email client -{ - config, - lib, - pkgs, - ... -}: -let - neomuttDesktopItem = pkgs.makeDesktopItem { - name = "neomutt"; - desktopName = "NeoMutt"; - exec = "${lib.getExe config.programs.kitty.package} -e ${lib.getExe config.programs.neomutt.package}"; - icon = "kitty"; - categories = [ - "Network" - "Email" - ]; - comment = "Terminal email client"; - terminal = false; - type = "Application"; - }; -in +{ config, lib, ... }: + { imports = [ + ./account + ./sync + ./neomutt.nix ./assertions.nix ./theme.nix ]; + options.communication.neomutt = { - enable = lib.mkEnableOption "Enables neomutt."; + enable = lib.mkEnableOption "Email suite (neomutt + mbsync + notmuch)"; }; config = lib.mkIf config.communication.neomutt.enable { - xdg.mimeApps.defaultApplications = { - "x-scheme-handler/mailto" = "neomutt.desktop"; - }; - - home.packages = [ - pkgs.urlscan - pkgs.w3m - neomuttDesktopItem - ]; - - xdg.configFile."neomutt/mailcap".text = '' - text/html; w3m -dump -T text/html '%s'; copiousoutput; nametemplate=%s.html - text/html; $BROWSER '%s'; nametemplate=%s.html - image/*; xdg-open '%s' & - application/pdf; xdg-open '%s'; nametemplate=%s.pdf - video/*; xdg-open '%s' & - audio/*; xdg-open '%s' & - application/*; xdg-open '%s' & - ''; - - xdg.configFile."neomutt/flag_legend.txt".text = '' - NeoMutt Index Flags - =================== - - Message state ($flag_chars) -- unchanged defaults - N New - unread, arrived since you last checked - O Old - unread, but seen in a previous session - D Deleted - marked for deletion (pending expunge) - d Att-del - has attachments marked for deletion - ! Flagged - important (toggle with F) - * Tagged - selected for a bulk operation (toggle with t) - r Replied - you've replied to this message - (blank) - read, nothing else notable - - Cryptography ($crypt_chars) - Verified - signed, and the signature checks out - Encrypted - PGP/SMIME encrypted - Signed - PGP/SMIME signed (unverified) - PGP key - message contains a PGP public key - (blank) - no cryptography info - - Addressing ($to_chars) - To you only - sent to you and only you - To (with others) - you're in the To: list, among others - Cc only - you're only in the Cc: list - From you - sent by you - Mailing list - sent to a list you're subscribed to - (blank) - not in To/Cc (e.g. Bcc, unlisted) - - Press q to close. - ''; - - programs.neomutt = { - sidebar.enable = true; - sort = "reverse-threads"; - vimKeys = true; - unmailboxes = true; - checkStatsInterval = 20; - - extraConfig = '' - set menu_scroll = yes - - # cancel any prompt/command-line entry with Esc, vim-style - set abort_key = "<Esc>" - - # attachments: use our mailcap for dedicated openers, - # and auto-render html/images inline where sensible - set mailcap_path = "~/.config/neomutt/mailcap" - auto_view text/html image/png image/jpeg image/gif application/pdf - ''; - - binds = [ - # --- sidebar navigation (vim: k=up, j=down) --- - { - map = [ - "index" - "pager" - ]; - key = "\\CK"; - action = "sidebar-prev"; - } - { - map = [ - "index" - "pager" - ]; - key = "\\CJ"; - action = "sidebar-next"; - } - { - # select the highlighted mailbox directly, no Ctrl+O needed - map = [ - "index" - "pager" - ]; - key = "o"; - action = "sidebar-open"; - } - - # --- open / go deeper (vim: l = right/forward) --- - { - map = [ "index" ]; - key = "<Return>"; - action = "display-message"; - } - { - map = [ "index" ]; - key = "l"; - action = "display-message"; - } - { - map = [ "browser" ]; - key = "<Return>"; - action = "select-entry"; - } - { - map = [ "browser" ]; - key = "l"; - action = "select-entry"; - } - { - map = [ "attach" ]; - key = "<Return>"; - action = "view-attach"; - } - { - map = [ "attach" ]; - key = "l"; - action = "view-attach"; - } - { - map = [ - "alias" - "query" - ]; - key = "<Return>"; - action = "select-entry"; - } - - # --- back / exit (vim: h = left/back) --- - { - map = [ "pager" ]; - key = "h"; - action = "exit"; - } - { - map = [ "browser" ]; - key = "h"; - action = "exit"; - } - { - map = [ "attach" ]; - key = "h"; - action = "exit"; - } - { - map = [ "index" ]; - key = "h"; - action = "collapse-thread"; - } - - # --- pager line scrolling (vim: j/k move by line) --- - { - map = [ "pager" ]; - key = "j"; - action = "next-line"; - } - { - map = [ "pager" ]; - key = "k"; - action = "previous-line"; - } - { - # kept from before: jump straight to the next unread message, - # extends the h/l "back/forward" metaphor already in use - map = [ "pager" ]; - key = "l"; - action = "next-unread"; - } - - # --- top/bottom of buffer (vim: gg/G -- NOT 0/$, which mean - # start/end of *line* in vim and would be a false friend here) --- - { - map = [ "index" ]; - key = "gg"; - action = "first-entry"; - } - { - map = [ "index" ]; - key = "G"; - action = "last-entry"; - } - { - map = [ "pager" ]; - key = "gg"; - action = "top"; - } - { - map = [ "pager" ]; - key = "G"; - action = "bottom"; - } - { - map = [ "browser" ]; - key = "gg"; - action = "first-entry"; - } - { - map = [ "browser" ]; - key = "G"; - action = "last-entry"; - } - - # --- page scrolling (vim: Ctrl-f/b full page, Ctrl-d/u half page) --- - { - map = [ "pager" ]; - key = "\\Cf"; - action = "next-page"; - } - { - map = [ "pager" ]; - key = "\\Cb"; - action = "previous-page"; - } - { - map = [ "pager" ]; - key = "\\Cd"; - action = "half-down"; - } - { - map = [ "pager" ]; - key = "\\Cu"; - action = "half-up"; - } - { - # index has no half-page concept, so Ctrl-d/u fall back to - # the same full-page scroll as Ctrl-f/b - map = [ "index" ]; - key = "\\Cf"; - action = "next-page"; - } - { - map = [ "index" ]; - key = "\\Cb"; - action = "previous-page"; - } - { - map = [ "index" ]; - key = "\\Cd"; - action = "half-down"; - } - { - map = [ "index" ]; - key = "\\Cu"; - action = "half-up"; - } - - # --- search (vim: / forward, ? backward, n/N repeat) --- - { - map = [ - "index" - "pager" - ]; - key = "/"; - action = "search"; - } - { - map = [ - "index" - "pager" - ]; - key = "?"; - action = "search-reverse"; - } - { - map = [ - "index" - "pager" - ]; - key = "n"; - action = "search-next"; - } - { - map = [ - "index" - "pager" - ]; - key = "N"; - action = "search-opposite"; - } - - { - map = [ - "index" - "pager" - ]; - key = "u"; - action = "undelete-message"; - } - - # --- copy (vim: yy yank -- non-destructive, unlike save-message - # which moves/deletes the original) --- - { - map = [ "index" ]; - key = "y"; - action = "noop"; - } - { - map = [ "index" ]; - key = "yy"; - action = "copy-message"; - } - - # --- tagging (vim: v/V visual select single/whole block) --- - # NOTE: this overrides neomutt's default "v" (view-attachments). - # Attachments are still reachable from the pager/index via the - # mailcap auto-view or the attach menu, but if you want plain - # "v" back for view-attachments, just delete these two binds. - { - map = [ "index" ]; - key = "v"; - action = "tag-entry"; - } - { - map = [ "index" ]; - key = "V"; - action = "tag-thread"; - } - - # --- mailbox switching (vim: gt = next tab) --- - { - map = [ "index" ]; - key = "gt"; - action = "next-unread-mailbox"; - } - - # --- filter/limit (vim: gf to 'find' or 'filter') --- - { - map = [ "index" ]; - key = "gf"; - action = "limit"; - } - - # --- filter/limit (vim: gf to 'find' or 'filter') --- - { - map = [ "index" ]; - key = "gF"; - action = "limit"; - } - - # --- help message to show functions and keybindings --- - { - map = [ - "index" - "pager" - "browser" - "attach" - ]; - key = "g?"; - action = "help"; - } - ]; - - macros = [ - { - map = [ - "index" - "pager" - ]; - key = "O"; - action = "<sync-mailbox><shell-escape>mbsync -a<enter><sync-mailbox>"; - } - { - # gx: extract links from the current message and open one, vim-netrw style - map = [ - "index" - "pager" - ]; - key = "gx"; - action = "<pipe-message>urlscan<Enter>"; - } - { - map = [ - "index" - "pager" - ]; - key = "gl"; - action = "<shell-escape>less ~/.config/neomutt/flag_legend.txt<enter>"; - } - { - # ZZ: save and quit, vim-style (sync mailbox, then quit) - map = [ "index" ]; - key = "ZZ"; - action = "<sync-mailbox><quit>"; - } - ]; - }; + accounts.email.maildirBasePath = "${config.home.homeDirectory}/.local/share/mail"; + home.sessionVariables.MAILDIR = config.accounts.email.maildirBasePath; }; } diff --git a/modules/communication/neomutt/neomutt.nix b/modules/communication/neomutt/neomutt.nix new file mode 100644 index 0000000..84afdf7 --- /dev/null +++ b/modules/communication/neomutt/neomutt.nix @@ -0,0 +1,423 @@ +# neomutt - Terminal email client +{ + config, + lib, + pkgs, + ... +}: +let + neomuttDesktopItem = pkgs.makeDesktopItem { + name = "neomutt"; + desktopName = "NeoMutt"; + exec = "${lib.getExe config.programs.kitty.package} -e ${lib.getExe config.programs.neomutt.package}"; + icon = "kitty"; + categories = [ + "Network" + "Email" + ]; + comment = "Terminal email client"; + terminal = false; + type = "Application"; + }; +in +{ + config = lib.mkIf config.communication.neomutt.enable { + xdg.mimeApps.defaultApplications = { + "x-scheme-handler/mailto" = "neomutt.desktop"; + }; + + home.packages = [ + pkgs.urlscan + pkgs.w3m + neomuttDesktopItem + ]; + + xdg.configFile."neomutt/mailcap".text = '' + text/html; w3m -dump -T text/html '%s'; copiousoutput; nametemplate=%s.html + text/html; $BROWSER '%s'; nametemplate=%s.html + image/*; xdg-open '%s' & + application/pdf; xdg-open '%s'; nametemplate=%s.pdf + video/*; xdg-open '%s' & + audio/*; xdg-open '%s' & + application/*; xdg-open '%s' & + ''; + + xdg.configFile."neomutt/flag_legend.txt".text = '' + NeoMutt Index Flags + =================== + + Message state ($flag_chars) -- unchanged defaults + N New - unread, arrived since you last checked + O Old - unread, but seen in a previous session + D Deleted - marked for deletion (pending expunge) + d Att-del - has attachments marked for deletion + ! Flagged - important (toggle with F) + * Tagged - selected for a bulk operation (toggle with t) + r Replied - you've replied to this message + (blank) - read, nothing else notable + + Cryptography ($crypt_chars) + Verified - signed, and the signature checks out + Encrypted - PGP/SMIME encrypted + Signed - PGP/SMIME signed (unverified) + PGP key - message contains a PGP public key + (blank) - no cryptography info + + Addressing ($to_chars) + To you only - sent to you and only you + To (with others) - you're in the To: list, among others + Cc only - you're only in the Cc: list + From you - sent by you + Mailing list - sent to a list you're subscribed to + (blank) - not in To/Cc (e.g. Bcc, unlisted) + + Press q to close. + ''; + + programs.neomutt = { + sidebar.enable = true; + sort = "reverse-threads"; + vimKeys = true; + unmailboxes = true; + checkStatsInterval = 20; + + extraConfig = '' + set menu_scroll = yes + + # cancel any prompt/command-line entry with Esc, vim-style + set abort_key = "<Esc>" + + # attachments: use our mailcap for dedicated openers, + # and auto-render html/images inline where sensible + set mailcap_path = "~/.config/neomutt/mailcap" + auto_view text/html image/png image/jpeg image/gif application/pdf + ''; + + binds = [ + # --- sidebar navigation (vim: k=up, j=down) --- + { + map = [ + "index" + "pager" + ]; + key = "\\CK"; + action = "sidebar-prev"; + } + { + map = [ + "index" + "pager" + ]; + key = "\\CJ"; + action = "sidebar-next"; + } + { + # select the highlighted mailbox directly, no Ctrl+O needed + map = [ + "index" + "pager" + ]; + key = "o"; + action = "sidebar-open"; + } + + # --- open / go deeper (vim: l = right/forward) --- + { + map = [ "index" ]; + key = "<Return>"; + action = "display-message"; + } + { + map = [ "index" ]; + key = "l"; + action = "display-message"; + } + { + map = [ "browser" ]; + key = "<Return>"; + action = "select-entry"; + } + { + map = [ "browser" ]; + key = "l"; + action = "select-entry"; + } + { + map = [ "attach" ]; + key = "<Return>"; + action = "view-attach"; + } + { + map = [ "attach" ]; + key = "l"; + action = "view-attach"; + } + { + map = [ + "alias" + "query" + ]; + key = "<Return>"; + action = "select-entry"; + } + + # --- back / exit (vim: h = left/back) --- + { + map = [ "pager" ]; + key = "h"; + action = "exit"; + } + { + map = [ "browser" ]; + key = "h"; + action = "exit"; + } + { + map = [ "attach" ]; + key = "h"; + action = "exit"; + } + { + map = [ "index" ]; + key = "h"; + action = "collapse-thread"; + } + + # --- pager line scrolling (vim: j/k move by line) --- + { + map = [ "pager" ]; + key = "j"; + action = "next-line"; + } + { + map = [ "pager" ]; + key = "k"; + action = "previous-line"; + } + { + # kept from before: jump straight to the next unread message, + # extends the h/l "back/forward" metaphor already in use + map = [ "pager" ]; + key = "l"; + action = "next-unread"; + } + + # --- top/bottom of buffer (vim: gg/G -- NOT 0/$, which mean + # start/end of *line* in vim and would be a false friend here) --- + { + map = [ "index" ]; + key = "gg"; + action = "first-entry"; + } + { + map = [ "index" ]; + key = "G"; + action = "last-entry"; + } + { + map = [ "pager" ]; + key = "gg"; + action = "top"; + } + { + map = [ "pager" ]; + key = "G"; + action = "bottom"; + } + { + map = [ "browser" ]; + key = "gg"; + action = "first-entry"; + } + { + map = [ "browser" ]; + key = "G"; + action = "last-entry"; + } + + # --- page scrolling (vim: Ctrl-f/b full page, Ctrl-d/u half page) --- + { + map = [ "pager" ]; + key = "\\Cf"; + action = "next-page"; + } + { + map = [ "pager" ]; + key = "\\Cb"; + action = "previous-page"; + } + { + map = [ "pager" ]; + key = "\\Cd"; + action = "half-down"; + } + { + map = [ "pager" ]; + key = "\\Cu"; + action = "half-up"; + } + { + # index has no half-page concept, so Ctrl-d/u fall back to + # the same full-page scroll as Ctrl-f/b + map = [ "index" ]; + key = "\\Cf"; + action = "next-page"; + } + { + map = [ "index" ]; + key = "\\Cb"; + action = "previous-page"; + } + { + map = [ "index" ]; + key = "\\Cd"; + action = "half-down"; + } + { + map = [ "index" ]; + key = "\\Cu"; + action = "half-up"; + } + + # --- search (vim: / forward, ? backward, n/N repeat) --- + { + map = [ + "index" + "pager" + ]; + key = "/"; + action = "search"; + } + { + map = [ + "index" + "pager" + ]; + key = "?"; + action = "search-reverse"; + } + { + map = [ + "index" + "pager" + ]; + key = "n"; + action = "search-next"; + } + { + map = [ + "index" + "pager" + ]; + key = "N"; + action = "search-opposite"; + } + + { + map = [ + "index" + "pager" + ]; + key = "u"; + action = "undelete-message"; + } + + # --- copy (vim: yy yank -- non-destructive, unlike save-message + # which moves/deletes the original) --- + { + map = [ "index" ]; + key = "y"; + action = "noop"; + } + { + map = [ "index" ]; + key = "yy"; + action = "copy-message"; + } + + # --- tagging (vim: v/V visual select single/whole block) --- + # NOTE: this overrides neomutt's default "v" (view-attachments). + # Attachments are still reachable from the pager/index via the + # mailcap auto-view or the attach menu, but if you want plain + # "v" back for view-attachments, just delete these two binds. + { + map = [ "index" ]; + key = "v"; + action = "tag-entry"; + } + { + map = [ "index" ]; + key = "V"; + action = "tag-thread"; + } + + # --- mailbox switching (vim: gt = next tab) --- + { + map = [ "index" ]; + key = "gt"; + action = "next-unread-mailbox"; + } + + # --- filter/limit (vim: gf to 'find' or 'filter') --- + { + map = [ "index" ]; + key = "gf"; + action = "limit"; + } + + # --- filter/limit (vim: gf to 'find' or 'filter') --- + { + map = [ "index" ]; + key = "gF"; + action = "limit"; + } + + # --- help message to show functions and keybindings --- + { + map = [ + "index" + "pager" + "browser" + "attach" + ]; + key = "g?"; + action = "help"; + } + ]; + + macros = [ + { + map = [ + "index" + "pager" + ]; + key = "O"; + action = "<sync-mailbox><shell-escape>mbsync -a<enter><sync-mailbox>"; + } + { + # gx: extract links from the current message and open one, vim-netrw style + map = [ + "index" + "pager" + ]; + key = "gx"; + action = "<pipe-message>urlscan<Enter>"; + } + { + map = [ + "index" + "pager" + ]; + key = "gl"; + action = "<shell-escape>less ~/.config/neomutt/flag_legend.txt<enter>"; + } + { + # ZZ: save and quit, vim-style (sync mailbox, then quit) + map = [ "index" ]; + key = "ZZ"; + action = "<sync-mailbox><quit>"; + } + ]; + }; + }; +} diff --git a/modules/communication/neomutt/sync/default.nix b/modules/communication/neomutt/sync/default.nix new file mode 100644 index 0000000..3fd9362 --- /dev/null +++ b/modules/communication/neomutt/sync/default.nix @@ -0,0 +1,8 @@ +{ ... }: + +{ + imports = [ + ./mbsync.nix + ./notmuch.nix + ]; +} diff --git a/modules/communication/neomutt/sync/mbsync.nix b/modules/communication/neomutt/sync/mbsync.nix new file mode 100644 index 0000000..6d5ad86 --- /dev/null +++ b/modules/communication/neomutt/sync/mbsync.nix @@ -0,0 +1,9 @@ +{ config, lib, ... }: + +with lib; + +{ + config = mkIf config.communication.neomutt.enable { + programs.mbsync.enable = true; + }; +} diff --git a/modules/communication/neomutt/sync/notmuch.nix b/modules/communication/neomutt/sync/notmuch.nix new file mode 100644 index 0000000..9679bf4 --- /dev/null +++ b/modules/communication/neomutt/sync/notmuch.nix @@ -0,0 +1,9 @@ +{ config, lib, ... }: + +with lib; + +{ + config = mkIf config.communication.neomutt.enable { + programs.notmuch.enable = true; + }; +} diff --git a/modules/communication/neomutt/theme.nix b/modules/communication/neomutt/theme.nix index 1d62c55..583b03f 100644 --- a/modules/communication/neomutt/theme.nix +++ b/modules/communication/neomutt/theme.nix @@ -58,7 +58,7 @@ color index_tags cyan default # base0C (Transformed tags, %g %J) # --- Index Message States --------------------------------------------- - # By targeting *only* `index_subject`, we preserve your multi-color + # By targeting *only* `index_subject`, you preserve your multi-color # columns while still easily identifying unread or flagged emails. color index_subject blue default "~U" # Unread: Blue (base0D) color index_subject blue default "~N" # New: Blue (base0D) @@ -117,7 +117,7 @@ color body brightblack default "^gpg: " # # --- Sidebar (was completely unset despite sidebar.enable = true) ----- - set sidebar_format = " %D%* %?N? %N?%?F? %F? " + set sidebar_format = " %D%* %?N? %N%?F? %F? " color sidebar_background default default # transparent, blends with terminal bg color sidebar_divider white default # base03 (matches old tree color) color sidebar_ordinary white default # base05 (normal mailbox entries) diff --git a/modules/private/email.example.nix b/modules/private/email.example.nix index 1c863cc..adf71c3 100644 --- a/modules/private/email.example.nix +++ b/modules/private/email.example.nix @@ -1,56 +1,13 @@ -{ config, lib, ... }: +{ ... }: { - config.communication.email.accounts = { + config.communication.neomutt.accounts = { "example" = { realName = "Example User"; address = "user@gmail.com"; - userName = "user@gmail.com"; - passwordCommand = "${lib.getExe config.security.gopass.package} -o mail/user@gmail.com"; + passwordGopassSecret = "mail/user@gmail.com"; flavor = "gmail.com"; primary = true; - neomutt = { - enable = true; - mailboxType = "maildir"; - extraConfig = '' - mailboxes =Inbox ="[Gmail]/Drafts" ="[Gmail]/Sent Mail" ="[Gmail]/Spam" ="[Gmail]/Trash" - ''; - }; - - folders = { - inbox = "Inbox"; - drafts = "[Gmail]/Drafts"; - sent = "[Gmail]/Sent Mail"; - trash = "[Gmail]/Trash"; - }; - - mbsync = { - enable = true; - create = "maildir"; - patterns = [ - "INBOX" - "[Gmail]/Drafts" - "[Gmail]/Sent Mail" - "[Gmail]/Spam" - "[Gmail]/Trash" - ]; - extraConfig.channel = { - SyncState = "*"; - Sync = "All"; - CopyArrivalDate = "yes"; - Expunge = "Both"; - }; - }; - - notmuch = { - enable = true; - neomutt.virtualMailboxes = [ - { - name = "All Mail"; - query = "folder:arpitchakladar/Inbox or folder:\"arpitchakladar/[Gmail]/Sent Mail\""; - } - ]; - }; }; }; } |
