This repository has no description
11 kB
277 lines
1{ self, ... }:
2{
3 perSystem =
4 {
5 pkgs,
6 lib,
7 system,
8 ...
9 }:
10 {
11 packages.mutt =
12 let
13 # Mailboxes configuration from mail.nix
14 mailboxes = self.packages.${system}.notmuch.mailboxes;
15 notmuchConfig = self.packages.${system}.notmuch.passthru.config.configFile.path;
16
17 mailcap = pkgs.writeText "mailcap" ''
18 text/html; ${pkgs.elinks}/bin/elinks -dump ; copiousoutput;
19 '';
20
21 # Script to view HTML emails in browser with embedded images
22 viewHtmlBrowser = pkgs.writeShellScriptBin "view-html-browser" ''
23 set -euo pipefail
24 tmpfile=$(mktemp --suffix=.html)
25 trap 'rm -f "$tmpfile"' EXIT
26
27 ${pkgs.python3}/bin/python3 -c '
28 import email, sys, base64
29
30 msg = email.message_from_file(sys.stdin)
31
32 # Build map of Content-ID -> base64 data URI
33 cid_map = {}
34 for part in msg.walk():
35 cid = part.get("Content-ID", "").strip("<>")
36 if cid and part.get_content_maintype() == "image":
37 data = part.get_payload(decode=True)
38 if data:
39 mime = part.get_content_type()
40 cid_map[cid] = f"data:{mime};base64,{base64.b64encode(data).decode()}"
41
42 # Find HTML part
43 html = None
44 for part in msg.walk():
45 if part.get_content_type() == "text/html":
46 payload = part.get_payload(decode=True)
47 charset = part.get_content_charset() or "utf-8"
48 html = payload.decode(charset, errors="replace")
49 break
50
51 if html:
52 for cid, data_uri in cid_map.items():
53 html = html.replace(f"cid:{cid}", data_uri)
54 print(html)
55 else:
56 print("<html><body><p>No HTML content found.</p></body></html>")
57 ' > "$tmpfile"
58
59 ${if pkgs.stdenv.isDarwin then "open" else "xdg-open"} "$tmpfile"
60 sleep 2
61 '';
62
63 msmtprc = pkgs.writeText "msmtprc" ''
64 defaults
65 logfile ~/.msmtp.log
66 account prism
67 host neoprism.r
68 account c-base
69 from lassulus@c-base.org
70 host c-mail.c-base.org
71 port 465
72 tls on
73 tls_starttls off
74 auth on
75 user lassulus
76 passwordeval pass show c-base/pass
77 account default: prism
78 '';
79
80 msmtp = pkgs.writeShellScriptBin "msmtp" ''
81 export NOTMUCH_CONFIG="${notmuchConfig}"
82
83 # Check if using c-base account (either explicit -a c-base or via From: header)
84 using_cbase=0
85 for arg in "$@"; do
86 if [[ "$arg" == "c-base" ]] || [[ "$arg" =~ @c-base\.org ]]; then
87 using_cbase=1
88 break
89 fi
90 done
91
92 # For c-base account, always send directly
93 if [ "$using_cbase" -eq 1 ]; then
94 ${pkgs.coreutils}/bin/tee >(${pkgs.notmuch}/bin/notmuch insert +sent) | \
95 ${pkgs.msmtp}/bin/msmtp -C ${msmtprc} "$@"
96 # For default account, try neoprism.r first, then SSH
97 elif ping -W2 -c1 neoprism.r >/dev/null 2>&1; then
98 ${pkgs.coreutils}/bin/tee >(${pkgs.notmuch}/bin/notmuch insert +sent) | \
99 ${pkgs.msmtp}/bin/msmtp -C ${msmtprc} "$@"
100 else
101 # Fall back to SSH tunnel to neoprism, submit via SMTP to local daemon
102 echo "neoprism.r not reachable, sending via SSH..." >&2
103 ${pkgs.coreutils}/bin/tee >(${pkgs.notmuch}/bin/notmuch insert +sent) | \
104 ${pkgs.openssh}/bin/ssh -p 45621 neoprism.lassul.us 'msmtp -C /dev/null --host=localhost --port=25 --read-envelope-from --read-recipients'
105 fi
106 '';
107
108 muttrc = pkgs.writeText "muttrc" ''
109 # read html mails
110 auto_view text/html
111 set mailcap_path = ${mailcap}
112
113 # notmuch
114 set folder="$HOME/Maildir"
115 set nm_default_uri = "notmuch://$HOME/Maildir"
116 set nm_record = yes
117 set nm_record_tags = "-inbox me archive"
118 set spoolfile = +Inbox
119 set virtual_spoolfile = yes
120
121 set sendmail="${msmtp}/bin/msmtp"
122 set from="lassulus@lassul.us"
123 alternates ^.*@lassul\.us$ ^.*@.*\.r$
124 unset envelope_from_address
125 set use_envelope_from
126 set reverse_name
127 set markers=no
128
129 set sort=threads
130
131 set index_format="%4C %Z %?GI?%GI& ? %[%y-%m-%d] %-20.20a %?M?(%3M)& ? %s %> %r %g"
132
133 virtual-mailboxes "Unread" "notmuch://?query=tag:unread"
134 virtual-mailboxes "INBOX" "notmuch://?query=tag:inbox"
135 ${lib.concatMapStringsSep "\n" (
136 i: ''${" "}virtual-mailboxes "${i.name}" "notmuch://?query=tag:${i.name}"''
137 ) (lib.mapAttrsToList lib.nameValuePair mailboxes)}
138 virtual-mailboxes "TODO" "notmuch://?query=tag:TODO"
139 virtual-mailboxes "Starred" "notmuch://?query=tag:*"
140 virtual-mailboxes "Archive" "notmuch://?query=tag:archive"
141 virtual-mailboxes "Sent" "notmuch://?query=tag:sent"
142 virtual-mailboxes "Junk" "notmuch://?query=tag:junk"
143 virtual-mailboxes "All" "notmuch://?query=*"
144
145 # clan.lol IMAP - no mailboxes declaration to prevent any startup connection
146 # Select the separator entry in sidebar, folder-hook will redirect to the IMAP server
147 virtual-mailboxes "clan-infra" "notmuch://?query=tag:nonexistent-separator-tag"
148 account-hook imaps://mail.clan.lol "set imap_user = 'infra@clan.lol'; set imap_pass = \"\`SOPS_AGE_KEY=\$(rbw get --folder clan.lol clan-infra --field age-key) nix develop git+https://git.clan.lol/clan/clan-infra -c clan vars get --flake git+https://git.clan.lol/clan/clan-infra web01 infra-mail/infra-password\`\""
149 folder-hook "notmuch://\\?query=tag:nonexistent-separator-tag" "push '<change-folder>imaps://mail.clan.lol<enter>'"
150
151 tag-transforms "junk" "k" \
152 "unread" "u" \
153 "replied" "↻" \
154 "TODO" "T" \
155
156 # notmuch bindings
157 macro index \\\\ "<vfolder-from-query>"
158 macro index + "<modify-labels>+*\n<sync-mailbox>"
159 macro index - "<modify-labels>-*\n<sync-mailbox>"
160
161 # muchsync
162 bind index \Cr noop
163 macro index \Cr \
164 "<enter-command>unset wait_key<enter> \
165 <sync-mailbox> \
166 <shell-escape>${self.packages.${pkgs.system}.muchsync}/bin/muchsync -vv<enter> \
167 <sync-mailbox>
168
169 #killed
170 bind index d noop
171 bind pager d noop
172
173 bind index S noop
174 bind index s noop
175 bind pager S noop
176 bind pager s noop
177 macro index S "<modify-labels-then-hide>-inbox -unread +junk\n"
178 macro index s "<modify-labels>-junk\n"
179 macro pager S "<modify-labels-then-hide>-inbox -unread +junk\n"
180 macro pager s "<modify-labels>-junk\n"
181
182 bind index A noop
183 bind index a noop
184 bind pager A noop
185 bind pager a noop
186 macro index A "<modify-labels>+archive -unread -inbox\n"
187 macro index a "<modify-labels>-archive\n"
188 macro pager A "<modify-labels>+archive -unread -inbox\n"
189 macro pager a "<modify-labels>-archive\n"
190
191 bind index U noop
192 bind index u noop
193 bind pager U noop
194 bind pager u noop
195 macro index U "<modify-labels>+unread\n"
196 macro index u "<modify-labels>-unread\n"
197 macro pager U "<modify-labels>+unread\n"
198 macro pager u "<modify-labels>-unread\n"
199
200 bind index t noop
201 bind pager t noop
202 macro index t "<modify-labels>"
203
204 # top index bar in email view
205 set pager_index_lines=7
206 # top_index_bar toggle
207 macro pager ,@1 "<enter-command> set pager_index_lines=0; macro pager ] ,@2 'Toggle indexbar<Enter>"
208 macro pager ,@2 "<enter-command> set pager_index_lines=3; macro pager ] ,@3 'Toggle indexbar<Enter>"
209 macro pager ,@3 "<enter-command> set pager_index_lines=7; macro pager ] ,@1 'Toggle indexbar<Enter>"
210 macro pager ] ,@1 'Toggle indexbar
211
212 # scan urls from emails
213 macro index,pager \cb "<pipe-message> ${lib.getExe pkgs.urlscan}<Enter>"
214 macro attach,compose \cb "<pipe-entry> ${lib.getExe pkgs.urlscan}<Enter>"
215
216 # view HTML in graphical browser
217 macro index,pager H "<pipe-message>${viewHtmlBrowser}/bin/view-html-browser<Enter>" "View HTML in browser"
218
219 # sidebar
220 set sidebar_divider_char = '│'
221 set sidebar_delim_chars = "/"
222 set sidebar_short_path
223 set sidebar_folder_indent
224 set sidebar_visible = yes
225 set sidebar_format = '%D%?F? [%F]?%* %?N?%N/? %?S?%S?'
226 set sidebar_width = 20
227 color sidebar_new yellow red
228
229 # sidebar bindings
230 bind index <left> sidebar-prev
231 bind index <right> sidebar-next
232 bind index <space> sidebar-open
233 # sidebar toggle
234 macro index,pager ,@) "<enter-command> set sidebar_visible=no; macro index,pager [ ,@( 'Toggle sidebar'<Enter>"
235 macro index,pager ,@( "<enter-command> set sidebar_visible=yes; macro index,pager [ ,@) 'Toggle sidebar'<Enter>"
236 macro index,pager [ ,@( 'Toggle sidebar'
237
238 # forward with attachment
239 set mime_forward = no
240 set forward_attachments = yes
241
242 # better handling of long URLs
243 set wrap = 0 # don't wrap lines in the pager
244 set markers = no # don't show + markers for wrapped lines
245 set smart_wrap = yes # wrap at word boundaries when displaying
246 set pager_stop = yes # don't jump to next message at end of pager
247 '';
248
249 in
250 (self.wrapLib.makeWrapper {
251 pkgs = pkgs;
252 package = pkgs.neomutt;
253 aliases = [ "mutt" ];
254 runtimeInputs = [
255 pkgs.elinks
256 pkgs.msmtp
257 pkgs.notmuch
258 pkgs.openssh
259 pkgs.urlscan
260 ]
261 ++ lib.optionals pkgs.stdenv.isLinux [
262 pkgs.iputils
263 ];
264 env = {
265 NOTMUCH_CONFIG = notmuchConfig;
266 };
267 flags = {
268 "-F" = "${muttrc}";
269 };
270 }).overrideAttrs
271 (old: {
272 passthru = (old.passthru or { }) // {
273 inherit msmtp;
274 };
275 });
276 };
277}