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