muw (28080B)
1 #!/bin/sh -e 2 3 parser_definition() { 4 setup REST help:usage -- "usage: ${0##*/} [cmd] [option...]" 5 msg -- '' 'mu-wizard, a helper script for configuring mu4e accounts' '' 6 msg -- 'Commands:' 7 cmd_a add -- "Add and autoconfigure an email address" 8 cmd_a delete -- "Pick an account to delete" 9 cmd_a list -- "List configured accounts" 10 cmd_a purge -- "Purge all configuration" 11 cmd_a sync -- "Sync mail for accounts" 12 cmd mu-init -- "Run 'mu init' with the configured accounts" 13 cmd data -- "Output system data directory and exit" 14 msg -- '' "Run '${0##*/} COMMAND --help' to see help information for the" 15 msg -- "given command." 16 global_options 17 msg -- 'Note:' 18 msg -- 'Once at least one account is added, you can run' 19 msg -- "'${0##*/} sync' to begin downloading your mail." 20 } 21 22 cmd_a() { 23 # Grab the first character of the subcommand, and add it to the option 24 # parser as a subcommand. 25 cmd "${1%${1#?}}" hidden:1; cmd "$@" 26 } 27 28 parser_definition_add() { 29 setup REST help:usage -- "usage: ${0##*/} add [option...]" 30 msg -- '' 'Add and autoconfigure an email address' '' 31 msg -- 'Options:' 32 flag nocheck -c --disable-checks -- "Disable checking domains.csv for acquiring domain information" 33 flag nooverride -o --disable-overrides -- "Disable checking override files for acquiring domain information" 34 msg -- '' 'Any information not provided below, will be asked interactively.' 35 msg -- 'Account options:' 36 param fulladdr -a --address var:EMAIL -- "Email address for account generation" 37 option maxmes -m --max-messages var:COUNT on:0 -- \ 38 "Maximum message COUNT to be kept offline, leave" "empty for unlimited." 39 param realname -r --real-name var:NAME -- "Your full name to be identified with the account" 40 param title -t --title var:TITLE -- "Unique account title" 41 option login -l --login var:USERNAME -- "Use your full address if empty or USERNAME for" \ 42 "your login name" 43 msg -- '' "Domain options: (setting either of these implies '-c -o')" 44 param imap -i --imap var:SERVER -- "IMAP server address" 45 param iport -I --imap-port var:PORT -- "IMAP server port" 46 param smtp -s --smtp var:SERVER -- "SMTP server address" 47 param sport -S --smtp-port var:PORT -- "SMTP server port" 48 global_options 49 } 50 51 parser_definition_delete() { 52 setup REST help:usage -- "usage: ${0##*/} delete [option...] [account...]" 53 msg -- '' 'Delete account' '' 54 flag noconfirm -y -- "Don't confirm account deletion" 55 global_options 56 } 57 58 parser_definition_list() { 59 global_options "usage: ${0##*/} list" "List configured accounts" 60 } 61 parser_definition_data() { 62 global_options "usage: ${0##*/} data" "Output system data directory" 63 } 64 parser_definition_sync() { 65 global_options "usage: ${0##*/} sync [account...]" "Sync mail for accounts" 66 } 67 parser_definition_mu() { 68 setup REST help:usage -- "usage: ${0##*/} mu-init" 69 msg -- '' 'Initiate/reinitate mu database' '' 70 msg -- 'Options:' 71 flag index -i -- "Index the mail directory after initiating the database" 72 global_options 73 } 74 75 parser_definition_purge() { 76 setup REST help:usage -- "usage: ${0##*/} purge [option...]" 77 msg -- '' 'Purge all configuration' '' 78 msg -- 'Options:' 79 flag noconfirm -y -- "Don't confirm configuration purge" 80 flag keep_isync -i -- "Keep isync configuration" 81 flag keep_msmtp -m -- "Keep msmtp configuration" 82 global_options 83 } 84 85 global_options() { 86 [ "$1" ] && setup REST help:usage -- "$1" '' "$2" 87 msg -- '' 'Global options:' 88 disp :version -v --version -- "Display version information" 89 disp :usage -h --help -- "Print this help message" 90 msg -- '' 91 } 92 93 # shellcheck shell=sh disable=SC2016 94 # [getoptions] License: Creative Commons Zero v1.0 Universal 95 getoptions() { 96 _error='' _on=1 _off='' _export='' _plus='' _mode='' _alt='' _rest='' 97 _flags='' _nflags='' _opts='' _help='' _abbr='' _cmds='' _init=@empty IFS=' ' 98 99 _0() { echo "$@"; } 100 for i in 1 2 3 4 5; do eval "_$i() { _$((${i-}-1)) \" \$@\"; }"; done 101 102 quote() { 103 q="$2'" r='' 104 while [ "$q" ]; do r="$r${q%%\'*}'\''" && q=${q#*\'}; done 105 q="'${r%????}'" && q=${q#\'\'} && q=${q%\'\'} 106 eval "$1=\${q:-\"''\"}" 107 } 108 code() { 109 [ "${1#:}" = "$1" ] && c=3 || c=4 110 eval "[ ! \${$c:+x} ] || $2 \"\$$c\"" 111 } 112 kv() { eval "${2-}${1%%:*}=\${1#*:}"; } 113 loop() { [ $# -gt 1 ] && [ "$2" != -- ]; } 114 115 invoke() { eval '"_$@"'; } 116 prehook() { invoke "$@"; } 117 for i in setup flag param option disp msg; do 118 eval "$i() { prehook $i \"\$@\"; }" 119 done 120 121 args() { 122 on=$_on off=$_off export=$_export init=$_init _hasarg=$1 && shift 123 while loop "$@" && shift; do 124 case $1 in 125 -?) [ "$_hasarg" ] && _opts="$_opts${1#-}" || _flags="$_flags${1#-}" ;; 126 +?) _plus=1 _nflags="$_nflags${1#+}" ;; 127 [!-+]*) kv "$1" 128 esac 129 done 130 } 131 defvar() { 132 case $init in 133 @none) : ;; 134 @export) code "$1" _0 "export $1" ;; 135 @empty) code "$1" _0 "${export:+export }$1=''" ;; 136 @unset) code "$1" _0 "unset $1 ||:" "unset OPTARG ||:; ${1#:}" ;; 137 *) 138 case $init in @*) eval "init=\"=\${${init#@}}\""; esac 139 case $init in [!=]*) _0 "$init"; return 0; esac 140 quote init "${init#=}" 141 code "$1" _0 "${export:+export }$1=$init" "OPTARG=$init; ${1#:}" 142 esac 143 } 144 _setup() { 145 [ "${1#-}" ] && _rest=$1 146 while loop "$@" && shift; do kv "$1" _; done 147 } 148 _flag() { args '' "$@"; defvar "$@"; } 149 _param() { args 1 "$@"; defvar "$@"; } 150 _option() { args 1 "$@"; defvar "$@"; } 151 _disp() { args '' "$@"; } 152 _msg() { args '' _ "$@"; } 153 154 cmd() { _mode=@ _cmds="$_cmds${_cmds:+|}'$1'"; } 155 "$@" 156 cmd() { :; } 157 _0 "${_rest:?}=''" 158 159 _0 "$2() {" 160 _1 'OPTIND=$(($#+1))' 161 _1 'while OPTARG= && [ $# -gt 0 ]; do' 162 [ "$_abbr" ] && getoptions_abbr "$@" 163 164 args() { 165 sw='' validate='' pattern='' counter='' on=$_on off=$_off export=$_export 166 while loop "$@" && shift; do 167 case $1 in 168 --\{no-\}*) i=${1#--?no-?}; sw="$sw${sw:+|}'--$i'|'--no-$i'" ;; 169 --with\{out\}-*) i=${1#--with?out?-}; sw="$sw${sw:+|}'--with-$i'|'--without-$i'" ;; 170 [-+]? | --*) sw="$sw${sw:+|}'$1'" ;; 171 *) kv "$1" 172 esac 173 done 174 quote on "$on" 175 quote off "$off" 176 } 177 setup() { :; } 178 _flag() { 179 args "$@" 180 [ "$counter" ] && on=1 off=-1 v="\$((\${$1:-0}+\$OPTARG))" || v='' 181 _3 "$sw)" 182 _4 '[ "${OPTARG:-}" ] && OPTARG=${OPTARG#*\=} && set "noarg" "$1" && break' 183 _4 "eval '[ \${OPTARG+x} ] &&:' && OPTARG=$on || OPTARG=$off" 184 valid "$1" "${v:-\$OPTARG}" 185 _4 ';;' 186 } 187 _param() { 188 args "$@" 189 _3 "$sw)" 190 _4 '[ $# -le 1 ] && set "required" "$1" && break' 191 _4 'OPTARG=$2' 192 valid "$1" '$OPTARG' 193 _4 'shift ;;' 194 } 195 _option() { 196 args "$@" 197 _3 "$sw)" 198 _4 'set -- "$1" "$@"' 199 _4 '[ ${OPTARG+x} ] && {' 200 _5 'case $1 in --no-*|--without-*) set "noarg" "${1%%\=*}"; break; esac' 201 _5 '[ "${OPTARG:-}" ] && { shift; OPTARG=$2; } ||' "OPTARG=$on" 202 _4 "} || OPTARG=$off" 203 valid "$1" '$OPTARG' 204 _4 'shift ;;' 205 } 206 valid() { 207 set -- "$validate" "$pattern" "$1" "$2" 208 [ "$1" ] && _4 "$1 || { set -- ${1%% *}:\$? \"\$1\" $1; break; }" 209 [ "$2" ] && { 210 _4 "case \$OPTARG in $2) ;;" 211 _5 '*) set "pattern:'"$2"'" "$1"; break' 212 _4 "esac" 213 } 214 code "$3" _4 "${export:+export }$3=\"$4\"" "${3#:}" 215 } 216 _disp() { 217 args "$@" 218 _3 "$sw)" 219 code "$1" _4 "echo \"\${$1}\"" "${1#:}" 220 _4 'exit 0 ;;' 221 } 222 _msg() { :; } 223 224 [ "$_alt" ] && _2 'case $1 in -[!-]?*) set -- "-$@"; esac' 225 _2 'case $1 in' 226 _wa() { _4 "eval 'set -- $1' \${1+'\"\$@\"'}"; } 227 _op() { 228 _3 "$1) OPTARG=\$1; shift" 229 _wa '"${OPTARG%"${OPTARG#??}"}" '"$2"'"${OPTARG#??}"' 230 _4 "$3" 231 } 232 _3 '--?*=*) OPTARG=$1; shift' 233 _wa '"${OPTARG%%\=*}" "${OPTARG#*\=}"' 234 _4 ';;' 235 _3 '--no-*|--without-*) unset OPTARG ;;' 236 [ "$_alt" ] || { 237 [ "$_opts" ] && _op "-[$_opts]?*" '' ';;' 238 [ ! "$_flags" ] || _op "-[$_flags]?*" - 'OPTARG= ;;' 239 } 240 [ "$_plus" ] && { 241 [ "$_nflags" ] && _op "+[$_nflags]?*" + 'unset OPTARG ;;' 242 _3 '+*) unset OPTARG ;;' 243 } 244 _2 'esac' 245 _2 'case $1 in' 246 "$@" 247 rest() { 248 _4 'while [ $# -gt 0 ]; do' 249 _5 "$_rest=\"\${$_rest}" '\"\${$(($OPTIND-$#))}\""' 250 _5 'shift' 251 _4 'done' 252 _4 'break ;;' 253 } 254 _3 '--)' 255 [ "$_mode" = @ ] || _4 'shift' 256 rest 257 _3 "[-${_plus:++}]?*)" 258 case $_mode in [=#]) rest ;; *) _4 'set "unknown" "$1"; break ;;'; esac 259 _3 '*)' 260 case $_mode in 261 @) 262 _4 "case \$1 in ${_cmds:-*}) ;;" 263 _5 '*) set "notcmd" "$1"; break' 264 _4 'esac' 265 rest ;; 266 [+#]) rest ;; 267 *) _4 "$_rest=\"\${$_rest}" '\"\${$(($OPTIND-$#))}\""' 268 esac 269 _2 'esac' 270 _2 'shift' 271 _1 'done' 272 _1 '[ $# -eq 0 ] && { OPTIND=1; unset OPTARG; return 0; }' 273 _1 'case $1 in' 274 _2 'unknown) set "Unrecognized option: $2" "$@" ;;' 275 _2 'noarg) set "Does not allow an argument: $2" "$@" ;;' 276 _2 'required) set "Requires an argument: $2" "$@" ;;' 277 _2 'pattern:*) set "Does not match the pattern (${1#*:}): $2" "$@" ;;' 278 _2 'notcmd) set "Not a command: $2" "$@" ;;' 279 _2 '*) set "Validation error ($1): $2" "$@"' 280 _1 'esac' 281 [ "$_error" ] && _1 "$_error" '"$@" >&2 || exit $?' 282 _1 'echo "$1" >&2' 283 _1 'exit 1' 284 _0 '}' 285 286 [ ! "$_help" ] || eval "shift 2; getoptions_help $1 $_help" ${3+'"$@"'} 287 } 288 # [getoptions_help] License: Creative Commons Zero v1.0 Universal 289 getoptions_help() { 290 _width='30,12' _plus='' _leading=' ' 291 292 pad() { p=$2; while [ ${#p} -lt "$3" ]; do p="$p "; done; eval "$1=\$p"; } 293 kv() { eval "${2-}${1%%:*}=\${1#*:}"; } 294 sw() { pad sw "$sw${sw:+, }" "$1"; sw="$sw$2"; } 295 296 args() { 297 _type=$1 var=${2%% *} sw='' label='' hidden='' && shift 2 298 while [ $# -gt 0 ] && i=$1 && shift && [ "$i" != -- ]; do 299 case $i in 300 --*) sw $((${_plus:+4}+4)) "$i" ;; 301 -?) sw 0 "$i" ;; 302 +?) [ ! "$_plus" ] || sw 4 "$i" ;; 303 *) [ "$_type" = setup ] && kv "$i" _; kv "$i" 304 esac 305 done 306 [ "$hidden" ] && return 0 || len=${_width%,*} 307 308 [ "$label" ] || case $_type in 309 setup | msg) label='' len=0 ;; 310 flag | disp) label="$sw " ;; 311 param) label="$sw $var " ;; 312 option) label="${sw}[=$var] " 313 esac 314 [ "$_type" = cmd ] && label=${label:-$var } len=${_width#*,} 315 pad label "${label:+$_leading}$label" "$len" 316 [ ${#label} -le "$len" ] && [ $# -gt 0 ] && label="$label$1" && shift 317 echo "$label" 318 pad label '' "$len" 319 for i; do echo "$label$i"; done 320 } 321 322 for i in setup flag param option disp 'msg -' cmd; do 323 eval "${i% *}() { args $i \"\$@\"; }" 324 done 325 326 echo "$2() {" 327 echo "cat<<'GETOPTIONSHERE'" 328 "$@" 329 echo "GETOPTIONSHERE" 330 echo "}" 331 } 332 333 out() { printf '%s\n' "$@" >&2 ;} 334 err() { printf 'err: %s\n' "$@" >&2 ;} 335 die() { err "$@"; exit 1 ;} 336 version() { printf 'mu-wizard version: %s\n' unreleased ;} 337 notify() { 338 notify-send -i mail-unread -a "mu-wizard" "$@" 339 } 340 prompt() { 341 # The first argument is used as the prompt, and will print as 'prompt: '. 342 # Any other arguments will be printed before prompt if they exist. 343 prompt=$1 ans= 344 [ "$2" ] && { shift 1; out "$@" ;} 345 printf '%s: ' "$prompt" >&2 346 read -r ans || return 347 printf %s "$ans" 348 } 349 prompt_required() { 350 promptout=''; promptout=$(prompt "$@") 351 until [ "$promptout" ]; do 352 out "This is required." 353 promptout=$(prompt "$@") 354 done 355 printf %s "$promptout" 356 } 357 prompt_noecho() { 358 # This is the same with the prompt() function except that it doesn't echo 359 # the user input to the terminal. It can be used to ask for passwords and 360 # other secret information. 361 stty=$(stty -g); stty -echo 362 prompt "$@"; printf '\n' >&2 363 stty "$stty" 364 } 365 366 getbut() { 367 # This function can be used to get a single button input. No arguments 368 # necessary. 369 stty=$(stty -g); stty -icanon -echo 370 dd bs=1 count=1 2>/dev/null 371 stty "$stty" 372 } 373 374 yesno() { 375 # Function to ask the user yes/no questions. Returns 0 if the button 376 # received is Y/y, Returns 1 if the button received is N/n, and returns 377 # 2 if any other button is received. If the noconfirm option is set from 378 # the options, do not ask anything. 379 # 380 # The 'noconfirm' variable is declared and assigned by the option parser. 381 # shellcheck disable=2154 382 [ "$noconfirm" ] && return 0 383 printf '%s\n' "$@"; printf 'y/n: ' 384 ans=$(getbut) 385 printf '%s\n' "$ans" >&2 386 case "$ans" in Y|y) return 0;; N|n) return 1; esac; return 2 387 } 388 389 delete() { 390 rm -f "$accountdir/$1.el" 391 sed_i "$HOME/.mbsyncrc" "/^IMAPStore $1-remote\$/,/^# End profile\$/d" 392 sed_i "$config_home/msmtp/config" "/^account $1\$/,/^# End profile\$/d" 393 rm -f /tmp/mbsync-boxes 394 } 395 396 in_profiles() { 397 # Check whether the title is already a profile. 398 while read -r profile; do 399 [ "$profile" = "$1" ] && return 0 400 done <<EOF 401 $(get_profiles) 402 EOF 403 return 1 404 } 405 406 sed_i() { 407 # POSIX compliant 'sed -i' like function. This can be only used for a single 408 # file, and the filename should always come first. 409 file=$1; shift 410 [ -f "$file" ] || die "$file: No such file or directory" 411 sed "$@" "$file" > _; cat _ > "$file" 412 rm -f _ 413 } 414 415 get_profiles() { 416 # Function to get all available profiles 417 eval=$1 418 set -- 419 for profile in "$accountdir/"*.el; do 420 profile=${profile##*/} 421 set -- "$@" "${profile%.el}" 422 done 423 if [ "$eval" ]; then 424 printf "'%s' " "$@" 425 else 426 printf '%s\n' "$@" 427 fi 428 } 429 430 msmtp_header() { 431 mkdir -p "$config_home/msmtp" \ 432 "${XDG_CACHE_HOME:=$HOME/.cache}/msmtp" 433 cat <<EOF > "$config_home/msmtp/config" 434 defaults 435 auth on 436 tls on 437 tls_trust_file $sslcert 438 logfile $XDG_CACHE_HOME/msmtp/msmtp.log 439 EOF 440 } 441 442 pm_ask() { 443 case ${pass_prog##*/} in 444 pass) pass insert "$pmt" 445 password_command="pass show $pmt" ;; 446 pash) trap 'delete $title; rm -f _' EXIT INT 447 sed 's/yn "Gen/false "Gen/g' "$(command -v pash)" >_ 448 sh _ add "$pmt" 449 rm -f _; trap 'delete $title' EXIT INT 450 password_command="pash show $pmt" ;; 451 pm) pass=$(prompt_noecho "Enter your password") 452 pass2=$(prompt_noecho "Enter your password again") 453 if [ "$pass" = "$pass2" ]; then 454 pm add "$pmt" <<EOF 455 $pass 456 EOF 457 password_command="pm show $pmt" 458 else 459 err "Passwords don't match" 460 return 1 461 fi 462 esac 463 } 464 465 pm_del() { 466 case ${pass_prog##*/} in 467 pass) pass rm -f "$pmt" ;; 468 pash) yes | pash del "$pmt" ;; 469 pm) pm del "$pmt" 470 esac >/dev/null 2>&1 ||: 471 } 472 473 test_connection() { 474 mkdir -p "$MAILDIR/$title" 475 476 # Since we are "Flattening" the inbox structure by replacing '/' with '.', 477 # we need to also replace it in the 'mbsync -l' output by hand. See the 478 # Flatten section on mbsync(1). 479 { mbsync -l "$title" || { 480 err "Log-on not successful." \ 481 "It seems that either you have inputted the wrong password or server" \ 482 "settings, or there are requirements for your account out of the" \ 483 "control of mu-wizard." 484 delete "$title" 485 kill 0 486 } } | sed 's|/|.|g' > /tmp/mbsync-boxes 487 488 while read -r dir; do mkdir -p "$MAILDIR/$title/${dir#/}"; done < /tmp/mbsync-boxes 489 490 drafts=$(sed -n '/[Dd][Rr][Aa][Ff][Tt][Ss]/{p;q}' /tmp/mbsync-boxes) 491 trash=$(sed -n '/[Tt][Rr][Aa][Ss][Hh]/{p;q}' /tmp/mbsync-boxes) 492 inbox=$(sed -n '/[Ii][Nn][Bb][Oo][Xx]/{p;q}' /tmp/mbsync-boxes) 493 sent=$(sed -n '/[Ss][Ee][Nn][Tt]/{p;q}' /tmp/mbsync-boxes) 494 495 rm -f /tmp/mbsync-boxes 496 } 497 498 get_domains() { 499 if [ -f "$confdir/domains.csv" ]; then 500 { [ -f "$sharedir/domains.csv" ] && cat "$sharedir/domains.csv" 501 cat "$confdir/domains.csv" ;} | sort -uo "$confdir/domains.csv" 502 else 503 if [ -f "$sharedir/domains.csv" ]; then 504 cat "$sharedir/domains.csv"; fi > "$confdir/domains.csv" 505 fi 506 } 507 508 set_mbsync() { 509 cat <<EOF >> "$HOME/.mbsyncrc" 510 IMAPStore $title-remote 511 Host $imap 512 Port $iport 513 User $login 514 PassCmd "$password_command" 515 SSLType $ssltype 516 CertificateFile $sslcert 517 518 MaildirStore $title-local 519 Subfolders Verbatim 520 Path ~/.local/share/mail/$title/ 521 Inbox ~/.local/share/mail/$title/INBOX 522 Flatten . 523 524 Channel $title 525 Expunge Both 526 $isync_far :$title-remote: 527 $isync_near :$title-local: 528 Patterns * !"[Gmail]/All Mail" 529 Create Both 530 SyncState * 531 MaxMessages ${maxmes:-0} 532 ExpireUnread no 533 # End profile 534 535 EOF 536 } 537 538 set_msmtp() { 539 [ -f "$config_home/msmtp/config" ] || msmtp_header 540 541 cat <<EOF >> "$config_home/msmtp/config" 542 account $title 543 host $smtp 544 port $sport 545 from $fulladdr 546 user $login 547 passwordeval "$password_command" 548 $starttlsoff 549 # End profile 550 551 EOF 552 } 553 554 set_accountlisp() { 555 cat <<EOF > "$accountdir/$title.el" 556 (add-to-list 'mu4e-contexts 557 (make-mu4e-context 558 :name "$title" 559 :match-func (lambda (msg) 560 (when msg 561 (mu4e-message-contact-field-matches msg 562 :to "$fulladdr"))) 563 :vars '((user-mail-address . "$fulladdr") 564 (message-sendmail-extra-arguments . ("-a" "$title")) 565 (mu4e-sent-messages-behavior . $msg_behavior) 566 (mu4e-drafts-folder . "/$title/${drafts:=Drafts}") 567 (mu4e-trash-folder . "/$title/${trash:=Trash}") 568 (mu4e-sent-folder . "/$title/${sent:=Sent}") 569 (mu4e-maildir-shortcuts . (("/$title/${inbox:-INBOX}" . ?i) 570 ("/$title/$drafts" . ?d) 571 ("/$title/${sent:-Sent}" . ?s))) 572 (user-full-name . "$realname")))) 573 EOF 574 575 } 576 577 get_information() { 578 for file in /etc/ssl/certs/ca-certificates.crt /etc/pki/tls/certs/ca-bundle.crt \ 579 /etc/certificates/cert.pem /etc/ssl/ca-bundle.pem /etc/ssl/cert.pem \ 580 /etc/pki/tls/cacert.pem /usr/share/ca-certificates; do 581 [ -r "$file" ] || continue 582 sslcert=$file; break 583 done 584 585 : "${fulladdr:=$(prompt_required Email \ 586 "Insert the email address that you want to configure for mu4e")}" 587 588 # Check the override directory for possible alterations of the 589 # configuration. If the override is found on the configuration dirctory, it 590 # will be used instead. 591 [ "$nooverride" ] || 592 for file in "$confdir/overrides/"* "$sharedir/overrides/"*; do 593 [ -f "$sharedir/overrides/${fulladdr##*@}" ] && { 594 domain=${fulladdr##*@} 595 # shellcheck disable=1090 596 . "$sharedir/overrides/$domain" 597 break 598 } 599 done 600 601 [ "$nocheck" ] || [ "$domain" ] || 602 while IFS=, read -r domain imap iport smtp sport; do 603 case "$domain" in "${fulladdr##*@}") break; esac 604 done < "$confdir/domains.csv" 605 606 if [ "$domain" = "${fulladdr##*@}" ]; then 607 out "Your domain information was found!'" \ 608 "The following information will be used:" ""\ 609 "Domain: $domain" \ 610 "IMAP: $imap:$iport" \ 611 "SMTP: $smtp:$sport" 612 else 613 : "${imap:="$(prompt Server "Insert the IMAP server for your email provider" \ 614 '(excluding the port number)')"}" 615 616 : "${iport:=$(prompt Port \ 617 "What is your server's IMAP port number? (Usually 993)")}" 618 619 : "${smtp:=$(prompt Server "Insert the SMTP server for your email provider" \ 620 "(excluding the port number)")}" 621 622 : "${sport:=$(prompt Port \ 623 "What is your server's SMTP port number? (Usually 587)")}" 624 fi 625 626 627 : "${realname:=$(prompt "Real name" \ 628 "Enter your full name you want to be identified on this account.")}" 629 630 : "${title:=$(prompt_required "Account name" \ 631 "Enter a short, one-word identifier for this email account that will" \ 632 "distinguish them from any other accounts you add")}" 633 634 while in_profiles "$title"; do 635 out "The title '$title' is already used." 636 title=$(prompt_required "Account name" \ 637 "Enter a short, one-word identifier for this email account that will" \ 638 "distinguish them from any other accounts you add") 639 done 640 641 : "${login:=$(prompt "Login" \ 642 "If your account has a special username different from your address," \ 643 "insert it now. Otherwise leave this blank.")}" 644 645 # If login is unspecified, use the full address 646 [ "${login:-1}" = 1 ] && login="$fulladdr" 647 648 : "${maxmes=$(prompt "Maximum messages" \ 649 "If you want to limit the number of messages kept offline to a number," \ 650 "enter it below. Otherwise leave this blank.")}" 651 652 case "$sport" in 465) starttlsoff="tls_starttls off"; esac 653 } 654 655 main() { 656 pass_prog=${MUW_PWM:-$(command -v pass || command -v pash || command -v pm)} || 657 die "No applicable password manager found." 658 command -v mbsync >/dev/null || die "mbsync must be installed for mu-wizard to work." 659 660 sharedir=/usr/share/mu-wizard 661 ssltype=IMAPS 662 msg_behavior=sent 663 664 mkdir -p "${config_home:=${XDG_CONFIG_HOME:-$HOME/.config}}" \ 665 "${confdir:=$config_home/mu4e}" \ 666 "${cac_dir:=${XDG_CACHE_HOME:-$HOME/.cache}}" \ 667 "${accountdir:=$confdir/accounts}" \ 668 "${MAILDIR:=${XDG_DATA_HOME:-$HOME/.local/share}/mail}" 669 670 export MAILDIR 671 672 # isync > 1.4.0 depracates the terms Master/Slave, and replaces them with 673 # Far/Near. Check the version by looking at 'mbsync --version' output and 674 # use the newer syntax if available. 675 isync_version=$(mbsync --version) 676 isync_version=${isync_version##* } isync_version=${isync_version%.*} 677 if [ "${isync_version%%.*}" -gt 1 ] || [ "${isync_version#*.}" -ge 4 ]; then 678 isync_far=Far isync_near=Near 679 else 680 isync_far=Master isync_near=Slave 681 fi 682 683 eval "$(getoptions parser_definition parse)" 684 parse "$@"; eval set -- "$REST" 685 action=$1; shift 686 687 case $action in 688 a|add) 689 eval "$(getoptions parser_definition_add parse)" 690 parse "$@"; eval set -- "$REST" 691 692 # If any domain information is provided, disable checking domains.csv and 693 # overrides. 694 test -n "$imap" -o -n "$iport" -o -n "$smtp" -o -n "$sport" && 695 nocheck=1 nooverride=1 696 get_domains 697 get_information 698 trap 'delete $title' INT EXIT 699 pmt="muw/$fulladdr" 700 while :; do pm_del; pm_ask && break; done 701 set_mbsync 702 test_connection 703 set_msmtp 704 set_accountlisp 705 [ -f "$confdir/mu4e-config.el" ] || cp "$sharedir/mu4e-config.el" "$confdir/mu4e-config.el" 706 707 # We add user's personal domain information to the config directory, 708 # so even if the user doesn't want to share the information in a git 709 # repository, they can have it themselves when they may need it. 710 711 printf '%s,%s,%s,%s,%s' "${fulladdr##*@}" "$imap" "$iport" "$smtp" "$sport" | 712 sort -uo "$confdir/domains.csv" "$confdir/domains.csv" - 713 trap - INT EXIT 714 out "All done. You can now run '${0##*/} sync $title' in order to sync this account." 715 ;; 716 l|list) 717 eval "$(getoptions parser_definition_list parse)" 718 parse "$@"; eval set -- "$REST" 719 get_profiles 720 ;; 721 d|delete) 722 eval "$(getoptions parser_definition_delete parse)" 723 parse "$@"; eval set -- "$REST" 724 [ "$1" ] || { 725 get_profiles 726 set -- "$(prompt Profile "Pick a profile to be deleted")" 727 } 728 for title; do 729 in_profiles "$title" || die "Profile '$title' doesn't exist." 730 yesno "Are you sure you want to delete '$title'?" || exit 1 731 delete "$title" 732 done 733 ;; 734 p|purge) 735 eval "$(getoptions parser_definition_purge parse)" 736 parse "$@"; eval set -- "$REST" 737 yesno "Are you sure you want to delete all account data?" || exit 1 738 rm -rf "$HOME/.mbsyncrc" "$confdir" "$config_home/msmtp" 739 ;; 740 s|sync) 741 lastsync='' 742 syncfile=$cac_dir/muw-lastsync 743 mu_format="$(printf 'f\ts')" 744 eval "$(getoptions parser_definition_sync parse)" 745 parse "$@"; eval set -- "$REST" 746 pgrep -x mbsync >/dev/null && die "mbsync is already running." 747 [ "$1" ] || eval set -- "$(get_profiles eval)" 748 for acc; do in_profiles "$acc" || die "Invalid account: '$acc'"; done 749 for acc; do mbsync "${acc##*/}" & done; wait 750 for acc; do 751 newcount=$( 752 if [ -f "$syncfile" ]; then 753 find "$MAILDIR/$acc/"*/new -type f -newer "$syncfile" 754 else 755 find "$MAILDIR/$acc/"*/new -type f 756 fi | wc -l 757 ) 758 [ "$newcount" -gt 0 ] && 759 notify "mu-wizard" "$newcount new mail in '$acc' account." 760 done 761 762 [ -f "$syncfile" ] && 763 lastsync=d:$(date -r "$syncfile" "+%Y-%m-%dT%H:%M..") 764 765 touch "$syncfile" 766 767 # Update mu index, first try using Emacs and fallback to using mu itself 768 emacsclient -e "(mu4e-update-index)" >/dev/null 2>&1 || mu index 769 770 # Send notification for new unread mails. Here is an explanation of 771 # what this does: 772 # 773 # --skip-dups - Skip duplicates 774 # 'g:u' - Find unread email 775 # '$lastsync' - Received after the last synchronization 776 # -s date --reverse - Reverse the emails by date 777 # -f "$format" - Get 'from' and 'subject' fields, tab seperated 778 mu find --skip-dups g:u "$lastsync" -s d --reverse -f "$mu_format" 2>/dev/null | 779 while IFS=$(printf '\t') read -r from subject; do 780 781 # Only display the name, not the email address. Remove 782 # trailing whitespace, and quotation marks if there are any. 783 from=${from%%\<*} from=${from%"${from##*[!\ ]}"} 784 from=${from#\"} from=${from%\"} 785 786 notify "$from" "$subject" 787 done 788 ;; 789 mu-init) 790 eval "$(getoptions parser_definition_mu parse)" 791 parse "$@"; set -- 792 for account in "$accountdir/"*.el; do 793 [ -f "$account" ] || continue 794 set -- "$@" "--my-address=$(sed -n 's|"[^"]*$||;/user-mail-address/{s|^.*"||p;q}' "$account")" 795 done 796 [ "$1" ] || die "No address could be found, did you add any accounts?" 797 mu init -m "$MAILDIR" "$@" 798 799 # The 'index' variable is declared and assigned by the parser 800 # shellcheck disable=2154 801 [ -z "$index" ] || mu index 802 ;; 803 data) 804 eval "$(getoptions parser_definition_data parse)" 805 parse "$@"; eval set -- "$REST" 806 printf '%s\n' "$sharedir" 807 esac 808 } 809 810 main "$@"