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 332 lines
1{ self, inputs, ... }: 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.passthru.mailboxes; 15 notmuchConfig = self.packages.${system}.notmuch.passthru.configuration.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 timeout 10 67 account prism 68 host neoprism.r 69 account c-base 70 from lassulus@c-base.org 71 host c-mail.c-base.org 72 port 465 73 tls on 74 tls_starttls off 75 auth on 76 user lassulus 77 passwordeval rbw get --folder c-base login --field password 78 account clan-infra 79 from infra@clan.lol 80 host mail.clan.lol 81 port 587 82 tls on 83 tls_starttls on 84 auth on 85 user infra@clan.lol 86 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 87 account dedede 88 from mail@dedede.org 89 host mail.privateemail.com 90 port 587 91 tls on 92 tls_starttls on 93 auth on 94 user mail@dedede.org 95 passwordeval rbw get --folder dedede mail@dedede.org 96 account default: prism 97 ''; 98 99 msmtp = pkgs.writeShellScriptBin "msmtp" '' 100 export NOTMUCH_CONFIG="${notmuchConfig}" 101 102 # Save message to temporary file first 103 tmpfile=$(mktemp) 104 trap 'rm -f "$tmpfile"' EXIT 105 cat > "$tmpfile" 106 107 # Detect which account to use based on From: header in the email 108 account="" 109 from_line=$(grep -i "^From:" "$tmpfile" | head -1) 110 if [[ "$from_line" =~ @c-base\.org ]]; then 111 account="c-base" 112 elif [[ "$from_line" =~ @clan\.lol ]]; then 113 account="clan-infra" 114 elif [[ "$from_line" =~ @dedede\.org ]]; then 115 account="dedede" 116 fi 117 118 # For external accounts (c-base, clan-infra, dedede), always send directly 119 if [ -n "$account" ]; then 120 if ${pkgs.msmtp}/bin/msmtp -C ${msmtprc} -a "$account" "$@" < "$tmpfile"; then 121 ${pkgs.notmuch}/bin/notmuch insert +sent < "$tmpfile" 122 exit 0 123 else 124 exit 1 125 fi 126 fi 127 128 # For default account, try neoprism.r first, fallback to SSH 129 if ${pkgs.msmtp}/bin/msmtp -C ${msmtprc} "$@" < "$tmpfile" 2>/dev/null; then 130 ${pkgs.notmuch}/bin/notmuch insert +sent < "$tmpfile" 131 exit 0 132 fi 133 134 # If direct connection failed, fall back to SSH tunnel 135 echo "Direct connection failed, sending via SSH..." >&2 136 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 137 ${pkgs.notmuch}/bin/notmuch insert +sent < "$tmpfile" 138 exit 0 139 else 140 echo "Failed to send email" >&2 141 exit 1 142 fi 143 ''; 144 145 muttrc = pkgs.writeText "muttrc" '' 146 # read html mails 147 auto_view text/html 148 set mailcap_path = ${mailcap} 149 150 # notmuch 151 set folder="$HOME/Maildir" 152 set nm_default_uri = "notmuch://$HOME/Maildir" 153 set nm_record = yes 154 set nm_record_tags = "-inbox me archive" 155 set spoolfile = +Inbox 156 set virtual_spoolfile = yes 157 158 set sendmail="${msmtp}/bin/msmtp" 159 set from="lassulus@lassul.us" 160 alternates ^.*@lassul\.us$ ^lassulus@c-base\.org$ 161 unset envelope_from_address 162 set use_envelope_from 163 set reverse_name 164 set markers=no 165 166 # Folder hooks: set alternates per-mailbox so identities don't overlap 167 folder-hook . "unalternates *; alternates ^.*@lassul\\.us$ ^lassulus@c-base\\.org$; set from=lassulus@lassul.us" 168 folder-hook "imaps://.*mail\\.clan\\.lol" "unalternates *; alternates ^.*@clan\\.lol$; set from=infra@clan.lol" 169 folder-hook "imaps://.*mail\\.privateemail\\.com" "unalternates *; alternates ^.*@dedede\\.org$; set from=mail@dedede.org" 170 171 # Automatically use c-base email when sending/replying to c-base addresses 172 send-hook "." "unmy_hdr From" 173 send-hook "~C .*@c-base\.org" "my_hdr From: lassulus@c-base.org" 174 175 set sort=threads 176 177 set index_format="%4C %Z %?GI?%GI& ? %[%y-%m-%d] %-20.20a %?M?(%3M)& ? %s %> %r %g" 178 179 virtual-mailboxes "Unread" "notmuch://?query=tag:unread" 180 virtual-mailboxes "INBOX" "notmuch://?query=tag:inbox" 181 ${lib.concatMapStringsSep "\n" ( 182 i: ''${" "}virtual-mailboxes "${i.name}" "notmuch://?query=tag:${i.name}"'' 183 ) (lib.mapAttrsToList lib.nameValuePair mailboxes)} 184 virtual-mailboxes "TODO" "notmuch://?query=tag:TODO" 185 virtual-mailboxes "Starred" "notmuch://?query=tag:*" 186 virtual-mailboxes "Archive" "notmuch://?query=tag:archive" 187 virtual-mailboxes "Sent" "notmuch://?query=tag:sent" 188 virtual-mailboxes "Junk" "notmuch://?query=tag:junk" 189 virtual-mailboxes "All" "notmuch://?query=*" 190 191 # clan.lol IMAP - no mailboxes declaration to prevent any startup connection 192 # Select the separator entry in sidebar, folder-hook will redirect to the IMAP server 193 virtual-mailboxes "clan-infra" "notmuch://?query=tag:nonexistent-separator-tag" 194 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\`\"" 195 folder-hook "notmuch://\\?query=tag:nonexistent-separator-tag" "push '<change-folder>imaps://mail.clan.lol<enter>'" 196 197 # dedede.org IMAP (Namecheap Private Email) 198 virtual-mailboxes "dedede" "notmuch://?query=tag:nonexistent-dedede-tag" 199 account-hook imaps://mail.privateemail.com "set imap_user = 'mail@dedede.org'; set imap_pass = \"\`rbw get --folder dedede mail@dedede.org\`\"" 200 folder-hook "notmuch://\\?query=tag:nonexistent-dedede-tag" "push '<change-folder>imaps://mail.privateemail.com<enter>'" 201 202 tag-transforms "junk" "k" \ 203 "unread" "u" \ 204 "replied" "↻" \ 205 "TODO" "T" \ 206 207 # notmuch bindings 208 macro index \\\\ "<vfolder-from-query>" 209 macro index + "<modify-labels>+*\n<sync-mailbox>" 210 macro index - "<modify-labels>-*\n<sync-mailbox>" 211 212 # muchsync 213 bind index \Cr noop 214 macro index \Cr \ 215 "<enter-command>unset wait_key<enter> \ 216 <sync-mailbox> \ 217 <shell-escape>${self.packages.${pkgs.system}.muchsync}/bin/muchsync -vv<enter> \ 218 <sync-mailbox> 219 220 #killed 221 bind index d noop 222 bind pager d noop 223 224 bind index S noop 225 bind index s noop 226 bind pager S noop 227 bind pager s noop 228 macro index S "<modify-labels-then-hide>-inbox -unread +junk\n" 229 macro index s "<modify-labels>-junk\n" 230 macro pager S "<modify-labels-then-hide>-inbox -unread +junk\n" 231 macro pager s "<modify-labels>-junk\n" 232 233 bind index A noop 234 bind index a noop 235 bind pager A noop 236 bind pager a noop 237 macro index A "<modify-labels>+archive -unread -inbox\n" 238 macro index a "<modify-labels>-archive\n" 239 macro pager A "<modify-labels>+archive -unread -inbox\n" 240 macro pager a "<modify-labels>-archive\n" 241 242 bind index U noop 243 bind index u noop 244 bind pager U noop 245 bind pager u noop 246 macro index U "<modify-labels>+unread\n" 247 macro index u "<modify-labels>-unread\n" 248 macro pager U "<modify-labels>+unread\n" 249 macro pager u "<modify-labels>-unread\n" 250 251 bind index t noop 252 bind pager t noop 253 macro index t "<modify-labels>" 254 255 # top index bar in email view 256 set pager_index_lines=7 257 # top_index_bar toggle 258 macro pager ,@1 "<enter-command> set pager_index_lines=0; macro pager ] ,@2 'Toggle indexbar<Enter>" 259 macro pager ,@2 "<enter-command> set pager_index_lines=3; macro pager ] ,@3 'Toggle indexbar<Enter>" 260 macro pager ,@3 "<enter-command> set pager_index_lines=7; macro pager ] ,@1 'Toggle indexbar<Enter>" 261 macro pager ] ,@1 'Toggle indexbar 262 263 # scan urls from emails 264 macro index,pager \cb "<pipe-message> ${lib.getExe pkgs.urlscan}<Enter>" 265 macro attach,compose \cb "<pipe-entry> ${lib.getExe pkgs.urlscan}<Enter>" 266 267 # view HTML in graphical browser 268 macro index,pager H "<pipe-message>${viewHtmlBrowser}/bin/view-html-browser<Enter>" "View HTML in browser" 269 270 # forward invoice to sevdesk 271 send-hook "~t autobox@sevdesk.email" "my_hdr From: sevdesk@lassul.us" 272 macro index,pager I "<forward-message>autobox@sevdesk.email<enter>" "Forward invoice to sevdesk" 273 274 # sidebar 275 set sidebar_divider_char = '│' 276 set sidebar_delim_chars = "/" 277 set sidebar_short_path 278 set sidebar_folder_indent 279 set sidebar_visible = yes 280 set sidebar_format = '%D%?F? [%F]?%* %?N?%N/? %?S?%S?' 281 set sidebar_width = 20 282 color sidebar_new yellow red 283 284 # sidebar bindings 285 bind index <left> sidebar-prev 286 bind index <right> sidebar-next 287 bind index <space> sidebar-open 288 # sidebar toggle 289 macro index,pager ,@) "<enter-command> set sidebar_visible=no; macro index,pager [ ,@( 'Toggle sidebar'<Enter>" 290 macro index,pager ,@( "<enter-command> set sidebar_visible=yes; macro index,pager [ ,@) 'Toggle sidebar'<Enter>" 291 macro index,pager [ ,@( 'Toggle sidebar' 292 293 # forward with attachment 294 set mime_forward = no 295 set forward_attachments = yes 296 297 # better handling of long URLs 298 set wrap = 0 # don't wrap lines in the pager 299 set markers = no # don't show + markers for wrapped lines 300 set smart_wrap = yes # wrap at word boundaries when displaying 301 set pager_stop = yes # don't jump to next message at end of pager 302 ''; 303 304 in 305 (inputs.wrappers.lib.wrapPackage { 306 pkgs = pkgs; 307 package = pkgs.neomutt; 308 aliases = [ "mutt" ]; 309 runtimeInputs = [ 310 pkgs.elinks 311 pkgs.msmtp 312 pkgs.notmuch 313 pkgs.openssh 314 pkgs.urlscan 315 ] 316 ++ lib.optionals pkgs.stdenv.isLinux [ 317 pkgs.iputils 318 ]; 319 env = { 320 NOTMUCH_CONFIG = notmuchConfig; 321 }; 322 flags = { 323 "-F" = "${muttrc}"; 324 }; 325 }).overrideAttrs 326 (old: { 327 passthru = (old.passthru or { }) // { 328 inherit msmtp; 329 }; 330 }); 331 }; 332}