mutt: move to home manager module

master
Yorick van Pelt 2023-05-20 13:39:59 +02:00
parent 3c81a5b5ed
commit 8daefcef2c
Signed by: yorick
GPG Key ID: D8D3CC6D951384DE
10 changed files with 581 additions and 153 deletions

View File

@ -0,0 +1,520 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.accounts.email;
gpgModule = types.submodule {
options = {
key = mkOption {
type = types.str;
description = ''
The key to use as listed in <command>gpg --list-keys</command>.
'';
};
signByDefault = mkOption {
type = types.bool;
default = false;
description = "Sign messages by default.";
};
encryptByDefault = mkOption {
type = types.bool;
default = false;
description = "Encrypt outgoing messages by default.";
};
};
};
signatureModule = types.submodule {
options = {
text = mkOption {
type = types.str;
default = "";
example = ''
--
Luke Skywalker
May the force be with you.
'';
description = ''
Signature content.
'';
};
delimiter = mkOption {
type = types.str;
default = ''
--
'';
example = literalExpression ''
~*~*~*~*~*~*~*~*~*~*~*~
'';
description = ''
The delimiter used between the document and the signature.
'';
};
command = mkOption {
type = with types; nullOr path;
default = null;
example = literalExpression ''
pkgs.writeScript "signature" "echo This is my signature"
'';
description = "A command that generates a signature.";
};
showSignature = mkOption {
type = types.enum [ "append" "attach" "none" ];
default = "none";
description = "Method to communicate the signature.";
};
};
};
tlsModule = types.submodule {
options = {
enable = mkOption {
type = types.bool;
default = true;
description = ''
Whether to enable TLS/SSL.
'';
};
useStartTls = mkOption {
type = types.bool;
default = false;
description = ''
Whether to use STARTTLS.
'';
};
certificatesFile = mkOption {
type = types.nullOr types.path;
default = config.accounts.email.certificatesFile;
defaultText = "config.accounts.email.certificatesFile";
description = ''
Path to file containing certificate authorities that should
be used to validate the connection authenticity. If
<literal>null</literal> then the system default is used.
Note, if set then the system default may still be accepted.
'';
};
};
};
imapModule = types.submodule {
options = {
host = mkOption {
type = types.str;
example = "imap.example.org";
description = ''
Hostname of IMAP server.
'';
};
port = mkOption {
type = types.nullOr types.port;
default = null;
example = 993;
description = ''
The port on which the IMAP server listens. If
<literal>null</literal> then the default port is used.
'';
};
tls = mkOption {
type = tlsModule;
default = { };
description = ''
Configuration for secure connections.
'';
};
};
};
jmapModule = types.submodule {
options = {
host = mkOption {
type = types.nullOr types.str;
default = null;
example = "jmap.example.org";
description = ''
Hostname of JMAP server.
</para><para>
If both this option and <xref
linkend="opt-accounts.email.accounts._name_.jmap.sessionUrl"/> are specified,
<code>host</code> is preferred by applications when establishing a
session.
'';
};
sessionUrl = mkOption {
type = types.nullOr types.str;
default = null;
example = "https://jmap.example.org:443/.well-known/jmap";
description = ''
URL for the JMAP Session resource.
</para><para>
If both this option and <xref
linkend="opt-accounts.email.accounts._name_.jmap.host"/> are specified,
<code>host</code> is preferred by applications when establishing a
session.
'';
};
};
};
smtpModule = types.submodule {
options = {
host = mkOption {
type = types.str;
example = "smtp.example.org";
description = ''
Hostname of SMTP server.
'';
};
port = mkOption {
type = types.nullOr types.port;
default = null;
example = 465;
description = ''
The port on which the SMTP server listens. If
<literal>null</literal> then the default port is used.
'';
};
tls = mkOption {
type = tlsModule;
default = { };
description = ''
Configuration for secure connections.
'';
};
};
};
maildirModule = types.submodule ({ config, ... }: {
options = {
path = mkOption {
type = types.str;
description = ''
Path to maildir directory where mail for this account is
stored. This is relative to the base maildir path.
'';
};
absPath = mkOption {
type = types.str;
default = "${cfg.maildirBasePath}/${config.path}";
description = ''
A convenience option whose value is the absolute path of
this maildir.
'';
};
};
});
mailAccountOpts = { name, config, ... }: {
options = {
name = mkOption {
type = types.str;
readOnly = true;
description = ''
Unique identifier of the account. This is set to the
attribute name of the account configuration.
'';
};
primary = mkOption {
type = types.bool;
default = false;
description = ''
Whether this is the primary account. Only one account may be
set as primary.
'';
};
flavor = mkOption {
type = types.enum [
"plain"
"gmail.com"
"runbox.com"
"fastmail.com"
"yandex.com"
"outlook.office365.com"
];
default = "plain";
description = ''
Some email providers have peculiar behavior that require
special treatment. This option is therefore intended to
indicate the nature of the provider.
</para><para>
When this indicates a specific provider then, for example,
the IMAP, SMTP, and JMAP server configuration may be set
automatically.
'';
};
address = mkOption {
type = types.strMatching ".*@.*";
example = "jane.doe@example.org";
description = "The email address of this account.";
};
aliases = mkOption {
type = types.listOf (types.strMatching ".*@.*");
default = [ ];
example = [ "webmaster@example.org" "admin@example.org" ];
description = "Alternative email addresses of this account.";
};
realName = mkOption {
type = types.str;
example = "Jane Doe";
description = "Name displayed when sending mails.";
};
userName = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
The server username of this account. This will be used as
the SMTP, IMAP, and JMAP user name.
'';
};
passwordCommand = mkOption {
type = types.nullOr (types.either types.str (types.listOf types.str));
default = null;
apply = p: if isString p then splitString " " p else p;
example = "secret-tool lookup email me@example.org";
description = ''
A command, which when run writes the account password on
standard output.
'';
};
folders = mkOption {
type = types.submodule {
options = {
inbox = mkOption {
type = types.str;
default = "Inbox";
description = ''
Relative path of the inbox mail.
'';
};
sent = mkOption {
type = types.nullOr types.str;
default = "Sent";
description = ''
Relative path of the sent mail folder.
'';
};
drafts = mkOption {
type = types.nullOr types.str;
default = "Drafts";
description = ''
Relative path of the drafts mail folder.
'';
};
trash = mkOption {
type = types.str;
default = "Trash";
description = ''
Relative path of the deleted mail folder.
'';
};
};
};
default = { };
description = ''
Standard email folders.
'';
};
imap = mkOption {
type = types.nullOr imapModule;
default = null;
description = ''
The IMAP configuration to use for this account.
'';
};
jmap = mkOption {
type = types.nullOr jmapModule;
default = null;
description = ''
The JMAP configuration to use for this account.
'';
};
signature = mkOption {
type = signatureModule;
default = { };
description = ''
Signature configuration.
'';
};
gpg = mkOption {
type = types.nullOr gpgModule;
default = null;
description = ''
GPG configuration.
'';
};
smtp = mkOption {
type = types.nullOr smtpModule;
default = null;
description = ''
The SMTP configuration to use for this account.
'';
};
maildir = mkOption {
type = types.nullOr maildirModule;
defaultText = { path = "\${name}"; };
description = ''
Maildir configuration for this account.
'';
};
};
config = mkMerge [
{
name = name;
maildir = mkOptionDefault { path = "${name}"; };
}
(mkIf (config.flavor == "yandex.com") {
userName = mkDefault config.address;
imap = {
host = "imap.yandex.com";
port = 993;
tls.enable = true;
};
smtp = {
host = "smtp.yandex.com";
port = 465;
tls.enable = true;
};
})
(mkIf (config.flavor == "outlook.office365.com") {
userName = mkDefault config.address;
imap = {
host = "outlook.office365.com";
port = 993;
tls.enable = true;
};
smtp = {
host = "smtp.office365.com";
port = 587;
tls = {
enable = true;
useStartTls = true;
};
};
})
(mkIf (config.flavor == "fastmail.com") {
userName = mkDefault config.address;
imap = {
host = "imap.fastmail.com";
port = 993;
};
smtp = {
host = "smtp.fastmail.com";
port = if config.smtp.tls.useStartTls then 587 else 465;
};
jmap = {
host = "fastmail.com";
sessionUrl = "https://jmap.fastmail.com/.well-known/jmap";
};
})
(mkIf (config.flavor == "gmail.com") {
userName = mkDefault config.address;
imap = {
host = "imap.gmail.com";
port = 993;
};
smtp = {
host = "smtp.gmail.com";
port = if config.smtp.tls.useStartTls then 587 else 465;
};
})
(mkIf (config.flavor == "runbox.com") {
imap = { host = "mail.runbox.com"; };
smtp = { host = "mail.runbox.com"; };
})
];
};
in {
options.accounts.email = {
certificatesFile = mkOption {
type = types.nullOr types.path;
default = "/etc/ssl/certs/ca-certificates.crt";
description = ''
Path to default file containing certificate authorities that
should be used to validate the connection authenticity. This
path may be overridden on a per-account basis.
'';
};
maildirBasePath = mkOption {
type = types.str;
default = "${config.home.homeDirectory}/Maildir";
defaultText = "$HOME/Maildir";
apply = p:
if hasPrefix "/" p then p else "${config.home.homeDirectory}/${p}";
description = ''
The base directory for account maildir directories. May be a
relative path, in which case it is relative the home
directory.
'';
};
accounts = mkOption {
type = types.attrsOf (types.submodule mailAccountOpts);
default = { };
description = "List of email accounts.";
};
};
config = mkIf (cfg.accounts != { }) {
assertions = [
(let
primaries =
catAttrs "name" (filter (a: a.primary) (attrValues cfg.accounts));
in {
assertion = length primaries == 1;
message = "Must have exactly one primary mail account but found "
+ toString (length primaries) + optionalString (length primaries > 1)
(", namely " + concatStringsSep ", " primaries);
})
];
};
}

