mu-wizard

shell script to easily configure mu4e accounts on Emacs
git clone git://git.ckyln.com/~cem/mu-wizard
Log | Files | Refs | LICENSE

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 "$@"