This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

superconfig / tools / mutt / flake-module.nix
15 kB 350 lines
1{ self, inputs, ... }: 2{ 3 perSystem = 4 { 5 pkgs, 6 lib, 7 system, 8 ... 9 }: 10 { 11 packages.mutt = 12 let 13 notmuchConfig = self.packages.${system}.notmuch.passthru.configuration.configFile.path; 14 15 # nixpkgs's autotools elinks is broken against gettext 0.23.1. 16 # Use our local meson build (pkgs/elinks) until upstream PR 17 # #515347 lands. 18 elinks = self.packages.${system}.elinks; 19 20 # Generate virtual-mailboxes from Maildir folder structure at build time 21 # This discovers sieve folders dynamically — no hardcoded list needed 22 genVirtualMailboxes = pkgs.writeShellScript "gen-virtual-mailboxes" '' 23 maildir="$HOME/Maildir" 24 [ -d "$maildir" ] || exit 0 25 for dir in "$maildir"/.*; do 26 [ -d "$dir/cur" ] || continue 27 dotname=$(basename "$dir") 28 # Display name without leading dot 29 name=''${dotname#.} 30 echo "virtual-mailboxes \"$name\" \"notmuch://?query=folder:$dotname\"" 31 done | sort 32 ''; 33 34 mailcap = pkgs.writeText "mailcap" '' 35 text/html; ${elinks}/bin/elinks -dump ; copiousoutput; 36 ''; 37 38 # Script to view HTML emails in browser with embedded images 39 viewHtmlBrowser = pkgs.writeShellScriptBin "view-html-browser" '' 40 set -euo pipefail 41 tmpfile=$(mktemp --suffix=.html) 42 trap 'rm -f "$tmpfile"' EXIT 43 44 ${pkgs.python3}/bin/python3 -c ' 45 import email, sys, base64 46 47 msg = email.message_from_file(sys.stdin) 48 49 # Build map of Content-ID -> base64 data URI 50 cid_map = {} 51 for part in msg.walk(): 52 cid = part.get("Content-ID", "").strip("<>") 53 if cid and part.get_content_maintype() == "image": 54 data = part.get_payload(decode=True) 55 if data: 56 mime = part.get_content_type() 57 cid_map[cid] = f"data:{mime};base64,{base64.b64encode(data).decode()}" 58 59 # Find HTML part 60 html = None 61 for part in msg.walk(): 62 if part.get_content_type() == "text/html": 63 payload = part.get_payload(decode=True) 64 charset = part.get_content_charset() or "utf-8" 65 html = payload.decode(charset, errors="replace") 66 break 67 68 if html: 69 for cid, data_uri in cid_map.items(): 70 html = html.replace(f"cid:{cid}", data_uri) 71 print(html) 72 else: 73 print("<html><body><p>No HTML content found.</p></body></html>") 74 ' > "$tmpfile" 75 76 ${if pkgs.stdenv.isDarwin then "open" else "xdg-open"} "$tmpfile" 77 sleep 2 78 ''; 79 80 msmtprc = pkgs.writeText "msmtprc" '' 81 defaults 82 logfile ~/.msmtp.log 83 timeout 10 84 account neoprism 85 host neoprism.r 86 account c-base 87 from lassulus@c-base.org 88 host c-mail.c-base.org 89 port 465 90 tls on 91 tls_starttls off 92 auth on 93 user lassulus 94 passwordeval rbw get --folder c-base shell.c-base.org --field password 95 account clan-infra 96 from infra@clan.lol 97 host mail.clan.lol 98 port 587 99 tls on 100 tls_starttls on 101 auth on 102 user infra@clan.lol 103 passwordeval 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 104 account dedede 105 from mail@dedede.org 106 host mail.privateemail.com 107 port 587 108 tls on 109 tls_starttls on 110 auth on 111 user mail@dedede.org 112 passwordeval rbw get --folder dedede mail@dedede.org 113 account default: neoprism 114 ''; 115 116 msmtp = pkgs.writeShellScriptBin "msmtp" '' 117 export NOTMUCH_CONFIG="${notmuchConfig}" 118 119 # Save message to temporary file first 120 tmpfile=$(mktemp) 121 trap 'rm -f "$tmpfile"' EXIT 122 cat > "$tmpfile" 123 124 # Detect which account to use based on From: header in the email 125 account="" 126 from_line=$(grep -i "^From:" "$tmpfile" | head -1) 127 if [[ "$from_line" =~ @c-base\.org ]]; then 128 account="c-base" 129 elif [[ "$from_line" =~ @clan\.lol ]]; then 130 account="clan-infra" 131 elif [[ "$from_line" =~ @dedede\.org ]]; then 132 account="dedede" 133 fi 134 135 # For external accounts (c-base, clan-infra, dedede), always send directly 136 if [ -n "$account" ]; then 137 if ${pkgs.msmtp}/bin/msmtp -C ${msmtprc} -a "$account" "$@" < "$tmpfile"; then 138 ${pkgs.notmuch}/bin/notmuch insert +sent < "$tmpfile" 139 exit 0 140 else 141 exit 1 142 fi 143 fi 144 145 # For default account, try neoprism.r first, fallback to SSH 146 if ${pkgs.msmtp}/bin/msmtp -C ${msmtprc} "$@" < "$tmpfile" 2>/dev/null; then 147 ${pkgs.notmuch}/bin/notmuch insert +sent < "$tmpfile" 148 exit 0 149 fi 150 151 # If direct connection failed, fall back to SSH tunnel 152 echo "Direct connection failed, sending via SSH..." >&2 153 if ${pkgs.openssh}/bin/ssh -o ConnectTimeout=10 -p 45621 lass@neoprism.lassul.us 'msmtp -C /dev/null --host=localhost --port=25 --read-envelope-from --read-recipients' < "$tmpfile"; then 154 ${pkgs.notmuch}/bin/notmuch insert +sent < "$tmpfile" 155 exit 0 156 else 157 echo "Failed to send email" >&2 158 exit 1 159 fi 160 ''; 161 162 muttrc = pkgs.writeText "muttrc" '' 163 # read html mails 164 auto_view text/html 165 set mailcap_path = ${mailcap} 166 167 # notmuch 168 set folder="$HOME/Maildir" 169 set nm_default_uri = "notmuch://$HOME/Maildir" 170 set nm_record = yes 171 set nm_record_tags = "-inbox me archive" 172 set virtual_spoolfile 173 set spoolfile = "notmuch://?query=folder:\"\" AND NOT tag:archive" 174 175 set mail_check_stats 176 set confirmappend = no 177 set confirmcreate = no 178 set mbox_type = Maildir 179 set delete = yes 180 set sendmail="${msmtp}/bin/msmtp" 181 set from="lassulus@lassul.us" 182 alternates ^.*@lassul\.us$ ^lassulus@c-base\.org$ 183 unset envelope_from_address 184 set use_envelope_from 185 set reverse_name 186 set markers=no 187 188 # Folder hooks: set alternates per-mailbox so identities don't overlap 189 folder-hook . "unalternates *; alternates ^.*@lassul\\.us$ ^lassulus@c-base\\.org$; set from=lassulus@lassul.us" 190 folder-hook "imaps://.*mail\\.clan\\.lol" "unalternates *; alternates ^.*@clan\\.lol$; set from=infra@clan.lol" 191 folder-hook "imaps://.*mail\\.privateemail\\.com" "unalternates *; alternates ^.*@dedede\\.org$; set from=mail@dedede.org" 192 193 # Automatically use c-base email when sending/replying to c-base addresses 194 send-hook "." "unmy_hdr From" 195 send-hook "~C .*@c-base\.org" "my_hdr From: lassulus@c-base.org" 196 197 set sort=threads 198 199 set index_format="%4C %Z %?GI?%GI& ? %[%y-%m-%d] %-20.20a %?M?(%3M)& ? %s %> %r %g" 200 201 virtual-mailboxes "INBOX" "notmuch://?query=folder:\"\" AND NOT tag:archive" 202 virtual-mailboxes "Unread" "notmuch://?query=tag:unread" 203 source "${genVirtualMailboxes}|" 204 virtual-mailboxes "TODO" "notmuch://?query=tag:TODO" 205 virtual-mailboxes "Starred" "notmuch://?query=tag:*" 206 virtual-mailboxes "Archive" "notmuch://?query=tag:archive" 207 virtual-mailboxes "Sent" "notmuch://?query=tag:sent" 208 virtual-mailboxes "Junk" "notmuch://?query=tag:junk" 209 virtual-mailboxes "All" "notmuch://?query=*" 210 211 # clan.lol IMAP - no mailboxes declaration to prevent any startup connection 212 # Select the separator entry in sidebar, folder-hook will redirect to the IMAP server 213 virtual-mailboxes "clan-infra" "notmuch://?query=tag:nonexistent-separator-tag" 214 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\`\"" 215 folder-hook "notmuch://\\?query=tag:nonexistent-separator-tag" "push '<change-folder>imaps://mail.clan.lol<enter>'" 216 217 # dedede.org IMAP (Namecheap Private Email) 218 virtual-mailboxes "dedede" "notmuch://?query=tag:nonexistent-dedede-tag" 219 account-hook imaps://mail.privateemail.com "set imap_user = 'mail@dedede.org'; set imap_pass = \"\`rbw get --folder dedede mail@dedede.org\`\"" 220 folder-hook "notmuch://\\?query=tag:nonexistent-dedede-tag" "push '<change-folder>imaps://mail.privateemail.com<enter>'" 221 222 tag-transforms "junk" "k" \ 223 "unread" "u" \ 224 "replied" "↻" \ 225 "TODO" "T" \ 226 227 # notmuch bindings 228 macro index \\\\ "<vfolder-from-query>" 229 macro index + "<modify-labels>+*\n<sync-mailbox>" 230 macro index - "<modify-labels>-*\n<sync-mailbox>" 231 232 # muchsync 233 bind index \Cr noop 234 macro index \Cr \ 235 "<enter-command>unset wait_key<enter> \ 236 <sync-mailbox> \ 237 <shell-escape>${self.packages.${pkgs.system}.muchsync}/bin/muchsync -vv<enter> \ 238 <sync-mailbox> 239 240 #killed 241 bind index d noop 242 bind pager d noop 243 244 bind index S noop 245 bind index s noop 246 bind pager S noop 247 bind pager s noop 248 macro index S "<modify-labels-then-hide>-inbox -unread +junk\n" 249 macro index s "<modify-labels>-junk\n" 250 macro pager S "<modify-labels-then-hide>-inbox -unread +junk\n" 251 macro pager s "<modify-labels>-junk\n" 252 253 bind index A noop 254 bind index a noop 255 bind pager A noop 256 bind pager a noop 257 macro index A "<modify-labels>+archive -unread -inbox\n" "Archive message" 258 macro pager A "<modify-labels>+archive -unread -inbox\n" "Archive message" 259 260 bind index U noop 261 bind index u noop 262 bind pager U noop 263 bind pager u noop 264 macro index U "<modify-labels>+unread\n" 265 macro index u "<modify-labels>-unread\n" 266 macro pager U "<modify-labels>+unread\n" 267 macro pager u "<modify-labels>-unread\n" 268 269 bind index t noop 270 bind pager t noop 271 macro index t "<modify-labels>" 272 273 # top index bar in email view 274 set pager_index_lines=7 275 # top_index_bar toggle 276 macro pager ,@1 "<enter-command> set pager_index_lines=0; macro pager ] ,@2 'Toggle indexbar<Enter>" 277 macro pager ,@2 "<enter-command> set pager_index_lines=3; macro pager ] ,@3 'Toggle indexbar<Enter>" 278 macro pager ,@3 "<enter-command> set pager_index_lines=7; macro pager ] ,@1 'Toggle indexbar<Enter>" 279 macro pager ] ,@1 'Toggle indexbar 280 281 # scan urls from emails 282 macro index,pager \cb "<pipe-message> ${lib.getExe pkgs.urlscan}<Enter>" 283 macro attach,compose \cb "<pipe-entry> ${lib.getExe pkgs.urlscan}<Enter>" 284 285 # view HTML in graphical browser 286 macro index,pager H "<pipe-message>${viewHtmlBrowser}/bin/view-html-browser<Enter>" "View HTML in browser" 287 288 # forward invoice to sevdesk 289 send-hook "~t autobox@sevdesk.email" "my_hdr From: sevdesk@lassul.us" 290 macro index,pager I "<forward-message>autobox@sevdesk.email<enter>" "Forward invoice to sevdesk" 291 292 # sidebar 293 set sidebar_divider_char = '│' 294 set sidebar_delim_chars = "/" 295 set sidebar_short_path 296 set sidebar_folder_indent 297 set sidebar_visible = yes 298 set sidebar_format = '%D%?F? [%F]?%* %?N?%N/? %?S?%S?' 299 set sidebar_width = 30 300 color sidebar_new yellow red 301 302 # sidebar bindings 303 bind index <left> sidebar-prev 304 bind index <right> sidebar-next 305 bind index <space> sidebar-open 306 # sidebar toggle 307 macro index,pager ,@) "<enter-command> set sidebar_visible=no; macro index,pager [ ,@( 'Toggle sidebar'<Enter>" 308 macro index,pager ,@( "<enter-command> set sidebar_visible=yes; macro index,pager [ ,@) 'Toggle sidebar'<Enter>" 309 macro index,pager [ ,@( 'Toggle sidebar' 310 311 # forward with attachment 312 set mime_forward = no 313 set forward_attachments = yes 314 315 # better handling of long URLs 316 set wrap = 0 # don't wrap lines in the pager 317 set markers = no # don't show + markers for wrapped lines 318 set smart_wrap = yes # wrap at word boundaries when displaying 319 set pager_stop = yes # don't jump to next message at end of pager 320 ''; 321 322 in 323 (inputs.wrappers.lib.wrapPackage { 324 pkgs = pkgs; 325 package = pkgs.neomutt; 326 aliases = [ "mutt" ]; 327 runtimeInputs = [ 328 elinks 329 pkgs.msmtp 330 pkgs.notmuch 331 pkgs.openssh 332 pkgs.urlscan 333 ] 334 ++ lib.optionals pkgs.stdenv.isLinux [ 335 pkgs.iputils 336 ]; 337 env = { 338 NOTMUCH_CONFIG = notmuchConfig; 339 }; 340 flags = { 341 "-F" = "${muttrc}"; 342 }; 343 }).overrideAttrs 344 (old: { 345 passthru = (old.passthru or { }) // { 346 inherit msmtp; 347 }; 348 }); 349 }; 350}