neomutt: Initial IMAP support (#4597)

neomutt: Updated options and added tests

neomutt: Added test for individual mailbox type

neomutt: Formatted code

neomutt: Enable ssl_force_tls based on IMAP instead of SMTP

neomutt: Applied suggestions from @chayleaf

neomutt: fix breaking tests
This commit is contained in:
Christian Dannie Storgaard 2024-02-11 19:22:37 +02:00 committed by GitHub
parent bfd0ae29a8
commit a09cfdbaf1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 360 additions and 14 deletions

View file

@ -17,6 +17,14 @@ let
default = null;
description = "Name to display";
};
type = mkOption {
type = types.nullOr (types.enum [ "maildir" "imap" ]);
example = "imap";
default = null;
description =
"Whether this mailbox is a maildir folder or an IMAP mailbox";
};
};
};
@ -75,6 +83,14 @@ in {
description = "Use a different name as mailbox name";
};
mailboxType = mkOption {
type = types.enum [ "maildir" "imap" ];
default = "maildir";
example = "imap";
description =
"Whether this account uses maildir folders or IMAP mailboxes";
};
extraMailboxes = mkOption {
type = with types; listOf (either str (submodule extraMailboxOptions));
default = [ ];

View file

@ -8,6 +8,59 @@ let
neomuttAccounts =
filter (a: a.neomutt.enable) (attrValues config.accounts.email.accounts);
accountCommandNeeded = any (a:
a.neomutt.enable && (a.neomutt.mailboxType == "imap"
|| (any (m: !isString m && m.type == "imap") a.neomutt.extraMailboxes)))
(attrValues config.accounts.email.accounts);
accountCommand = let
imapAccounts = filter (a:
a.neomutt.enable && a.imap.host != null && a.userName != null
&& a.passwordCommand != null) (attrValues config.accounts.email.accounts);
accountCase = account:
let passwordCmd = toString account.passwordCommand;
in ''
${account.userName}@${account.imap.host})
found=1
username="${account.userName}"
password="$(${passwordCmd})"
;;'';
in pkgs.writeShellScriptBin "account-command.sh" ''
# Automatically set login variables based on the current account.
# This requires NeoMutt >= 2022-05-16
while [ ! -z "$1" ]; do
case "$1" in
--hostname)
shift
hostname="$1"
;;
--username)
shift
username="$1@"
;;
--type)
shift
type="$1"
;;
*)
exit 1
;;
esac
shift
done
found=
case "''${username}''${hostname}" in
${concatMapStringsSep "\n" accountCase imapAccounts}
esac
if [ -n "$found" ]; then
echo "username: $username"
echo "password: $password"
fi
'';
sidebarModule = types.submodule {
options = {
enable = mkEnableOption "sidebar support";
@ -101,6 +154,21 @@ let
accountFilename = account: config.xdg.configHome + "/neomutt/" + account.name;
accountRootIMAP = account:
let
userName =
lib.optionalString (account.userName != null) "${account.userName}@";
port = lib.optionalString (account.imap.port != null)
":${toString account.imap.port}";
protocol = if account.imap.tls.enable then "imaps" else "imap";
in "${protocol}://${userName}${account.imap.host}${port}";
accountRoot = account:
if account.neomutt.mailboxType == "imap" then
accountRootIMAP account
else
account.maildir.absPath;
genCommonFolderHooks = account:
with account; {
from = "'${address}'";
@ -128,12 +196,12 @@ let
smtp_pass = ''"`${passCmd}`"'';
};
genMaildirAccountConfig = account:
genAccountConfig = account:
with account;
let
folderHook = mapAttrsToList setOption (genCommonFolderHooks account
// optionalAttrs cfg.changeFolderWhenSourcingAccount {
folder = "'${account.maildir.absPath}'";
folder = "'${accountRoot account}'";
});
in ''
${concatStringsSep "\n" folderHook}
@ -145,29 +213,40 @@ let
"mailboxes"
else
''named-mailboxes "${account.neomutt.mailboxName}"'';
extraMailboxes = concatMapStringsSep "\n" (extra:
if isString extra then
''mailboxes "${account.maildir.absPath}/${extra}"''
else if extra.name == null then
''mailboxes "${account.maildir.absPath}/${extra.mailbox}"''
mailroot = accountRoot account;
hookName = if account.neomutt.mailboxType == "imap" then
"account-hook"
else
''
named-mailboxes "${extra.name}" "${account.maildir.absPath}/${extra.mailbox}"'')
"folder-hook";
extraMailboxes = concatMapStringsSep "\n" (extra:
let
mailboxroot = if !isString extra && extra.type == "imap" then
accountRootIMAP account
else if !isString extra && extra.type == "maildir" then
account.maildir.absPath
else
mailroot;
in if isString extra then
''mailboxes "${mailboxroot}/${extra}"''
else if extra.name == null then
''mailboxes "${mailboxroot}/${extra.mailbox}"''
else
''named-mailboxes "${extra.name}" "${mailboxroot}/${extra.mailbox}"'')
account.neomutt.extraMailboxes;
in with account; ''
# register account ${name}
${mailboxes} "${maildir.absPath}/${folders.inbox}"
${mailboxes} "${mailroot}/${folders.inbox}"
${extraMailboxes}
folder-hook ${maildir.absPath}/ " \
${hookName} ${mailroot}/ " \
source ${accountFilename account} "
'';
mraSection = account:
with account;
if account.maildir != null then
genMaildirAccountConfig account
if account.imap.host != null || account.maildir != null then
genAccountConfig account
else
throw "Only maildir is supported at the moment";
throw "Only maildir and IMAP is supported at the moment";
optionsStr = attrs: concatStringsSep "\n" (mapAttrsToList setOption attrs);
@ -219,7 +298,7 @@ let
in ''
# Generated by Home Manager.
set ssl_force_tls = ${
lib.hm.booleans.yesNo (smtp.tls.enable || smtp.tls.useStartTls)
lib.hm.booleans.yesNo (imap.tls.enable || imap.tls.useStartTls)
}
set certificate_file=${toString config.accounts.email.certificatesFile}
@ -366,7 +445,11 @@ in {
"source ${pkgs.neomutt}/share/doc/neomutt/vim-keys/vim-keys.rc"}
# Register accounts
${concatMapStringsSep "\n" registerAccount neomuttAccounts}
${
optionalString (accountCommandNeeded) ''
set account_command = '${accountCommand}/bin/account-command.sh'
''
}${concatMapStringsSep "\n" registerAccount neomuttAccounts}
# Source primary account
source ${accountFilename primary}

View file

@ -0,0 +1,39 @@
#!/nix/store/00000000000000000000000000000000-bash/bin/bash
# Automatically set login variables based on the current account.
# This requires NeoMutt >= 2022-05-16
while [ ! -z "$1" ]; do
case "$1" in
--hostname)
shift
hostname="$1"
;;
--username)
shift
username="$1@"
;;
--type)
shift
type="$1"
;;
*)
exit 1
;;
esac
shift
done
found=
case "${username}${hostname}" in
home.manager@imap.example.com)
found=1
username="home.manager"
password="$(password-command)"
;;
esac
if [ -n "$found" ]; then
echo "username: $username"
echo "password: $password"
fi

