diff options
-rw-r--r-- | README.md | 6 | ||||
-rw-r--r-- | mbsync.rb | 76 | ||||
-rw-r--r-- | mmm.rb | 49 | ||||
-rw-r--r-- | msmtp.rb | 26 | ||||
-rw-r--r-- | mutt.rb | 77 |
5 files changed, 234 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..96bea7c --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# Mbsync, Mutt, Msmtp Minimal Mail Manager + +`mmm` aims to provide a unified, opinionated configuration for a CLI MUA setup +with `mbsync` (aka `isync`), `mutt` (or more precisely `neomutt`), and `msmtp`. + +YAML is used as the configuration language. diff --git a/mbsync.rb b/mbsync.rb new file mode 100644 index 0000000..511c924 --- /dev/null +++ b/mbsync.rb @@ -0,0 +1,76 @@ +class MbsyncAccountConfig + def initialize account, config + @config = config + @account = account + make_imap_account + make_imap_store + make_maildir_store + make_channel + end + + def make_imap_account + @imap_account = <<~END.chomp + IMAPAccount #{@account['name']} + Host #{@account['imap']} + User #{@account['email']} + Port 993 + SSLType IMAPS + PassCmd "pass show #{@account['pass_tag'] || @account['name']}" + CertificateFile /etc/ssl/certs/ca-certificates.crt + AuthMechs * + END + end + + def make_imap_store + @imap_store = <<~END.chomp + IMAPStore #{@account['name']}-remote + Account #{@account['name']} + END + end + + def make_maildir_store + @maildir_store = <<~END.chomp + MaildirStore #{@account['name']}-local + Path #{@config['mail_directory']}/#{@account['name']}/ + Inbox #{@config['mail_directory']}/#{@account['name']}/INBOX + Subfolders Verbatim + Flatten . + END + end + + def make_channel + @channel = <<~END.chomp + Channel #{@account['name']} + Master :#{@account['name']}-remote: + Slave :#{@account['name']}-local: + Patterns * !"[Gmail]/All Mail" !"[Gmail]/Wszystkie" + Create Slave + SyncState * + ExpireUnread yes + MaxMessages #{@account['max_messages'] || '0'} + END + end + + def to_s + <<~END.chomp + # BEGIN profile generated by mmm + #{@imap_account} + + #{@imap_store} + + #{@maildir_store} + + #{@channel} + # END profile generated by mmm + END + end +end + +def make_mbsync_config config + accounts = config['accounts'].map do |account_config| + account = MbsyncAccountConfig.new account_config, config + account.to_s + end + + accounts.join "\n\n" +end @@ -0,0 +1,49 @@ +require 'yaml' +require 'optparse' + +require './mbsync' +require './mutt' +require './msmtp' + +options = { + config: '~/.config/mmm/config.yaml', + write: false +} + +OptionParser.new do |opts| + opts.on '-w', '--write', 'Write configs to disk. Otherwise, just outputs to stdout.' + opts.on '-cCONFIG', '--config CONFIG', 'Specify configuration file, defaults to ~/.config/mmm/config.' +end.parse!(into: options) + +config = YAML.load File.read(File.expand_path options[:config]) + +def write_mbsync_config config, options + mbsync_config = make_mbsync_config config + (options[:write] ? File.open(File.expand_path(config['mbsyncrc']), 'w') : $stdout).puts mbsync_config +end + +def write_msmtp_config config, options + preamble = msmtprc_preamble + io = (options[:write] ? File.open(File.expand_path(config['msmtprc']), 'w') : $stdout) + io.puts preamble + io.puts + msmtp_config = make_msmtp_config config + io.puts msmtp_config +end + +def write_mutt_configs config, options + mutt_configs = make_mutt_configs config + profiles_directory = File.expand_path(config['neomutt_profiles_directory']) + mutt_configs.each do |account, account_config| + path = File.join(profiles_directory, "#{account}.neomuttrc") + (options[:write] ? File.open(path, 'w') : $stdout).puts account_config + end + + mmmrc = make_mmm_muttrc config + mmmrc_path = File.join(File.expand_path(config['neomutt_config_dir']), "mmm.neomuttrc") + (options[:write] ? File.open(mmmrc_path, 'w') : $stdout).puts mmmrc +end + +write_mbsync_config config, options +write_msmtp_config config, options +write_mutt_configs config, options diff --git a/msmtp.rb b/msmtp.rb new file mode 100644 index 0000000..383be87 --- /dev/null +++ b/msmtp.rb @@ -0,0 +1,26 @@ +def make_msmtp_config config + accounts = config['accounts'].map do |account| + <<~END.chomp + # BEGIN profile generated by mmm + account #{account['name']} + host #{account['smtp']} + port 587 + from #{account['email']} + user #{account['email']} + passwordeval "pass show #{account['pass_tag'] || account['name']}" + # END profile generated by mmm + END + end + + accounts.join "\n\n" +end + +def msmtprc_preamble + <<~END + defaults + auth on + tls on + tls_trust_file /etc/ssl/certs/ca-certificates.crt + logfile ~/.msmtp.log + END +end @@ -0,0 +1,77 @@ +def make_named_mailboxes account + mailboxes = account['mailboxes'].filter do |mailbox| + mailbox['alias'] + end + + if !mailboxes.empty? + string = mailboxes.map do |mailbox| + "#{mailbox['alias']} \"+#{mailbox['name']}\"" + end.join ' ' + + "named-mailboxes #{string}" + end +end + +def make_unnamed_mailboxes account + mailboxes = account['mailboxes'].filter do |mailbox| + !mailbox['alias'] + end + + if !mailboxes.empty? + string = mailboxes.map do |mailbox| + "\"+#{mailbox}\"" + end.join ' ' + + "mailboxes #{string}" + end +end + +def make_mutt_config account, config + named_mailboxes = make_named_mailboxes account + unnamed_mailboxes = make_unnamed_mailboxes account + mailboxes = "#{named_mailboxes}" + if !named_mailboxes + mailboxes = '' + elsif unnamed_mailboxes + mailboxes += "\n" + end + mailboxes += unnamed_mailboxes || '' + + <<~END.chomp + # File generated by mmm + set realname="#{account['real_name'] || config['real_name']}" + set from=#{account['email']} + set sendmail="msmtp -a #{account['name']}" + set folder=#{config['mail_directory']}/#{account['name']} + set spoolfile=+INBOX + set record="+#{account['record'] || 'Sent'}" + set postponed="+#{account['postponed'] || 'Drafts'}" + set header_cache="~/.cache/mmm/#{account['name']}/headers" + unmailboxes * + #{mailboxes} + END +end + +def make_mutt_activator account, i, config + "macro index i#{i} '<sync-mailbox><enter-command>source #{config['neomutt_profiles_directory']}/#{account['name']}.neomuttrc<enter><change-folder>!<enter>'" +end + +def make_mutt_configs config + configs = {} + config['accounts'].each do |account| + configs[account['name']] = make_mutt_config(account, config) + end + configs +end + +def make_mmm_muttrc config + activators = config['accounts'].each_with_index.map do |account, i| + make_mutt_activator account, i + 1, config + end.join "\n" + + <<~END + # File generated by mmm + bind index i noop + #{activators} + END +end |