mirror of
https://github.com/docker-mailserver/docker-mailserver.git
synced 2025-08-04 01:55:29 +02:00
refactor: CLI commands for database management (#2654)
See the associated PR for more detailed commentary on specific changes. ### Commands refactored: - User (**All:** add / list / update / del + _dovecot-master variants_) - Quota (**All:** set / del) - Virtual Alias (**All:** add / list /del) - Relay (**All:** add-relayhost / add-sasl / exclude-domain) ### Overall changes involve: - **Fairly common structure:** - `_main` method at the top provides an overview of logical steps: - After all methods are declared beneath it (_and imported from the new `helpers/database/db.sh`_), the `_main` is called at the bottom of the file. - `delmailuser` additionally processes option support for `-y` prior to calling `_main`. - `__usage` is now consistent with each of these commands, along with the `help` command. - Most logic delegated to new helper scripts. Some duplicate content remains on the basis that it's low-risk to maintenance and avoids less hassle to jump between files to check a single line, usually this is arg validation. - Error handling should be more consistent, along with var names (_no more `USER`/`EMAIL`/`FULL_EMAIL` to refer to the same expected value_). - **Three new management scripts** (in `helpers/database/manage/`) using a common structure for managing changes to their respective "Database" config file. - `postfix-accounts.sh` unified not only add and update commands, but also all the dovecot-master versions, a single password call for all 4 of them, with a 5th consumer of the password prompt from the relay command `addsaslpassword`. - These scripts delegate actual writes to `helpers/database/db.sh` which provides a common API to support the changes made. - This is more verbose/complex vs the current inline operations each command currently has, as it provides generic support instead of slightly different variations being maintained, along with handling some edge cases that existed and would lead to bugs (notably substring matches). - Centralizing changes here seems wiser than scattered about. I've tried to make it easy to grok, hopefully it's not worse than the current situation. - List operations were kept in their respective commands, `db.sh` is only really managing writes. I didn't see a nice way for removing the code duplication for list commands as the duplication was fairly minimal, especially for `listalias` and `listdovecotmasteruser` which were quite simple in their differences in the loop body. - `listmailuser` and `delmailuser` also retain methods exclusive to respective commands, I wasn't sure if there was any benefit to move those, but they were refactored.
This commit is contained in:
parent
428477a878
commit
57aeb6db2a
22 changed files with 1102 additions and 503 deletions
|
@ -1,161 +1,130 @@
|
|||
#! /bin/bash
|
||||
|
||||
# shellcheck disable=SC2094
|
||||
# ? This is done to ignore the message "Make sure not to read and write
|
||||
# ? the same file in the same pipeline", which is a result of ${DATABASE}
|
||||
# ? being used below. (This disables the message file-wide.)
|
||||
|
||||
# shellcheck source=../scripts/helpers/index.sh
|
||||
source /usr/local/bin/helpers/index.sh
|
||||
|
||||
DATABASE='/tmp/docker-mailserver/postfix-accounts.cf'
|
||||
ALIAS_DATABASE='/tmp/docker-mailserver/postfix-virtual.cf'
|
||||
QUOTA_DATABASE='/tmp/docker-mailserver/dovecot-quotas.cf'
|
||||
MAILDEL='false'
|
||||
function _main
|
||||
{
|
||||
[[ ${1:-} == 'help' ]] && { __usage ; exit 0 ; }
|
||||
# Tests expect early exit without error if no DB exists:
|
||||
[[ -s ${DATABASE_ACCOUNTS} ]] || return 0
|
||||
|
||||
# Validate Parameters:
|
||||
[[ -z ${*} ]] && { __usage ; _exit_with_error 'No account specified' ; }
|
||||
_maildel_request_if_missing
|
||||
|
||||
# TODO: May want to lock all database files prior to loop? (DATABASE_ACCOUNTS DATABASE_QUOTA DATABASE_VIRTUAL)
|
||||
# NOTE: Present lock method locks the original sourcing script itself.
|
||||
_create_lock
|
||||
|
||||
# Actual command to perform:
|
||||
for MAIL_ACCOUNT in "${@}"
|
||||
do
|
||||
_account_should_already_exist
|
||||
|
||||
[[ ${MAILDEL} -eq 1 ]] && _remove_maildir "${MAIL_ACCOUNT}"
|
||||
|
||||
_manage_virtual_aliases_delete '_' "${MAIL_ACCOUNT}" \
|
||||
|| _exit_with_error "Aliases for '${MAIL_ACCOUNT}' could not be deleted"
|
||||
|
||||
_manage_dovecot_quota_delete "${MAIL_ACCOUNT}" \
|
||||
|| _exit_with_error "Quota for '${MAIL_ACCOUNT}' could not be deleted"
|
||||
|
||||
# Performed last, avoids breaking command if a prior failure occurred
|
||||
_manage_accounts_delete "${MAIL_ACCOUNT}" \
|
||||
|| _exit_with_error "'${MAIL_ACCOUNT}' could not be deleted"
|
||||
|
||||
_log 'info' "'${MAIL_ACCOUNT}' and associated data deleted"
|
||||
done
|
||||
}
|
||||
|
||||
function __usage
|
||||
{
|
||||
printf '%s' "${PURPLE}DELMAILUSER${RED}(${YELLOW}8${RED})
|
||||
printf '%s' "${PURPLE}delmailuser${RED}(${YELLOW}8${RED})
|
||||
|
||||
${ORANGE}NAME${RESET}
|
||||
delmailuser - delete a user and related data
|
||||
|
||||
${ORANGE}SYNOPSIS${RESET}
|
||||
./setup.sh email del [ OPTIONS ] { <MAIL ADDRESS> [<MAIL ADDRESS>${RED}...${RESET}] ${RED}|${RESET} help }
|
||||
|
||||
${ORANGE}DESCRIPTION${RESET}
|
||||
Delete a mail user, aliases, quotas and mail data.
|
||||
${ORANGE}USAGE${RESET}
|
||||
./setup.sh email del [ OPTIONS ] <MAIL ACCOUNT> [<EXTRA MAIL ACCOUNTS> ${RED}...${RESET} ]
|
||||
|
||||
${ORANGE}OPTIONS${RESET}
|
||||
-y
|
||||
Indicate that ${LWHITE}all mail data${RESET} is to be deleted without another prompt.
|
||||
Skip prompt by approving to ${LWHITE}delete all mail storage${RESET} for the account(s).
|
||||
|
||||
-h
|
||||
Show this help dialogue.
|
||||
${BLUE}Generic Program Information${RESET}
|
||||
help Print the usage information.
|
||||
|
||||
${ORANGE}DESCRIPTION${RESET}
|
||||
Delete a mail account, including associated data (aliases, quotas) and
|
||||
optionally the mailbox storage for that account.
|
||||
|
||||
${ORANGE}EXAMPLES${RESET}
|
||||
${LWHITE}./setup.sh email del woohoo@some-domain.org${RESET}
|
||||
Delete the mail user, quotas and aliases, but ask
|
||||
again whether mailbox data should be deleted.
|
||||
${LWHITE}./setup.sh email del user@example.com${RESET}
|
||||
Delete the mail account 'user@example.com' and associated data,
|
||||
but ask if mailbox data should also be deleted.
|
||||
|
||||
${LWHITE}./setup.sh email del -y test@domain.com test@domain.com${RESET}
|
||||
Delete all mail data for the users 'test' and do not
|
||||
prompt to ask if all mail data should be deleted.
|
||||
${LWHITE}./setup.sh email del -y user@example.com extra-user@example.com${RESET}
|
||||
Delete the two mail accounts requested, their associated data and
|
||||
delete the mailbox data for both accounts without asking.
|
||||
|
||||
${ORANGE}EXIT STATUS${RESET}
|
||||
Exit status is 0 if command was successful, and 1 if there was an error.
|
||||
Exit status is 0 if command was successful. If wrong arguments are provided
|
||||
or arguments contain errors, the script will exit early with exit status 1.
|
||||
|
||||
"
|
||||
}
|
||||
|
||||
if [[ ${1} == 'help' ]]
|
||||
then
|
||||
__usage
|
||||
exit 0
|
||||
fi
|
||||
function _parse_options
|
||||
{
|
||||
while getopts ":yY" OPT
|
||||
do
|
||||
case "${OPT}" in
|
||||
( 'y' | 'Y' )
|
||||
MAILDEL=1
|
||||
;;
|
||||
|
||||
while getopts ":yYh" OPT
|
||||
do
|
||||
case "${OPT}" in
|
||||
( 'y' | 'Y' )
|
||||
MAILDEL=true
|
||||
;;
|
||||
( * )
|
||||
__usage
|
||||
_exit_with_error "The option '${OPT}' is unknown"
|
||||
;;
|
||||
|
||||
( 'h' )
|
||||
__usage
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
( * )
|
||||
__usage
|
||||
_exit_with_error "The option '${OPT}' is unknown"
|
||||
;;
|
||||
function _maildel_request_if_missing
|
||||
{
|
||||
if [[ ${MAILDEL} -eq 0 ]]
|
||||
then
|
||||
local MAILDEL_CHOSEN
|
||||
read -r -p "Do you want to delete the mailbox as well (removing all mails)? [Y/n] " MAILDEL_CHOSEN
|
||||
|
||||
esac
|
||||
done
|
||||
# TODO: Why would MAILDEL be set to true if MAILDEL_CHOSEN is empty?
|
||||
if [[ ${MAILDEL_CHOSEN} =~ (y|Y|yes|Yes) ]] || [[ -z ${MAILDEL_CHOSEN} ]]
|
||||
then
|
||||
MAILDEL=1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
function _remove_maildir
|
||||
{
|
||||
local MAIL_ACCOUNT=${1}
|
||||
|
||||
local LOCAL_PART="${MAIL_ACCOUNT%@*}"
|
||||
local DOMAIN_PART="${MAIL_ACCOUNT#*@}"
|
||||
local MAIL_ACCOUNT_STORAGE_DIR="/var/mail/${DOMAIN_PART}/${LOCAL_PART}"
|
||||
|
||||
[[ ! -d ${MAIL_ACCOUNT_STORAGE_DIR} ]] && _exit_with_error "Mailbox directory '${MAIL_ACCOUNT_STORAGE_DIR}' does not exist"
|
||||
|
||||
_log 'info' "Deleting Mailbox: '${MAIL_ACCOUNT_STORAGE_DIR}'"
|
||||
rm -R "${MAIL_ACCOUNT_STORAGE_DIR}" || _exit_with_error 'Mailbox could not be deleted'
|
||||
# Remove parent directory too if it's empty:
|
||||
rmdir "/var/mail/${DOMAIN_PART}" &>/dev/null
|
||||
}
|
||||
|
||||
# Support for optional maildir removal:
|
||||
MAILDEL=0
|
||||
_parse_options "${@}"
|
||||
# Remove options before passing over parameters to _main:
|
||||
shift $((OPTIND-1))
|
||||
|
||||
[[ -z ${*} ]] && { __usage ; _exit_with_error 'No user specified' ; }
|
||||
[[ -s ${DATABASE} ]] || exit 0
|
||||
|
||||
if ! ${MAILDEL}
|
||||
then
|
||||
read -r -p "Do you want to delete the mailbox as well (removing all mails)? [Y/n] " MAILDEL_CHOSEN
|
||||
if [[ ${MAILDEL_CHOSEN} =~ (y|Y|yes|Yes) ]] || [[ -z ${MAILDEL_CHOSEN} ]]
|
||||
then
|
||||
MAILDEL=true
|
||||
fi
|
||||
fi
|
||||
|
||||
_create_lock # Protect config file with lock to avoid race conditions
|
||||
|
||||
for EMAIL in "${@}"
|
||||
do
|
||||
ERROR=false
|
||||
|
||||
USER="${EMAIL%@*}"
|
||||
DOMAIN="${EMAIL#*@}"
|
||||
|
||||
# ${EMAIL} must not contain /s and other syntactic characters
|
||||
UNESCAPED_EMAIL="${EMAIL}"
|
||||
EMAIL=$(_escape "${EMAIL}")
|
||||
|
||||
if [[ -f ${DATABASE} ]]
|
||||
then
|
||||
if ! sedfile --strict -i "/^${EMAIL}|/d" "${DATABASE}"
|
||||
then
|
||||
_log 'error' "'${UNESCAPED_EMAIL}' couldn't be deleted in '${DATABASE}'"
|
||||
ERROR=true
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -f ${ALIAS_DATABASE} ]]
|
||||
then
|
||||
# delete all aliases where the user is the only recipient( " ${EMAIL}" )
|
||||
# delete user only for all aliases that deliver to multiple recipients ( ",${EMAIL}" "${EMAIL,}" )
|
||||
if sed -i \
|
||||
-e "/ ${EMAIL}$/d" -e "s/,${EMAIL}//g" -e "s/${EMAIL},//g" \
|
||||
"${ALIAS_DATABASE}"
|
||||
then
|
||||
_log 'info' "'${UNESCAPED_EMAIL}' and potential aliases deleted"
|
||||
else
|
||||
_log 'error' "Aliases for '${UNESCAPED_EMAIL}' couldn't be deleted in '${ALIAS_DATABASE}'"
|
||||
ERROR=true
|
||||
fi
|
||||
fi
|
||||
|
||||
# remove quota directives
|
||||
if [[ -f ${QUOTA_DATABASE} ]]
|
||||
then
|
||||
if ! sedfile --strict -i -e "/^${EMAIL}:.*$/d" "${QUOTA_DATABASE}"
|
||||
then
|
||||
_log 'warn' "Quota for '${UNESCAPED_EMAIL}' couldn't be deleted in '${QUOTA_DATABASE}'"
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! ${MAILDEL}
|
||||
then
|
||||
echo "Leaving the mailbox untouched.
|
||||
If you want to delete it at a later point,
|
||||
use 'sudo docker exec mailserver rm -R /var/mail/${DOMAIN}/${USER}'"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ -e "/var/mail/${DOMAIN}/${USER}" ]]
|
||||
then
|
||||
if rm -R "/var/mail/${DOMAIN}/${USER}"
|
||||
then
|
||||
_log 'info' 'Mailbox deleted'
|
||||
else
|
||||
_log 'error' 'Mailbox could not be deleted'
|
||||
ERROR=true
|
||||
fi
|
||||
rmdir "/var/mail/${DOMAIN}" &>/dev/null
|
||||
else
|
||||
log 'error' "Mailbox directory '/var/mail/${DOMAIN}/${USER}' did not exist"
|
||||
ERROR=true
|
||||
fi
|
||||
|
||||
${ERROR} && _exit_with_error 'See the messages above.'
|
||||
done
|
||||
|
||||
exit 0
|
||||
_main "${@}"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue