mutt-wizard

A wizard that autocreates an offline email setup for (neo)mutt with isync/mbsync
Log | Files | Refs | README | LICENSE

mw (16866B)


      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 \`$GPG --full-gen-key\` first.\\n"
      8         exit
      9     }
     10 ! command -v mbsync >/dev/null && printf "\`mbsync\` 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 SSLType $ssltype
     57 AuthMech LOGIN
     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 bind index,pager gg noop
     91 bind index,pager g noop
     92 bind index,pager M noop
     93 bind index,pager C noop
     94 bind index gg first-entry
     95 macro index o \"<shell-escape>mailsync -V $title<enter>\" \"run mbsync to sync $title\"
     96 unmailboxes *
     97 "
     98 else
     99 mutt_profile="# vim: filetype=neomuttrc
    100 # muttrc file for account $title
    101 set realname = \"$realname\"
    102 set from = \"$fulladdr\"
    103 set sendmail = \"$prefix/bin/msmtp -a $title\"
    104 alias me $realname <$fulladdr>
    105 set folder = \"imaps://$fulladdr@$imap:$iport\"
    106 set imap_user = \"$login\"
    107 set header_cache = $cachedir/$title/headers
    108 set message_cachedir = $cachedir/$title/bodies
    109 set imap_pass = \"\`pass mutt-wizard-$title\`\"
    110 
    111 set mbox_type = Maildir
    112 set ssl_starttls = yes
    113 set ssl_force_tls = yes
    114 
    115 bind index,pager gg noop
    116 bind index,pager g noop
    117 bind index,pager M noop
    118 bind index,pager C noop
    119 bind index gg first-entry
    120 unmailboxes *
    121 "
    122 fi
    123 	printf "DONE.\\n"
    124 }
    125 
    126 askinfo() { \
    127 	printf "Insert the \033[31memail address\033[0m that you want to autoconfigure for mutt/mbsync\\n\tEmail: \033[36m"
    128 	read -r fulladdr
    129 	printf "\033[0m"
    130 	while ! echo "$fulladdr" | grep "$emailre" >/dev/null; do
    131 		printf "That is not a valid \033[31memail address\033[0m, please retype the desired email.\\n\\nEmail: \033[36m\t"
    132 		read -r fulladdr
    133 		printf "\033[0m"
    134 	done
    135 	domain="$(echo "$fulladdr" | sed "s/.*@//")"
    136   search_query=$domain
    137   case "$domain" in
    138     protonmail.com|protonmail.ch|pm.me)
    139       search_query='protonmail.com' && break;;
    140     *)
    141       while : ; do
    142         printf "\nIs your email hosted with Protonmail? [yes/no] "
    143         read -r is_protonmail
    144         case $is_protonmail in
    145           [Yy][Ee][Ss]) search_query='protonmail.com' && break;;
    146           [Nn][Oo]) break;;
    147           *) printf 'Please answer Yes or No'
    148         esac; done;
    149   esac
    150 	printf "\\nSearching for \033[32m%s\033[0m in \033[34m\`domains.csv\`\033[0m..." "$domain"
    151 	serverinfo="$(grep "^$search_query" "$muttshare/domains.csv" 2>/dev/null)"
    152 	if [ -z "$serverinfo" ]; then
    153 		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"
    154 		printf "Insert the IMAP server for your email provider (excluding the port number)\\n\033[36m\t"
    155 		read -r imap
    156 		printf "\033[0mWhat is your server's IMAP port number? (Usually something like 993)\\n\033[36m\t"
    157 		read -r iport
    158 		printf "\033[0mInsert the SMTP server for your email provider (excluding the port number)\\n\033[36m\t"
    159 		read -r smtp
    160 		printf "\033[0mWhat is your server's SMTP port number? (Usually 587 or 465)\\n\033[36m\t"
    161 		read -r sport
    162 		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"
    163 	else
    164 		IFS=, read -r service imap iport smtp sport <<EOF
    165 $serverinfo
    166 EOF
    167 	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"
    168 	case "$service" in
    169 		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" ;;
    170 		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" ;;
    171 	esac
    172 	[ "$sport" = 465 ] && starttlsoff="tls_starttls off"
    173 	fi
    174 	printf "Enter the \033[35mfull name\033[0m you want to be identified by on this account.\\n\tReal name: "
    175 	read -r realname
    176 	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: "
    177 	read -r title
    178 	while ! echo "$title" | grep "$namere" >/dev/null || ls "$accdir"/[0-9]"-$title.muttrc" >/dev/null 2>&1; do
    179 		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"
    180 		read -r title
    181 		printf "\033[0m"
    182 	done
    183 	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"
    184 	read -r login
    185 	printf "\033[0m"
    186 	[ -z "$login" ] && login="$fulladdr"
    187 	[ "$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
    188 	echo "$maxmes" | grep "[1-9]" >/dev/null || maxmes="0"
    189 	getpass
    190 	getprofiles
    191 	mkdir -p "$muttdir" "$accdir" "$cachedir/$title/bodies" "$HOME/.config/msmtp"
    192 	getaccounts
    193 	for x in $(seq 1 9); do echo "$accounts" | grep "$x" >/dev/null 2>&1 || { export idnum="$x"; break ;}; done
    194 	[ ! -f "$msmtprc" ] && echo "$msmtp_header" > "$msmtprc"
    195 	echo "$msmtp_profile" >> "$msmtprc"
    196 	command -V apt-get >/dev/null 2>&1 && ln -s "$msmtprc" "$HOME/.msmtprc" 2>/dev/null
    197 	case "$service" in
    198 		protonmail.ch|protonmail.com|pm.me) protonfinger || return 1 ;;
    199 	esac
    200 	echo "$mutt_profile" > "$accdir/$idnum-$title.muttrc"
    201 	echo "$mbsync_profile" >> "$mbsyncrc"
    202 	notmuchauto
    203 	[ ! -f "$muttrc" ] && echo "# vim: filetype=neomuttrc" > "$muttrc" && echo "muttrc created."
    204 	! grep "^source.*mutt-wizard.muttrc" "$muttrc" >/dev/null && echo "source $mwconfig # mw-autogenerated" >> "$muttrc"
    205 	! grep "^source.*.muttrc" "$muttrc" | grep -v "$mwconfig" >/dev/null && echo "source $accdir/$idnum-$title.muttrc # mw-autogenerated" >> "$muttrc"
    206 	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"
    207 }
    208 
    209 protonfinger() { printf "Getting Protonmail bridge fingerprint...\\n"
    210   fingerprint="$(msmtp --serverinfo --host=127.0.0.1 --port=1025 --tls --tls-certcheck=off | grep SHA256: | sed 's/^.*: //')"
    211 	sed -ibu "s/account $title/&\ntls_trust_file\ntls_fingerprint $fingerprint/" "$msmtprc" ; rm -f "$msmtprc"bu
    212 }
    213 
    214 getpass() { while : ; do pass rm -f "mutt-wizard-$title" >/dev/null 2>&1
    215 		pass insert "mutt-wizard-$title" && break; done ;}
    216 
    217 formatShortcut() { \
    218 	while read -r data; do { echo "macro index,pager g$1 \"<change-folder>$data<enter>\" \"go to $2\" # mw-autogenerated"
    219 	echo "macro index,pager M$1 \";<save-message>$data<enter>\" \"move mail to $2\" # mw-autogenerated"
    220 	echo "macro index,pager C$1 \";<copy-message>$data<enter>\" \"copy mail to $2\" # mw-autogenerated"; } >> "$accdir/$idnum-$title.muttrc"
    221 	done ;}
    222 
    223 tryconnect() { mkdir -p "$maildir/$title"
    224 	if mailboxes="$(mbsync -l "$title" | sed 's/\//./')" >/dev/null 2>&1 && [ -n "$mailboxes" ]; then
    225 		[ "$accounttype" = "online" ] && sed -ibu "/IMAPStore $title-remote$/,/# End profile/d" "$mbsyncrc" ; rm -f "$mbsyncrc"bu
    226 		printf "\033[32mMailboxes detected.\033[0m\\n"
    227 		echo "$mailboxes" | xargs -I {} mkdir -p "$maildir/$title/{}"
    228 		return 0
    229 	else
    230 		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"
    231 		return 1
    232 	fi ;}
    233 
    234 finalize() { \
    235 	boxes="$(find "$maildir/$title/" -mindepth 1 -type d | sed "s/\ /\\\ /g;s/^.*\//=/;/=\(cur\|new\|tmp\)$/d")"
    236 	[ -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
    237 	printf "Setting default mailboxes for your Inbox, Sent, Drafts and Trash in mutt...\\n"
    238 	spoolfile=$(echo "$boxes" | grep -i -m 1 inbox | sed 's/=/+/g')
    239 	record=$(echo "$boxes" | grep -i -m 1 sent | sed 's/=/+/g')
    240 	postponed=$(echo "$boxes" | grep -i -m 1 draft | sed 's/=/+/g')
    241 	trash=$(echo "$boxes" | grep -i -m 1 trash | sed 's/=/+/g')
    242 	sed -ibu "/^mailboxes\|^set record\|^set postponed\|^set trash\|^set spoolfile/d" "$accdir/$idnum-$title.muttrc" ; rm -f "$accdir/$idnum-$title.muttrcbu"
    243 	{ echo "set spoolfile = \"$spoolfile\""; echo "set record = \"$record\""; echo "set postponed = \"$postponed\""; echo "set trash = \"$trash\""; } >> "$accdir/$idnum-$title.muttrc"
    244 	echo "mailboxes $(echo "$boxes" | sed -e "s/^\|$/\"/g" | tr "\n" " ")" >> "$accdir/$idnum-$title.muttrc"
    245 	printf "Setting up your keyboard shortcuts for jumping between mailboxes...\\n"
    246 	sed -ibu "/# mw-autogenerated/d" "$accdir/$idnum-$title.muttrc" ; rm -f "$accdir/$idnum-$title.muttrcbu"
    247 	echo "$boxes" | grep -i inbox | head -n 1 | formatShortcut i inbox
    248 	echo "$boxes" | grep -i sent | head -n 1 | formatShortcut s sent
    249 	echo "$boxes" | grep -i draft | head -n 1 | formatShortcut d drafts
    250 	echo "$boxes" | grep -i trash | head -n 1 | formatShortcut t trash
    251 	echo "$boxes" | grep -i spam | head -n 1 | formatShortcut S spam
    252 	echo "$boxes" | grep -i junk | head -n 1 | formatShortcut j junk
    253 	echo "$boxes" | grep -i archive | head -n 1 | formatShortcut a archive
    254 	[ "$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"
    255 	command -V urlview >/dev/null 2>&1 && [ ! -f "$HOME/.urlview" ] && echo "COMMAND \$BROWSER" > "$HOME/.urlview"
    256 	return 0
    257 }
    258 
    259 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
    260 	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
    261 	return 0 ;}
    262 
    263 pick() { printf "Select an accounts to %s:\\n" "$1"
    264 	list
    265 	read -r input
    266 	[ -z "$input" ] && return 1
    267 	title="$(echo "$accounts" | grep "$input" | awk '{print $2}')"
    268 	[ -z "$title" ] && printf "Invalid response." && return 1
    269 	return 0 ;}
    270 
    271 delete() { sed -ibu "/IMAPStore $title-remote$/,/# End profile/d" "$mbsyncrc" ; rm -rf "$mbsyncrc"bu
    272 	rm -rf "${cachedir:?}/${title:?}" "$accdir/"[1-9]"-$title.muttrc"
    273 	sed -ibu "/[0-9]-$title.muttrc/d" "$muttrc" ; rm -f "$muttrc"bu
    274 	sed -ibu "/account $title/,/^\(\s*$\|account\)/d" "$msmtprc"; rm -f "$msmtprc"bu
    275 	}
    276 
    277 choosecron() { ! pgrep cron >/dev/null && echo "No cron manager running. Install/enable one and then select this option again." && return 1
    278 	if crontab -l | grep mailsync >/dev/null; then
    279 		echo "Active mail sync cronjob detected. Do you want to remove it?"
    280 		printf "\033[36m\t"
    281 		read -r rmyn
    282 		printf "\033[0m"
    283 		echo "$rmyn" | grep -i "^y\(es\)*$" >/dev/null && crontab -l | sed '/mailsync/d' | crontab - >/dev/null && echo "Mail sync turned off."
    284 	else
    285 		echo "How many minutes between each mail sync?"
    286 		printf "\033[36m\t"
    287 		read -r minnum
    288 		printf "\033[0m"
    289 		while ! echo "$minnum" | grep "^[0-9]\+$" >/dev/null; do
    290 			printf "That doesn't look like a number. How many minutes between each mail sync?\\n\033[36m\t"
    291 			read -r minnum
    292 			printf "\033[0m"
    293 		done
    294 		(crontab -l; echo "*/$minnum * * * * $(type mailsync | cut -d' ' -f3)") | crontab - &&
    295 			echo "Cronjob added. Mail will sync every $minnum minutes. Be sure you have your cron manager running."
    296 	fi ;}
    297 
    298 asktype() { while : ; do
    299 		printf "Do you want to keep your mail for this account offline with mbsync? [yes/no]\\n\t"
    300 		read -r offnot
    301 		case "$offnot" in
    302 			[Yy][Ee][Ss]) accounttype="offline" && break ;;
    303 			[Nn][Oo]) accounttype="online" && break ;;
    304 			*) echo "Write out either yes or no completely. Try again or press ctrl-c to quit." ;;
    305 		esac; done ;}
    306 
    307 purge() { confirm "delete all account data" || exit
    308 	rm -rf "$mbsyncrc" "$accdir" "$HOME/.config/msmtp" "$cachedir"
    309 	crontab -l | sed '/mailsync/d' | crontab - >/dev/null
    310 	echo "All configs and account settings have been purged."
    311 	sed -ibu "/\# mw-autogenerated/d" "$muttrc" ; rm -f "$muttrc"bu
    312 }
    313 
    314 notmuchauto() { \
    315 	[ -z "$NOTMUCH_CONFIG" ] && NOTMUCH_CONFIG="$HOME/.notmuch-config"
    316 	[ -f "$NOTMUCH_CONFIG" ] && return 0
    317 	nmbasic="[database]
    318 path=$maildir
    319 [user]
    320 name=$realname
    321 primary_email=$fulladdr
    322 [new]
    323 tags=unread;inbox;
    324 ignore=
    325 [search]
    326 exclude_tags=deleted;spam;
    327 [maildir]
    328 synchronize_flags=true
    329 [crypto]
    330 gpg_path=$GPG"
    331 	echo "$nmbasic" > "$NOTMUCH_CONFIG" ;}
    332 
    333 trap 'echo -e "\033[0m\n"; exit' STOP INT ABRT KILL
    334 
    335 case "$1" in
    336 	ls) list ;;
    337 	add) asktype && askinfo && tryconnect && finalize || delete ;;
    338 	pass) pick "change the password of" && getpass ;;
    339 	delete) pick delete && confirm "delete the \`$title\` profile" && delete ;;
    340 	purge) purge ;;
    341 	cron) choosecron ;;
    342 	*) cat << EOF
    343 mw: mutt-wizard, auto-configure email accounts for mutt
    344 including downloadable mail with \`isync\`.
    345 
    346 Allowed options:
    347   add		Add and autoconfigure an email address (9 max.)
    348   ls		List configured accounts
    349   delete	Pick an account to delete
    350   purge		Delete all accounts and settings
    351   cron		Enable or disable an autosync via cronjob
    352   all else	Print this message
    353 
    354 NOTE: Once at least one account is added, you can run
    355 \`mbsync -a\` to begin downloading mail.
    356 EOF
    357 esac