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