mutt-wizard

Autoconfigure a terminal email setup with mutt and offline mail
Log | Files | Refs | README | LICENSE

mw (16387B)


      1 #!/bin/sh
      2 
      3 command -V gpg >/dev/null 2>&1 && GPG="gpg" || GPG="gpg2"
      4 [ -z ${PASSWORD_STORE_DIR+x} ] && PASSWORD_STORE_DIR="$HOME/.password-store"
      5 [ -r "$PASSWORD_STORE_DIR/.gpg-id" ] &&
      6     "$GPG" --list-secret-keys $(cat "$PASSWORD_STORE_DIR/.gpg-id") >/dev/null 2>&1 || {
      7         printf "\`pass\` must be installed and initialized to encrypt passwords.\\nBe sure it is installed and run \`pass init <yourgpgemail>\`.\\nIf you don't have a GPG public private key pair, run \`%s --full-gen-key\` first.\\n" "$GPG"
      8         exit
      9     }
     10 ! command -v mbsync >/dev/null && printf "\`mbsync (isync package)\` must be installed to run mutt-wizard.\\n" && exit
     11 
     12 prefix="/usr/local"
     13 muttdir="$HOME/.config/mutt"		# Main mutt config location
     14 accdir="$muttdir/accounts"		# Directory for account settings
     15 maildir="$HOME/.local/share/mail"	# Location of mail storage
     16 namere="^[a-z_][a-z0-9_-]*$"		# Regex to ensure viable username
     17 emailre=".\+@.\+\\..\+" 		# Regex to confirm valid email address
     18 muttshare="$prefix/share/mutt-wizard"
     19 mbsyncrc="$HOME/.mbsyncrc"
     20 mwconfig="$muttshare/mutt-wizard.muttrc"
     21 cachedir="$HOME/.cache/mutt-wizard"
     22 muttrc="$muttdir/muttrc"
     23 msmtprc="$HOME/.config/msmtp/config"
     24 ssltype="IMAPS"				# This is later changed to `None` later in the script if using Protonmail
     25 
     26 for x in "/etc/ssl/certs/ca-certificates.crt" "/etc/pki/tls/certs/ca-bundle.crt" "/etc/ssl/ca-bundle.pem" "/etc/pki/tls/cacert.pem" "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem" "/etc/ssl/cert.pem" "/usr/local/share/ca-certificates/"
     27 do
     28 	[ -f "$x" ] && sslcert="$x" && break
     29 done || { echo "CA Certificate not found. Please install one or link it to /etc/ssl/certs/ca-certificates.crt" && exit 1 ;}
     30 
     31 getaccounts() { accounts="$(find "$accdir" -type f | grep -o "[0-9]-.*.muttrc" | sed "s/-/: /;s/\..*//" | sort -n)" ;}
     32 list() { getaccounts && [ -n "$accounts" ] && echo "$accounts" ;}
     33 
     34 getprofiles() { \
     35 	unset msmtp_header msmtp_profile mutt_profile mbsync_profile
     36 	printf "Creating profiles for \`%s\`..." "$title"
     37 msmtp_header="defaults
     38 auth	on
     39 tls	on
     40 tls_trust_file	$sslcert
     41 logfile	~/.config/msmtp/msmtp.log
     42 "
     43 msmtp_profile="account $title
     44 host $smtp
     45 port $sport
     46 from $fulladdr
     47 user $login
     48 passwordeval \"pass mutt-wizard-$title\"
     49 $starttlsoff
     50 "
     51 mbsync_profile="IMAPStore $title-remote
     52 Host $imap
     53 Port $iport
     54 User $login
     55 PassCmd \"pass mutt-wizard-$title\"
     56 AuthMechs LOGIN
     57 SSLType $ssltype
     58 CertificateFile $sslcert
     59 
     60 MaildirStore $title-local
     61 Subfolders Verbatim
     62 Path ~/.local/share/mail/$title/
     63 Inbox ~/.local/share/mail/$title/INBOX
     64 Flatten .
     65 
     66 Channel $title
     67 Expunge Both
     68 Master :$title-remote:
     69 Slave :$title-local:
     70 Patterns * !\"[Gmail]/All Mail\"
     71 Create Both
     72 SyncState *
     73 MaxMessages $maxmes
     74 ExpireUnread no
     75 # End profile
     76 "
     77 
     78 if [ "$accounttype" = "offline" ]; then
     79 mutt_profile="# vim: filetype=neomuttrc
     80 # muttrc file for account $title
     81 set realname = \"$realname\"
     82 set from = \"$fulladdr\"
     83 set sendmail = \"msmtp -a $title\"
     84 alias me $realname <$fulladdr>
     85 set folder = \"$maildir/$title\"
     86 set header_cache = $cachedir/$title/headers
     87 set message_cachedir = $cachedir/$title/bodies
     88 set mbox_type = Maildir
     89 
     90 set crypt_autosign = yes
     91 set crypt_opportunistic_encrypt = yes
     92 set pgp_self_encrypt = yes
     93 set pgp_default_key  = $keyid
     94 
     95 bind index,pager gg noop
     96 bind index,pager g noop
     97 bind index,pager M noop
     98 bind index,pager C noop
     99 bind index gg first-entry
    100 macro index o \"<shell-escape>mbsync -V $title<enter>\" \"run mbsync to sync $title\"
    101 unmailboxes *
    102 "
    103 else
    104 mutt_profile="# vim: filetype=neomuttrc
    105 # muttrc file for account $title
    106 set realname = \"$realname\"
    107 set from = \"$fulladdr\"
    108 set sendmail = \"msmtp -a $title\"
    109 alias me $realname <$fulladdr>
    110 set folder = \"imaps://$login@$imap:$iport\"
    111 set imap_user = \"$login\"
    112 set header_cache = $cachedir/$title/headers
    113 set message_cachedir = $cachedir/$title/bodies
    114 set imap_pass = \"\`pass mutt-wizard-$title\`\"
    115 
    116 set crypt_autosign = yes
    117 set crypt_opportunistic_encrypt = yes
    118 set pgp_self_encrypt = yes
    119 set pgp_default_key  = $keyid
    120 
    121 
    122 set mbox_type = Maildir
    123 set ssl_starttls = yes
    124 set ssl_force_tls = yes
    125 
    126 bind index,pager gg noop
    127 bind index,pager g noop
    128 bind index,pager M noop
    129 bind index,pager C noop
    130 bind index gg first-entry
    131 unmailboxes *
    132 "
    133 fi
    134 	printf "DONE.\\n"
    135 }
    136 
    137 askinfo() { \
    138 	printf "Insert the \033[31memail address\033[0m that you want to autoconfigure for mutt/mbsync\\n\tEmail: \033[36m"
    139 	read -r fulladdr
    140 	keyid=$("$GPG" --list-keys --with-colons "$fulladdr" | awk -F: '/^pub:/ { print $5 }')
    141 	printf "\033[0m"
    142 	while ! echo "$fulladdr" | grep "$emailre" >/dev/null; do
    143 		printf "That is not a valid \033[31memail address\033[0m, please retype the desired email.\\n\\nEmail: \033[36m\t"
    144 		read -r fulladdr
    145 		printf "\033[0m"
    146 	done
    147 	domain="$(echo "$fulladdr" | sed "s/.*@//")"
    148   search_query=$domain
    149   case "$domain" in
    150     protonmail.com|protonmail.ch|pm.me)
    151       search_query='protonmail.com' ;;
    152     *)
    153       while : ; do
    154         printf "\nIs your email hosted with Protonmail? [yes/no] "
    155         read -r is_protonmail
    156         case $is_protonmail in
    157           [Yy][Ee][Ss]) search_query='protonmail.com' && break;;
    158           [Nn][Oo]) break;;
    159           *) printf 'Please answer Yes or No'
    160         esac; done;
    161   esac
    162 	printf "\\nSearching for \033[32m%s\033[0m in \033[34m\`domains.csv\`\033[0m..." "$domain"
    163 	serverinfo="$(grep "^$search_query" "$muttshare/domains.csv" 2>/dev/null)"
    164 	if [ -z "$serverinfo" ]; then
    165 		printf "Your email domain is not in mutt-wizard's database yet.\\nmutt-wizard will still autoconfigure everything, but you will have to manually type in your service's IMAP and SMTP server information.\\nYou can usually quickly find this by internet searching for it.\\n"
    166 		printf "Insert the IMAP server for your email provider (excluding the port number)\\n\033[36m\t"
    167 		read -r imap
    168 		printf "\033[0mWhat is your server's IMAP port number? (Usually something like 993)\\n\033[36m\t"
    169 		read -r iport
    170 		printf "\033[0mInsert the SMTP server for your email provider (excluding the port number)\\n\033[36m\t"
    171 		read -r smtp
    172 		printf "\033[0mWhat is your server's SMTP port number? (Usually 587 or 465)\\n\033[36m\t"
    173 		read -r sport
    174 		printf "\033[0m\\nGreat! If you want to be helpful, copy the line below and you can add it to the \`domains.csv\` file on Github.\\nThis will make things easier for others who use your email provider.\\n\\n%s,%s,%s,%s,%s\\n\\nAlthough be sure to test to see if these settings work first! ;-)\\n" "$domain" "$imap" "$iport" "$smtp" "$sport"
    175 	else
    176 		IFS=, read -r service imap iport smtp sport <<EOF
    177 $serverinfo
    178 EOF
    179 	printf "\\n\033[3;33mCongrats!\033[0m Server info has automatically been found, so you won't have to look anything up!\\n\t\033[1mIMAP server\033[0m: %s\\n\t\033[1mIMAP port\033[0m: %s\\n\t\033[1mSMTP server\033[0m: %s\\n\t\033[1mSMTP port\033[0m: %s\\nThis data will be used by the wizard.\\n" "$imap" "$iport" "$smtp" "$sport"
    180 	case "$service" in
    181 		gmail.com) printf "\033[31mREMEMBER: Gmail users must enable \"less secure\" (third-party) applications first for the sync to work:\\nhttps://support.google.com/accounts/answer/6010255\\n\033[0m" ;;
    182 		protonmail.ch|protonmail.com|pm.me) printf "\033[31mREMEMBER: Protonmail users must install and configure Protonmail Bridge first for the sync to work:\\nhttps://protonmail.com/bridge/\\n\033[0m" && ssltype="None" ;;
    183 	esac
    184 	[ "$sport" = 465 ] && starttlsoff="tls_starttls off"
    185 	fi
    186 	printf "Enter the \033[35mfull name\033[0m you want to be identified by on this account.\\n\tReal name: "
    187 	read -r realname
    188 	printf "Enter a short, \033[36mone-word identifier\033[0m for this email account that will distinguish them from any other accounts you add.\\n\tAccount name: "
    189 	read -r title
    190 	while ! echo "$title" | grep "$namere" >/dev/null || ls "$accdir"/[0-9]"-$title.muttrc" >/dev/null 2>&1; do
    191 		printf "\033[31mTry again\033[0m. Pick a nickname that is one word only including lowercase letters and _ or - and that you have \033[1mnot\033[0m used before.\\n\tAccount name: \033[36m\t"
    192 		read -r title
    193 		printf "\033[0m"
    194 	done
    195 	printf "If your account has a special username different from your address, insert it now. Otherwise leave this prompt totally blank.\\n\033[34mMost accounts will not have a separate login, so you should probably leave this blank.\033[0m\\n\tLogin(?): \033[36m"
    196 	read -r login
    197 	printf "\033[0m"
    198 	[ -z "$login" ] && login="$fulladdr"
    199 	[ "$accounttype" = "offline" ] && printf "If you want to limit the number of messages kept offline to a number, enter that number below. If you do not want to limit your mail and would like \`mbsync\` to sync all mail, press enter without typing a number.\\n\t" && read -r maxmes
    200 	echo "$maxmes" | grep "[1-9]" >/dev/null || maxmes="0"
    201 	getpass
    202 	getprofiles
    203 	mkdir -p "$muttdir" "$accdir" "$cachedir/$title/bodies" "$HOME/.config/msmtp"
    204 	getaccounts
    205 	for x in $(seq 1 9); do echo "$accounts" | grep "$x" >/dev/null 2>&1 || { export idnum="$x"; break ;}; done
    206 	[ ! -f "$msmtprc" ] && echo "$msmtp_header" > "$msmtprc"
    207 	echo "$msmtp_profile" >> "$msmtprc"
    208 	command -V apt-get >/dev/null 2>&1 && ln -s "$msmtprc" "$HOME/.msmtprc" 2>/dev/null
    209 	case "$service" in
    210 		protonmail.ch|protonmail.com|pm.me) protonfinger || return 1 ;;
    211 	esac
    212 	echo "$mutt_profile" > "$accdir/$idnum-$title.muttrc"
    213 	echo "$mbsync_profile" >> "$mbsyncrc"
    214 	notmuchauto
    215 	[ ! -f "$muttrc" ] && echo "# vim: filetype=neomuttrc" > "$muttrc" && echo "muttrc created."
    216 	! grep "^source.*mutt-wizard.muttrc" "$muttrc" >/dev/null && echo "source $mwconfig # mw-autogenerated" >> "$muttrc"
    217 	! grep "^source.*.muttrc" "$muttrc" | grep -v "$mwconfig" >/dev/null && echo "source $accdir/$idnum-$title.muttrc # mw-autogenerated" >> "$muttrc"
    218 	echo "macro index,pager i$idnum '<sync-mailbox><enter-command>source $accdir/$idnum-$title.muttrc<enter><change-folder>!<enter>;<check-stats>' \"switch to $fulladdr\" # mw-autogenerated" >> "$muttrc"
    219 }
    220 
    221 protonfinger() { printf "Getting Protonmail bridge fingerprint...\\n"
    222   fingerprint="$(msmtp --serverinfo --host=127.0.0.1 --port=1025 --tls --tls-certcheck=off | grep SHA256: | sed 's/^.*: //')"
    223 	sed -ibu "s/account $title/&\ntls_trust_file\ntls_fingerprint $fingerprint/" "$msmtprc" ; rm -f "$msmtprc"bu
    224 }
    225 
    226 getpass() { while : ; do pass rm -f "mutt-wizard-$title" >/dev/null 2>&1
    227 		pass insert "mutt-wizard-$title" && break; done ;}
    228 
    229 formatShortcut() { \
    230 	while read -r data; do { echo "macro index,pager g$1 \"<change-folder>$data<enter>\" \"go to $2\" # mw-autogenerated"
    231 	echo "macro index,pager M$1 \";<save-message>$data<enter>\" \"move mail to $2\" # mw-autogenerated"
    232 	echo "macro index,pager C$1 \";<copy-message>$data<enter>\" \"copy mail to $2\" # mw-autogenerated"; } >> "$accdir/$idnum-$title.muttrc"
    233 	done ;}
    234 
    235 tryconnect() { mkdir -p "$maildir/$title"
    236 	if mailboxes="$(mbsync -l "$title" | sed 's/\//./')" >/dev/null 2>&1 && [ -n "$mailboxes" ]; then
    237 		[ "$accounttype" = "online" ] && sed -ibu "/IMAPStore $title-remote$/,/# End profile/d" "$mbsyncrc" ; rm -f "$mbsyncrc"bu
    238 		printf "\033[32mMailboxes detected.\033[0m\\n"
    239 		echo "$mailboxes" | xargs -I {} mkdir -p "$maildir/$title/{}"
    240 		return 0
    241 	else
    242 		printf "\033[31m\033[31mLog-on not successful.\033[0m\\nIt seems that either you inputted the wrong password or server settings, or there are other requirements for your account out of the control of mutt-wizard.\\n"
    243 		return 1
    244 	fi ;}
    245 
    246 finalize() { \
    247 	boxes="$(find "$maildir/$title/" -mindepth 1 -type d | sed "s/\ /\\\ /g;s/^.*\//=/;/=\(cur\|new\|tmp\)$/d")"
    248 	[ -z "$boxes" ] && printf "\033[31mNo local mailboxes have been detected for %s.\033[0m\\nThis means that mbsync has not been successfully run.\\nRun mbsync, and if it has an error, be sure to check your password and server settings manually if needbe.\\n" "$title" && return
    249 	printf "Setting default mailboxes for your Inbox, Sent, Drafts and Trash in mutt...\\n"
    250 	spoolfile=$(echo "$boxes" | grep -i -m 1 inbox | sed 's/=/+/g')
    251 	record=$(echo "$boxes" | grep -i -m 1 sent | sed 's/=/+/g')
    252 	postponed=$(echo "$boxes" | grep -i -m 1 draft | sed 's/=/+/g')
    253 	trash=$(echo "$boxes" | grep -i -m 1 trash | sed 's/=/+/g')
    254 	sed -ibu "/^mailboxes\|^set record\|^set postponed\|^set trash\|^set spoolfile/d" "$accdir/$idnum-$title.muttrc" ; rm -f "$accdir/$idnum-$title.muttrcbu"
    255 	{ echo "set spoolfile = \"$spoolfile\""; echo "set record = \"$record\""; echo "set postponed = \"$postponed\""; echo "set trash = \"$trash\""; } >> "$accdir/$idnum-$title.muttrc"
    256 	echo "mailboxes $(echo "$boxes" | sed -e "s/^\|$/\"/g" | tr "\n" " ")" >> "$accdir/$idnum-$title.muttrc"
    257 	printf "Setting up your keyboard shortcuts for jumping between mailboxes...\\n"
    258 	sed -ibu "/# mw-autogenerated/d" "$accdir/$idnum-$title.muttrc" ; rm -f "$accdir/$idnum-$title.muttrcbu"
    259 	echo "$boxes" | grep -i inbox | head -n 1 | formatShortcut i inbox
    260 	echo "$boxes" | grep -i sent | head -n 1 | formatShortcut s sent
    261 	echo "$boxes" | grep -i draft | head -n 1 | formatShortcut d drafts
    262 	echo "$boxes" | grep -i trash | head -n 1 | formatShortcut t trash
    263 	echo "$boxes" | grep -i spam | head -n 1 | formatShortcut S spam
    264 	echo "$boxes" | grep -i junk | head -n 1 | formatShortcut j junk
    265 	echo "$boxes" | grep -i archive | head -n 1 | formatShortcut a archive
    266 	[ "$accounttype" = "offline" ] && printf "All done.\\n\033[33mYou should now be able to run \`\033[32mmbsync %s\033[33m\` to begin to download your mail.\033[0m\\n" "$title"
    267 	command -V urlview >/dev/null 2>&1 && [ ! -f "$HOME/.urlview" ] && echo "COMMAND \$BROWSER" > "$HOME/.urlview"
    268 	return 0
    269 }
    270 
    271 confirm() { printf "Do you want to %s? [yes/N]\\n\t" "$@" && read -r input && ! echo "$input" | grep -i "^yes$" >/dev/null && printf "That doesn't seem like a yes to me.\\n\\n" && return 1
    272 	printf "Are you really, really sure you want to %s?\\n\t" "$@" && read -r input && ! echo "$input" | grep -i "^yes$" >/dev/null && printf "That doesn't seem like a yes to me.\\n\\n" && return 1
    273 	return 0 ;}
    274 
    275 pick() { printf "Select an accounts to %s:\\n" "$1"
    276 	list
    277 	read -r input
    278 	[ -z "$input" ] && return 1
    279 	title="$(echo "$accounts" | grep "$input" | awk '{print $2}')"
    280 	[ -z "$title" ] && printf "Invalid response." && return 1
    281 	return 0 ;}
    282 
    283 delete() { sed -ibu "/IMAPStore $title-remote$/,/# End profile/d" "$mbsyncrc" ; rm -rf "$mbsyncrc"bu
    284 	rm -rf "${cachedir:?}/${title:?}" "$accdir/"[1-9]"-$title.muttrc"
    285 	sed -ibu "/[0-9]-$title.muttrc/d" "$muttrc" ; rm -f "$muttrc"bu
    286 	sed -ibu "/account $title/,/^\(\s*$\|account\)/d" "$msmtprc"; rm -f "$msmtprc"bu
    287 	}
    288 
    289 asktype() { while : ; do
    290 		printf "Do you want to keep your mail for this account offline with mbsync? [yes/no]\\n\t"
    291 		read -r offnot
    292 		case "$offnot" in
    293 			[Yy][Ee][Ss]) accounttype="offline" && break ;;
    294 			[Nn][Oo]) accounttype="online" && break ;;
    295 			*) echo "Write out either yes or no completely. Try again or press ctrl-c to quit." ;;
    296 		esac; done ;}
    297 
    298 purge() { confirm "delete all account data" || exit
    299 	rm -rf "$mbsyncrc" "$accdir" "$HOME/.config/msmtp" "$cachedir"
    300 	echo "All configs and account settings have been purged."
    301 	sed -ibu "/\# mw-autogenerated/d" "$muttrc" ; rm -f "$muttrc"bu
    302 }
    303 
    304 syncwrapper() { mbsync -a &
    305 	( kill -46 "$(pidof "${STATUSBAR:-dwmblocks}")" >/dev/null 2>&1 ) 2>/dev/null
    306 	wait
    307 	( kill -46 "$(pidof "${STATUSBAR:-dwmblocks}")" >/dev/null 2>&1 ) 2>/dev/null
    308 	notmuch new
    309 }
    310 
    311 notmuchauto() { \
    312 	[ -z "$NOTMUCH_CONFIG" ] && NOTMUCH_CONFIG="$HOME/.notmuch-config"
    313 	[ -f "$NOTMUCH_CONFIG" ] && return 0
    314 	nmbasic="[database]
    315 path=$maildir
    316 [user]
    317 name=$realname
    318 primary_email=$fulladdr
    319 [new]
    320 tags=unread;inbox;
    321 ignore=.mbsyncstate;.uidvalidity
    322 [search]
    323 exclude_tags=deleted;spam;
    324 [maildir]
    325 synchronize_flags=true
    326 [crypto]
    327 gpg_path=$GPG"
    328 	echo "$nmbasic" > "$NOTMUCH_CONFIG" ;}
    329 
    330 trap 'echo -e "\033[0m\n"; exit' INT ABRT
    331 
    332 case "$1" in
    333 	ls) list ;;
    334 	add) asktype && askinfo && tryconnect && finalize || delete ;;
    335 	pass) pick "change the password of" && getpass ;;
    336 	delete) pick delete && confirm "delete the \`$title\` profile" && delete ;;
    337 	sync) syncwrapper ;;
    338 	purge) purge ;;
    339 	*) cat << EOF
    340 mw: mutt-wizard, auto-configure email accounts for mutt
    341 including downloadable mail with \`isync\`.
    342 
    343 Allowed options:
    344   add		Add and autoconfigure an email address (9 max.)
    345   ls		List configured accounts
    346   delete	Pick an account to delete
    347   purge		Delete all accounts and settings
    348   sync		Syncs mail and updates notmuch database
    349   all else	Print this message
    350 
    351 NOTE: Once at least one account is added, you can run
    352 \`mbsync -a\` to begin downloading mail.
    353 EOF
    354 esac