m-chrzan.xyz
aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md6
-rw-r--r--mbsync.rb76
-rw-r--r--mmm.rb49
-rw-r--r--msmtp.rb26
-rw-r--r--mutt.rb77
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
diff --git a/mmm.rb b/mmm.rb
new file mode 100644
index 0000000..cb22517
--- /dev/null
+++ b/mmm.rb
@@ -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
diff --git a/mutt.rb b/mutt.rb
new file mode 100644
index 0000000..7b650b9
--- /dev/null
+++ b/mutt.rb
@@ -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