56
home-manager/email.nix Normal file
View File

@ -0,0 +1,56 @@
{ lib, pkgs, options, config, ... }:
{
imports = [./accounts-email.nix];
disabledModules = ["accounts/email.nix"];
config = {
programs.neomutt = {
enable = true;
settings = {
auto_tag = "yes";
crypt_reply_sign = "yes";
delete = "ask-yes";
imap_passive = "no";
mail_check = "60";
sort_aux = "reverse-last-date-received";
sort_browser = "date";
edit_headers = "yes";
implicit_autoview = "no";
};
binds = [
{ map = "index"; key = "G"; action = "imap-fetch-mail"; }
{ map = "pager"; key = "<up>"; action = "previous-line"; }
{ map = "pager"; key = "<down>"; action = "next-line"; }
];
extraConfig = "source ${./mutt-colors}";
};
xdg.configFile."neomutt/neomuttrc".text = lib.mkBefore ''
set imap_user = "yorick@yori.cc"
set imap_pass = "`pass sysadmin/yori.ccMail | head -n1`"
'';
accounts.email.accounts = {
yori-cc = rec {
primary = true;
userName = "yorick@yori.cc";
passwordCommand = "pass sysadmin/yori.ccMail | head -n1";
realName = "Yorick van Pelt";
address = "Yorick van Pelt <yorick@yorickvanpelt.nl>";
imap.host = "pennyworth.yori.cc";
smtp.host = "pennyworth.yori.cc";
gpg.key = "6EFD1053ADB6ABF50DF64792A36E70F9DC014A15";
neomutt.enable = true;
neomutt.extraMailboxes = [ "Archive" "Sent" "Spam" "Trash" ];
neomutt.extraConfig = ''
set pgp_sign_as = "${gpg.key}"
'';
maildir.absPath = "imaps://pennyworth.yori.cc";
folders = {
inbox = "INBOX";
trash = "Archive";
};
};
};
};
}

