mu-wizard

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

muw (30787B)


      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 warn() { printf '\033[1;33mWARNING\033[m %s\n' "$@" >&2 ;}
    335 info() { printf '\033[1;36mINFO\033[m %s\n' "$@" >&2 ;}
    336 err() { printf '\033[1mERROR \033[m%s\n' "$@" >&2 ;}
    337 die() { err "$@"; exit 1 ;}
    338 version() { printf 'mu-wizard version: %s\n' @VERSION@ ;}
    339 
    340 notify() {
    341     case ${notify_method##*/} in
    342         notify-send) "$notify_method" -i mail-unread -a "mu-wizard" "$@" ;;
    343         herbe)       "$notify_method" "$@" ;;
    344         null) info "No notification method found, disabling notifications"; notify_method=disabled ;;
    345         disabled) ;;
    346         *) warn "Notification method '$notify_method' unknown"; notify_method=disabled
    347     esac
    348 }
    349 prompt() {
    350     # The first argument is used as the prompt, and will print as 'prompt: '.
    351     # Any other arguments will be printed before prompt if they exist.
    352     prompt=$1 ans=
    353     [ "$2" ] && { shift 1; out "$@" ;}
    354     printf '%s: ' "$prompt" >&2
    355     read -r ans || return
    356     printf %s "$ans"
    357 }
    358 prompt_required() {
    359     promptout=''; promptout=$(prompt "$@")
    360     until [ "$promptout" ]; do
    361         out "This is required."
    362         promptout=$(prompt "$@")
    363     done
    364     printf %s "$promptout"
    365 }
    366 prompt_noecho() {
    367     # This is the same with the prompt() function except that it doesn't echo
    368     # the user input to the terminal. It can be used to ask for passwords and
    369     # other secret information.
    370     stty=$(stty -g); stty -echo
    371     prompt "$@"; printf '\n' >&2
    372     stty "$stty"
    373 }
    374 
    375 getbut() {
    376     # This function can be used to get a single button input. No arguments
    377     # necessary.
    378     stty=$(stty -g); stty -icanon -echo
    379     dd bs=1 count=1 2>/dev/null
    380     stty "$stty"
    381 }
    382 
    383 yesno() {
    384     # Function to ask the user yes/no questions. Returns 0 if the button
    385     # received is Y/y, Returns 1 if the button received is N/n, and returns
    386     # 2 if any other button is received. If the noconfirm option is set from
    387     # the options, do not ask anything.
    388     #
    389     # The 'noconfirm' variable is declared and assigned by the option parser.
    390     # shellcheck disable=2154
    391     [ "$noconfirm" ] && return 0
    392     printf '%s\n' "$@"; printf 'y/n: '
    393     ans=$(getbut)
    394     printf '%s\n' "$ans" >&2
    395     case "$ans" in Y|y) return 0;; N|n) return 1; esac; return 2
    396 }
    397 
    398 delete() {
    399     rm -f "$accountdir/$1.el"
    400     sed_i "/^IMAPStore $1-remote\$/,/^# End profile\$/d" "$HOME/.mbsyncrc"
    401     sed_i "/^account $1\$/,/^# End profile\$/d"          "$config_home/msmtp/config"
    402     rm -f /tmp/mbsync-boxes
    403 }
    404 
    405 in_profiles() {
    406     # Check whether the title is already a profile.
    407     while read -r profile; do
    408         [ "$profile" = "$1" ] && return 0
    409     done <<EOF
    410 $(get_profiles)
    411 EOF
    412     return 1
    413 }
    414 
    415 sed_i() {
    416     # POSIX compliant 'sed -i' like function. This can be only used for a single
    417     # file, and the filename should always come first.
    418     script='' sedf='--' optesc='' suffix=tmp.$$
    419 
    420     while getopts nre:Ef: flag; do
    421         case $flag in
    422             E|r|n) sedf="-${sedf##*-}$flag" optesc=-- ;;
    423             e) script=$(printf '%s\n%s\n' "$script" "$OPTARG") ;;
    424             f) script=$(printf '%s\n%s\n' "$script" "$(cat "$OPTARG")") ;;
    425             *) return 1
    426         esac
    427     done
    428 
    429     shift "$((OPTIND - 1))"
    430 
    431     [ "$script" ] || { script=$1; shift ;}
    432 
    433     for file; do
    434         # Create traps for removing temporary files on failure
    435         trap 'rm -f "$file.$suffix"' EXIT
    436         trap 'rm -f "$file.$suffix"; exit 1' INT
    437 
    438         # Save the edited stream in the temporary file
    439         sed "$sedf" $optesc "$script" "$file" > "$file.$suffix"
    440 
    441         # Pipe back the contents of the temporary file so that we don't have any
    442         # permission related issues.
    443         cat "$file.$suffix" > "$file"
    444 
    445         # Remove the temporary file.
    446         rm -f "$file.$suffix"
    447 
    448         # Restore the trap
    449         trap - INT EXIT
    450     done
    451 }
    452 
    453 get_profiles() {
    454     # Function to get all available profiles
    455     eval=$1
    456     set --
    457     for profile in "$accountdir/"*.el; do
    458         profile=${profile##*/}
    459         set -- "$@" "${profile%.el}"
    460     done
    461     if [ "$eval" ]; then
    462         printf "'%s' " "$@"
    463     else
    464         printf '%s\n' "$@"
    465     fi
    466 }
    467 
    468 msmtp_header() {
    469     mkdir -p "$config_home/msmtp" \
    470              "${XDG_CACHE_HOME:=$HOME/.cache}/msmtp"
    471     cat <<EOF > "$config_home/msmtp/config"
    472 defaults
    473 auth on
    474 tls on
    475 tls_trust_file $cacert
    476 logfile $XDG_CACHE_HOME/msmtp/msmtp.log
    477 EOF
    478 }
    479 
    480 pm_ask() {
    481     case ${pass_prog##*/} in
    482         pass) pass insert "$pmt"
    483               password_command="pass show $pmt" ;;
    484         pash) trap 'delete $title; rm -f _' EXIT INT
    485               sed 's/yn "Gen/false "Gen/g' "$(command -v pash)" >_
    486               sh _ add "$pmt"
    487               rm -f _; trap 'delete $title' EXIT INT
    488               password_command="pash show $pmt" ;;
    489         pm)   pass=$(prompt_noecho "Enter your password")
    490               pass2=$(prompt_noecho "Enter your password again")
    491               if [ "$pass" = "$pass2" ]; then
    492                   pm add "$pmt" <<EOF
    493 $pass
    494 EOF
    495                   password_command="pm show $pmt"
    496               else
    497                   err "Passwords don't match"
    498                   return 1
    499               fi
    500     esac
    501 }
    502 
    503 pm_del() {
    504     case ${pass_prog##*/} in
    505         pass) pass rm -f "$pmt" ;;
    506         pash) yes | pash del "$pmt" ;;
    507         pm)   pm del "$pmt"
    508     esac >/dev/null 2>&1 ||:
    509 }
    510 
    511 test_connection() {
    512     mkdir -p "$MAILDIR/$title"
    513 
    514     # Since we are "Flattening" the inbox structure by replacing '/' with '.',
    515     # we need to also replace it in the 'mbsync -l' output by hand. See the
    516     # Flatten section on mbsync(1).
    517     { mbsync -l "$title" || {
    518           err "Log-on not successful." \
    519           "It seems that either you have inputted the wrong password or server" \
    520           "settings, or there are requirements for your account out of the" \
    521           "control of mu-wizard."
    522           delete "$title"
    523           kill 0
    524     } } | sed 's|/|.|g' > /tmp/mbsync-boxes
    525 
    526     while read -r dir; do mkdir -p "$MAILDIR/$title/${dir#/}"; done < /tmp/mbsync-boxes
    527 
    528     drafts=$(grep -i drafts /tmp/mbsync-boxes | sed 1q)
    529     trash=$(grep  -i trash  /tmp/mbsync-boxes | sed 1q)
    530     inbox=$(grep  -i inbox  /tmp/mbsync-boxes | sed 1q)
    531     sent=$(grep   -i sent   /tmp/mbsync-boxes | sed 1q)
    532 
    533     rm -f /tmp/mbsync-boxes
    534 }
    535 
    536 get_domains() {
    537     if [ -f "$confdir/domains.csv" ]; then
    538         { [ -f "$sharedir/domains.csv" ] && cat "$sharedir/domains.csv"
    539           cat "$confdir/domains.csv" ;} | sort -uo "$confdir/domains.csv"
    540     else
    541         if [ -f "$sharedir/domains.csv" ]; then
    542             cat "$sharedir/domains.csv"; fi > "$confdir/domains.csv"
    543     fi
    544 }
    545 
    546 set_mbsync() {
    547     cat <<EOF >> "$HOME/.mbsyncrc"
    548 IMAPStore $title-remote
    549 Host $imap
    550 Port $iport
    551 User $login
    552 PassCmd "$password_command"
    553 AuthMechs LOGIN
    554 SSLType $ssltype
    555 CertificateFile $cacert
    556 
    557 MaildirStore $title-local
    558 Subfolders Verbatim
    559 Path ~/.local/share/mail/$title/
    560 Inbox ~/.local/share/mail/$title/INBOX
    561 Flatten .
    562 
    563 Channel $title
    564 Expunge Both
    565 $isync_far  :$title-remote:
    566 $isync_near :$title-local:
    567 Patterns * !"[Gmail]/All Mail"
    568 Create Both
    569 SyncState *
    570 MaxMessages ${maxmes:-0}
    571 ExpireUnread no
    572 # End profile
    573 
    574 EOF
    575 }
    576 
    577 set_msmtp() {
    578     # 'A && B || C' is intentional behaviour here. We want all of these checks
    579     # to pass.
    580     # shellcheck disable=2015
    581     [ -f "$config_home/msmtp/config" ] &&
    582     [ -s "$config_home/msmtp/config" ] || msmtp_header
    583 
    584     cat <<EOF >> "$config_home/msmtp/config"
    585 account $title
    586 host $smtp
    587 port $sport
    588 from $fulladdr
    589 user $login
    590 passwordeval "$password_command"
    591 $starttlsoff
    592 # End profile
    593 
    594 EOF
    595 }
    596 
    597 set_accountlisp() {
    598     cat <<EOF > "$accountdir/$title.el"
    599 (add-to-list 'mu4e-contexts
    600 (make-mu4e-context
    601   :name "$title"
    602   :match-func (lambda (msg)
    603                 (when msg
    604                   (mu4e-message-contact-field-matches msg
    605                      :to "$fulladdr")))
    606   :vars '((user-mail-address                . "$fulladdr")
    607           (message-sendmail-extra-arguments . ("-a" "$title"))
    608           (mu4e-sent-messages-behavior      . $msg_behavior)
    609           (mu4e-drafts-folder               . "/$title/${drafts:=Drafts}")
    610           (mu4e-trash-folder                . "/$title/${trash:=Trash}")
    611           (mu4e-sent-folder                 . "/$title/${sent:=Sent}")
    612           (mu4e-maildir-shortcuts           . (("/$title/${inbox:-INBOX}" . ?i)
    613                                                ("/$title/$drafts"         . ?d)
    614                                                ("/$title/${sent:-Sent}" . ?s)))
    615           (user-full-name                   . "$realname"))))
    616 EOF
    617 
    618 }
    619 
    620 get_information() {
    621 
    622     # Try to find a CA Certificate file. We want to prioritise the '$MUW_CACERT'
    623     # variable here.
    624     if [ "$MUW_CACERT" ]; then
    625         cacert=$MUW_CACERT
    626     else
    627         for file in /etc/ssl/certs/ca-certificates.crt /etc/pki/tls/certs/ca-bundle.crt \
    628                 /etc/certificates/cert.pem /etc/ssl/ca-bundle.pem /etc/ssl/cert.pem \
    629                 /etc/pki/tls/cacert.pem /usr/share/ca-certificates; do
    630             [ -r "$file" ] || continue
    631             cacert=$file; break
    632         done
    633     fi
    634 
    635     # If no certificate file was found, exit and notify the user.
    636     [ -r "$cacert" ] ||
    637         die "No proper CA Certificate file could be found. There are numerous ways of" \
    638             "obtaining it, and those can differ for each distro. You can see the" \
    639             "CA CERTIFICATES section on the muw-add(1) manual page to learn more."
    640 
    641     : "${fulladdr:=$(prompt_required Email \
    642              "Insert the email address that you want to configure for mu4e")}"
    643 
    644     # Check the override directory for possible alterations of the
    645     # configuration. If the override is found on the configuration dirctory, it
    646     # will be used instead.
    647     [ "$nooverride" ] ||
    648     for file in "$confdir/overrides/"* "$sharedir/overrides/"*; do
    649         [ -f "$sharedir/overrides/${fulladdr##*@}" ] && {
    650             domain=${fulladdr##*@}
    651             # shellcheck disable=1090
    652             . "$sharedir/overrides/$domain"
    653             break
    654         }
    655     done
    656 
    657     [ "$nocheck" ] || [ "$domain" ] ||
    658     while IFS=, read -r domain imap iport smtp sport; do
    659         case "$domain" in "${fulladdr##*@}") break; esac
    660     done < "$confdir/domains.csv"
    661 
    662     if [ "$domain" = "${fulladdr##*@}" ]; then
    663         out "Your domain information was found!'" \
    664             "The following information will be used:" ""\
    665             "Domain: $domain" \
    666             "IMAP:   $imap:$iport" \
    667             "SMTP:   $smtp:$sport"
    668     else
    669         : "${imap:="$(prompt Server "Insert the IMAP server for your email provider" \
    670             '(excluding the port number)')"}"
    671 
    672         : "${iport:=$(prompt Port \
    673             "What is your server's IMAP port number? (Usually 993)")}"
    674 
    675         : "${smtp:=$(prompt Server "Insert the SMTP server for your email provider" \
    676             "(excluding the port number)")}"
    677 
    678         : "${sport:=$(prompt Port \
    679             "What is your server's SMTP port number? (Usually 587)")}"
    680     fi
    681 
    682 
    683     : "${realname:=$(prompt "Real name" \
    684         "Enter your full name you want to be identified on this account.")}"
    685 
    686     : "${title:=$(prompt_required "Account name" \
    687         "Enter a short, one-word identifier for this email account that will" \
    688         "distinguish them from any other accounts you add")}"
    689 
    690     while in_profiles "$title"; do
    691         out "The title '$title' is already used."
    692         title=$(prompt_required "Account name" \
    693         "Enter a short, one-word identifier for this email account that will" \
    694         "distinguish them from any other accounts you add")
    695     done
    696 
    697     : "${login:=$(prompt "Login" \
    698         "If your account has a special username different from your address," \
    699         "insert it now. Otherwise leave this blank.")}"
    700 
    701     # If login is unspecified, use the full address
    702     [ "${login:-1}" = 1 ] && login="$fulladdr"
    703 
    704     : "${maxmes=$(prompt "Maximum messages" \
    705         "If you want to limit the number of messages kept offline to a number," \
    706         "enter it below. Otherwise leave this blank.")}"
    707 
    708     case "$sport" in 465) starttlsoff="tls_starttls off"; esac
    709 }
    710 
    711 prepare() {
    712     # Make the initial checks to make sure we have the necessary programs to
    713     # actually run mu-wizard.
    714     pass_prog=${MUW_PWM:-$(command -v pass || command -v pash || command -v pm)} ||
    715         die "No applicable password manager found."
    716     command -v mbsync >/dev/null || die "mbsync must be installed for mu-wizard to work."
    717 
    718     # Set some variables that are used throughout the script.
    719     sharedir=/usr/share/mu-wizard
    720     ssltype=IMAPS
    721     msg_behavior=sent
    722 
    723     # Create configuration and cache directories.
    724     mkdir -p "${config_home:=${XDG_CONFIG_HOME:-$HOME/.config}}" \
    725              "${confdir:=$config_home/mu4e}" \
    726              "${cac_dir:=${XDG_CACHE_HOME:-$HOME/.cache}}" \
    727              "${accountdir:=$confdir/accounts}" \
    728              "${MAILDIR:=${XDG_DATA_HOME:-$HOME/.local/share}/mail}"
    729 
    730     export MAILDIR
    731 
    732     # isync > 1.4.0 depracates the terms Master/Slave, and replaces them with
    733     # Far/Near. Check the version by looking at 'mbsync --version' output and
    734     # use the newer syntax if available.
    735     isync_version=$(mbsync --version)
    736     isync_version=${isync_version##* } isync_version=${isync_version%.*}
    737     if [ "${isync_version%%.*}" -gt 1 ] || [ "${isync_version#*.}" -ge 4 ]; then
    738         isync_far=Far isync_near=Near
    739     else
    740         isync_far=Master isync_near=Slave
    741     fi
    742 
    743     # Set notification program, we support both libnotify, which is the most
    744     # common one out there, and also herbe, which does not depend on dbus. We
    745     # try 'notify-send' first, because its interface is more convenient.
    746     notify_method=${MUW_NOTIFY:-$(
    747                     command -v notify-send ||
    748                     command -v herbe)} ||
    749                     notify_method=null
    750 }
    751 
    752 main() {
    753     prepare
    754 
    755     eval "$(getoptions parser_definition parse)"
    756     parse "$@"; eval set -- "$REST"
    757     action=$1;  shift
    758 
    759     case $action in
    760         a|add)
    761             eval "$(getoptions parser_definition_add parse)"
    762             parse "$@"; eval set -- "$REST"
    763 
    764             # If any domain information is provided, disable checking domains.csv and
    765             # overrides.
    766             test -n "$imap" -o -n "$iport" -o -n "$smtp" -o -n "$sport" &&
    767                 nocheck=1 nooverride=1
    768             get_domains
    769             get_information
    770             trap 'delete $title' INT EXIT
    771             pmt="muw/$fulladdr"
    772             while :; do pm_del; pm_ask && break; done
    773             set_mbsync
    774             test_connection
    775             set_msmtp
    776             set_accountlisp
    777             [ -f "$confdir/mu4e-config.el" ] || cp "$sharedir/mu4e-config.el" "$confdir/mu4e-config.el"
    778 
    779             # We add user's personal domain information to the config directory,
    780             # so even if the user doesn't want to share the information in a git
    781             # repository, they can have it themselves when they may need it.
    782 
    783             printf '%s,%s,%s,%s,%s' "${fulladdr##*@}" "$imap" "$iport" "$smtp" "$sport" |
    784                 sort -uo "$confdir/domains.csv" "$confdir/domains.csv" -
    785             trap - INT EXIT
    786             out "All done. You can now run '${0##*/} sync $title' in order to sync this account."
    787             out "You can also run '${0##*/} mu-init' to easily initialise the database."
    788             ;;
    789         l|list)
    790             eval "$(getoptions parser_definition_list parse)"
    791             parse "$@"; eval set -- "$REST"
    792             get_profiles
    793             ;;
    794         d|delete)
    795             eval "$(getoptions parser_definition_delete parse)"
    796             parse "$@"; eval set -- "$REST"
    797             [ "$1" ] || {
    798                 get_profiles
    799                 set -- "$(prompt Profile "Pick a profile to be deleted")"
    800             }
    801             for title; do
    802                 in_profiles "$title" || die "Profile '$title' doesn't exist."
    803                 yesno "Are you sure you want to delete '$title'?" || exit 1
    804                 delete "$title"
    805             done
    806             ;;
    807         p|purge)
    808             eval "$(getoptions parser_definition_purge parse)"
    809             parse "$@"; eval set -- "$REST"
    810             yesno "Are you sure you want to delete all account data?" || exit 1
    811             rm -rf "$HOME/.mbsyncrc" "$confdir" "$config_home/msmtp"
    812             ;;
    813         s|sync)
    814             lastsync=''
    815             syncfile=$cac_dir/muw-lastsync
    816             mu_format="$(printf 'f\ts')"
    817             eval "$(getoptions parser_definition_sync parse)"
    818             parse "$@"; eval set -- "$REST"
    819             pgrep -x mbsync >/dev/null && die "mbsync is already running."
    820             [ "$1" ] || eval set -- "$(get_profiles eval)"
    821             for acc; do in_profiles "$acc" || die "Invalid account: '$acc'"; done
    822             for acc; do mbsync "${acc##*/}" & done; wait
    823             for acc; do
    824                 newcount=$(
    825                     if [ -f "$syncfile" ]; then
    826                         find "$MAILDIR/$acc/"*/new -type f -newer "$syncfile"
    827                     else
    828                         find "$MAILDIR/$acc/"*/new -type f
    829                     fi | wc -l
    830                 )
    831                 [ "$newcount" -gt 0 ] &&
    832                     notify "mu-wizard" "$newcount new mail in '$acc' account."
    833             done
    834 
    835             [ -f "$syncfile" ] &&
    836                 lastsync=d:$(date -r "$syncfile" "+%Y-%m-%dT%H:%M..")
    837 
    838             touch "$syncfile"
    839 
    840             # Update mu index, first try using Emacs and fallback to using mu itself
    841             emacsclient -e "(mu4e-update-index)" >/dev/null 2>&1 || mu index
    842 
    843             # Send notification for new unread mails. Here is an explanation of
    844             # what this does:
    845             #
    846             # --skip-dups           - Skip duplicates
    847             # 'g:u'                 - Find unread email
    848             # '$lastsync'           - Received after the last synchronization
    849             # -s date --reverse     - Reverse the emails by date
    850             # -f "$format" - Get 'from' and 'subject' fields, tab seperated
    851             mu find --skip-dups g:u "$lastsync" -s d --reverse -f "$mu_format" 2>/dev/null |
    852                 while IFS=$(printf '\t') read -r from subject; do
    853 
    854                     # Only display the name, not the email address. Remove
    855                     # trailing whitespace, and quotation marks if there are any.
    856                     from=${from%%\<*} from=${from%"${from##*[!\ ]}"}
    857                     from=${from#\"} from=${from%\"}
    858 
    859                     notify "$from" "$subject"
    860                 done
    861             ;;
    862         mu-init)
    863             eval "$(getoptions parser_definition_mu parse)"
    864             parse "$@"; set --
    865             for account in "$accountdir/"*.el; do
    866                 [ -f "$account" ] || continue
    867                 set -- "$@" "--my-address=$(sed -n 's|"[^"]*$||;/user-mail-address/{s|^.*"||p;q}' "$account")"
    868             done
    869             [ "$1" ] || die "No address could be found, did you add any accounts?"
    870             mu init -m "$MAILDIR" "$@"
    871 
    872             # The 'index' variable is declared and assigned by the parser
    873             # shellcheck disable=2154
    874             [ -z "$index" ] || mu index
    875             ;;
    876         data)
    877             eval "$(getoptions parser_definition_data parse)"
    878             parse "$@"; eval set -- "$REST"
    879             printf '%s\n' "$sharedir"
    880     esac
    881 }
    882 
    883 main "$@"