View file

@ -1,6 +1,7 @@
{
neomutt-simple = ./neomutt.nix;
neomutt-with-msmtp = ./neomutt-with-msmtp.nix;
neomutt-with-imap = ./neomutt-with-imap.nix;
neomutt-not-primary = ./neomutt-not-primary.nix;
neomutt-with-binds = ./neomutt-with-binds.nix;
neomutt-with-binds-with-warning = ./neomutt-with-binds-with-warning.nix;
@ -9,6 +10,7 @@
neomutt-with-gpg = ./neomutt-with-gpg.nix;
neomutt-no-folder-change = ./neomutt-no-folder-change.nix;
neomutt-with-named-mailboxes = ./neomutt-with-named-mailboxes.nix;
neomutt-with-imap-type-mailboxes = ./neomutt-with-imap-type-mailboxes.nix;
neomutt-with-signature = ./neomutt-with-signature.nix;
neomutt-with-signature-command = ./neomutt-with-signature-command.nix;
neomutt-with-starttls = ./neomutt-with-starttls.nix;

View file

@ -0,0 +1,35 @@
# Generated by Home Manager.
set ssl_force_tls = yes
set certificate_file=/etc/ssl/certs/ca-certificates.crt
# GPG section
set crypt_use_gpgme = yes
set crypt_autosign = no
set crypt_opportunistic_encrypt = no
set pgp_use_gpg_agent = yes
set mbox_type = Maildir
set sort = "threads"
# MTA section
set smtp_pass="`password-command`"
set smtp_url='smtps://home.manager@smtp.example.com'
# MRA section
set folder='imaps://home.manager@imap.example.com:993'
set from='hm@example.com'
set postponed='+Drafts'
set realname='H. M. Test'
set record='+Sent'
set spoolfile='+Inbox'
set trash='+Trash'
# Extra configuration
color status cyan default
unset signature

View file

@ -0,0 +1,34 @@
# Generated by Home Manager.
set header_cache = "/home/hm-user/.cache/neomutt/headers/"
set message_cachedir = "/home/hm-user/.cache/neomutt/messages/"
set editor = "$EDITOR"
set implicit_autoview = yes
alternative_order text/enriched text/plain text
set delete = yes
# Binds
# Macros
# Register accounts
set account_command = '/nix/store/00000000000000000000000000000000-account-command.sh/bin/account-command.sh'
# register account hm@example.com
mailboxes "imaps://home.manager@imap.example.com:993/Inbox"
account-hook imaps://home.manager@imap.example.com:993/ " \
source /home/hm-user/.config/neomutt/hm@example.com "
# Source primary account
source /home/hm-user/.config/neomutt/hm@example.com
# Extra configuration

View file

@ -0,0 +1,36 @@
# Generated by Home Manager.
set header_cache = "/home/hm-user/.cache/neomutt/headers/"
set message_cachedir = "/home/hm-user/.cache/neomutt/messages/"
set editor = "$EDITOR"
set implicit_autoview = yes
alternative_order text/enriched text/plain text
set delete = yes
# Binds
# Macros
# Register accounts
set account_command = '/nix/store/00000000000000000000000000000000-account-command.sh/bin/account-command.sh'
# register account hm@example.com
named-mailboxes "someCustomName" "/home/hm-user/Mail/hm@example.com/Inbox"
mailboxes "/home/hm-user/Mail/hm@example.com/Sent"
named-mailboxes "Spam" "imaps://home.manager@imap.example.com:993/Junk Email"
mailboxes "/home/hm-user/Mail/hm@example.com/Trash"
folder-hook /home/hm-user/Mail/hm@example.com/ " \
source /home/hm-user/.config/neomutt/hm@example.com "
# Source primary account
source /home/hm-user/.config/neomutt/hm@example.com
# Extra configuration

View file

@ -0,0 +1,57 @@
{ config, lib, pkgs, ... }:
with lib;
{
imports = [ ../../accounts/email-test-accounts.nix ];
config = {
accounts.email.accounts = {
"hm@example.com" = {
notmuch.enable = true;
neomutt = {
enable = true;
extraConfig = ''
color status cyan default
'';
mailboxName = "someCustomName";
extraMailboxes = [
"Sent"
{
mailbox = "Junk Email";
name = "Spam";
type = "imap";
}
{ mailbox = "Trash"; }
];
};
imap.port = 993;
};
};
programs.neomutt = {
enable = true;
vimKeys = false;
};
test.stubs.neomutt = { };
nmt.script = ''
assertFileExists home-files/.config/neomutt/neomuttrc
assertFileExists home-files/.config/neomutt/hm@example.com
assertFileContent $(normalizeStorePaths home-files/.config/neomutt/neomuttrc) ${
./neomutt-with-imap-type-mailboxes-expected.conf
}
assertFileContent home-files/.config/neomutt/hm@example.com ${
./hm-example.com-expected
}
confFile=$(grep -o \
'/nix/store/.*-account-command.sh/bin/account-command.sh' \
$TESTED/home-files/.config/neomutt/neomuttrc)
assertFileContent "$(normalizeStorePaths "$confFile")" ${
./account-command.sh-expected
}
'';
};
}

View file

@ -0,0 +1,44 @@
{ config, lib, pkgs, ... }:
with lib;
{
imports = [ ../../accounts/email-test-accounts.nix ];
config = {
accounts.email.accounts = {
"hm@example.com" = {
neomutt = {
enable = true;
mailboxType = "imap";
extraConfig = ''
color status cyan default
'';
};
imap.port = 993;
};
};
programs.neomutt.enable = true;
test.stubs.neomutt = { };
nmt.script = ''
assertFileExists home-files/.config/neomutt/neomuttrc
assertFileExists home-files/.config/neomutt/hm@example.com
assertFileContent $(normalizeStorePaths home-files/.config/neomutt/neomuttrc) ${
./neomutt-with-imap-expected.conf
}
assertFileContent home-files/.config/neomutt/hm@example.com ${
./hm-example.com-imap-expected.conf
}
confFile=$(grep -o \
'/nix/store/.*-account-command.sh/bin/account-command.sh' \
$TESTED/home-files/.config/neomutt/neomuttrc)
assertFileContent "$(normalizeStorePaths "$confFile")" ${
./account-command.sh-expected
}
'';
};
}