View File

@ -7,10 +7,9 @@ let
} "${pkgs.thefuck}/bin/thefuck -a > $out";
headphones = "88:C9:E8:AD:73:E8";
in {
imports = [ ./desktop.nix ./emacs.nix ./lumi.nix ];
imports = [ ./desktop.nix ./emacs.nix ./lumi.nix ./email.nix ];
programs = {
nix-index.enable = true;
# todo: fish tide
# todo: .aws/config default region
gh = {
enable = true;
@ -110,6 +109,9 @@ in {
source ${thefuck-alias "fish"}
source ~/dotfiles/nr.fish
'';
plugins = [
{ inherit (pkgs.fishPlugins.tide) name src; }
];
};
bash = {
enable = true;
@ -170,7 +172,7 @@ in {
borgbackup
bup
# catdoc
# todo (upgrade): trurl
trurl
expect
fzf
fx
@ -266,7 +268,6 @@ in {
gimp
gopass
hledger
neomutt
spotify
tdesktop
signal-desktop
@ -295,10 +296,6 @@ in {
keyserver hkps://keys.openpgp.org
#keyserver-options auto-key-retrieve
'';
home.file.".mutt" = {
source = ../mutt/.mutt;
recursive = true;
};
home.sessionVariables = {
FLAKE_CONFIG_URI = "/home/yorick/dotfiles#homeConfigurations.${pkgs.stdenv.system}.activationPackage";
};

View File

@ -1,2 +0,0 @@
msg_cache/
hcache

View File

@ -1,9 +0,0 @@
set imap_user=yorick@yori.cc
set imap_pass = "`pass sysadmin/yori.ccMail | head -n1`"
set folder = "imaps://yorick@yori.cc@pennyworth.yori.cc/"
set spoolfile = "=INBOX"
set realname = "Yorick van Pelt"
set from = "Yorick van Pelt <yorick@yorickvanpelt.nl>"
set smtp_url = "smtp://yorick@yori.cc@pennyworth.yori.cc:587/"
set smtp_pass = "$imap_pass"

View File

@ -1,88 +0,0 @@
# -*-muttrc-*-
#
# Command formats for gpg.
#
# This version uses gpg-2comp from
# http://70t.de/download/gpg-2comp.tar.gz
#
# $Id$
#
# %p The empty string when no passphrase is needed,
# the string "PGPPASSFD=0" if one is needed.
#
# This is mostly used in conditional % sequences.
#
# %f Most PGP commands operate on a single file or a file
# containing a message. %f expands to this file's name.
#
# %s When verifying signatures, there is another temporary file
# containing the detached signature. %s expands to this
# file's name.
#
# %a In "signing" contexts, this expands to the value of the
# configuration variable $pgp_sign_as. You probably need to
# use this within a conditional % sequence.
#
# %r In many contexts, mutt passes key IDs to pgp. %r expands to
# a list of key IDs.
# Note that we explicitly set the comment armor header since GnuPG, when used
# in some localiaztion environments, generates 8bit data in that header, thereby
# breaking PGP/MIME.
# decode application/pgp
set pgp_decode_command="gpg --status-fd=2 %?p?--passphrase-fd 0? --no-verbose --quiet --batch --output - %f"
# verify a pgp/mime signature
set pgp_verify_command="gpg --status-fd=2 --no-verbose --quiet --batch --output - --verify %s %f"
# decrypt a pgp/mime attachment
set pgp_decrypt_command="gpg --status-fd=2 %?p?--passphrase-fd 0? --no-verbose --quiet --batch --output - %f"
# create a pgp/mime signed attachment
# set pgp_sign_command="gpg-2comp --comment '' --no-verbose --batch --output - %?p?--passphrase-fd 0? --armor --detach-sign --textmode %?a?-u %a? %f"
set pgp_sign_command="gpg --no-verbose --batch --quiet --output - %?p?--passphrase-fd 0? --armor --detach-sign --textmode %?a?-u %a? %f"
# create a application/pgp signed (old-style) message
# set pgp_clearsign_command="gpg-2comp --comment '' --no-verbose --batch --output - %?p?--passphrase-fd 0? --armor --textmode --clearsign %?a?-u %a? %f"
set pgp_clearsign_command="gpg --no-verbose --batch --quiet --output - %?p?--passphrase-fd 0? --armor --textmode --clearsign %?a?-u %a? %f"
# create a pgp/mime encrypted attachment
# set pgp_encrypt_only_command="pgpewrap gpg-2comp -v --batch --output - --encrypt --textmode --armor --always-trust -- -r %r -- %f"
set pgp_encrypt_only_command="pgpewrap gpg --batch --quiet --no-verbose --output - --encrypt --textmode --armor --always-trust -- -r %r -- %f"
# create a pgp/mime encrypted and signed attachment
# set pgp_encrypt_sign_command="pgpewrap gpg-2comp %?p?--passphrase-fd 0? -v --batch --output - --encrypt --sign %?a?-u %a? --armor --always-trust -- -r %r -- %f"
set pgp_encrypt_sign_command="pgpewrap gpg %?p?--passphrase-fd 0? --batch --quiet --no-verbose --textmode --output - --encrypt --sign %?a?-u %a? --armor --always-trust -- -r %r -- %f"
# import a key into the public key ring
set pgp_import_command="gpg --no-verbose --import %f"
# export a key from the public key ring
set pgp_export_command="gpg --no-verbose --export --armor %r"
# verify a key
set pgp_verify_key_command="gpg --verbose --batch --fingerprint --check-sigs %r"
# read in the public key ring
set pgp_list_pubring_command="gpg --no-verbose --batch --quiet --with-colons --with-fingerprint --with-fingerprint --list-keys %r"
# read in the secret key ring
set pgp_list_secring_command="gpg --no-verbose --batch --quiet --with-colons --with-fingerprint --with-fingerprint --list-secret-keys %r"
# fetch keys
# set pgp_getkeys_command="pkspxycwrap %r"
# pattern for good signature - may need to be adapted to locale!
# set pgp_good_sign="^gpgv?: Good signature from "
# OK, here's a version which uses gnupg's message catalog:
# set pgp_good_sign="`gettext -d gnupg -s 'Good signature from "' | tr -d '"'`"
# This version uses --status-fd messages
set pgp_good_sign="^\\[GNUPG:\\] GOODSIG"
# pattern to verify a decryption occurred
set pgp_decryption_okay="^\\[GNUPG:\\] DECRYPTION_OKAY"

View File

@ -1,4 +0,0 @@
set record="=Sent"
set postponed="=Drafts"
set trash="=Archive"
mailboxes =INBOX =Archive =Sent =Spam =Trash

View File

@ -1,4 +0,0 @@
text/html; w3m -I %{charset} -T text/html; copiousoutput;
application/pdf; pdftotext -layout -l 10 -nopgbrk /dev/stdin -; copiousoutput
content/pdf; pdftotext -layout -l 10 -nopgbrk /dev/stdin -; copiousoutput

View File

@ -1,38 +0,0 @@
source ~/.mutt/creds
source ~/.mutt/colors
source ~/.mutt/gpg
source ~/.mutt/mailboxes
set edit_headers
set auto_tag
bind pager <up> previous-line
bind pager <down> next-line
set smart_wrap
set sleep_time = 0 # gotta go faster
set imap_servernoise
unset imap_passive
set mail_check = 60
set header_cache = ~/.mutt/hcache
set message_cachedir = ~/.mutt/msg_cache
set net_inc = 5
set sort = threads
set sort_browser = date
set sort_aux = reverse-last-date-received
set sidebar_width= 10
set pgp_verify_sig
set pgp_replysign
set pgp_sign_as = DC014A15
ifdef ENCRYPT_SELF set pgp_encrypt_self
set pgp_use_gpg_agent
bind index G imap-fetch-mail
auto_view text/html # view html automatically
alternative_order text/plain text/enriched text/html # save html for last
set mailcap_path=~/.mutt/mailcap