§2024-09-26
ls
00_install_global_functions_variables.sh 02_install_prerequisites.sh 04_install_import_keys.sh 06_install_enable_services.sh 08_install_haraka.sh 10_install_wildduck_webmail.sh 12_install_ufw_rules.sh 14_install_start_services.sh hc4Noble.yushei.net-nameserver.txt log
01_install_commits.sh 03_install_check_running_services.sh 05_install_packages.sh 07_install_wildduck.sh 09_install_zone_mta.sh 11_install_nginx.sh 13_install_ssl_certs.sh 15_install_deploy.sh install.sh tee
sudo ./install.sh hc4Noble.yushei.net mail.hc4Noble.yushei.net >log 2>&1 &
¶ 00_install_global_functions_variables.sh `
#! /bin/bash
# These are all the common global variables and global functions.
AUT_HOSTNAME=`hostname`
export AUT_SAFETY=true
export AUT_HOME="${HOME}" # maybe a more robust way?
# COLOR VARIABLES
export RED='\033[0;31m'
export GREEN='\033[0;32m'
export ORANGE='\033[0;33m'
export YELLOW='\033[1;33m'
export BLUE='\033[0;34m'
export NC='\033[0m' # No Color
# GLOBAL FUNCTIONS
fun_get_password(){
# If we are not root, we should aquire the sudo password
if [ `whoami` == 'root' ]
then
PASSWORD=''
else
echo -n "GIMME your password! ($OURNAME):"
read -s PASSWORD
echo -e "\n"
fi
}
export -f fun_get_password
fun_check_password_boolean(){
# Check if the $PASSWORD is good or not
# TODO: if hostname can not be resolved (/etc/hosts misses 127.0.0.1)
# then sudo outputs 'cannot resolve hostname', so this check
# "obviously" fails.
sudo -k #disable sudo timeout
#prime it
echo $PASSWORD | sudo -S echo hello &> /dev/null
local RESULT=$(echo $PASSWORD | sudo -S sudo -n echo hello 2>&1)
if [ "$RESULT" == "hello" ]; then
echo 'Correct password.'
return 0
else
echo 'Wrong password.'
return 1
fi
}
export -f fun_check_password_boolean
fun_check_password(){
if ! fun_check_password_boolean; then
echo -e "${RED}ERROR:${NC} Wrong password, we should quit now."
exit 1
fi
}
export -f fun_check_password
fun_get_user_variables_no_default(){
# get REMOTE_SERVER_EMAIL, if not supplied, quit. No default.
local VARIABLES=("${!1}")
local TMP_DEFAULT
local TMP_USER
local TMP_READ
echo "Automatic timeout is 120 sec"
for i in ${VARIABLES[@]}; do
TMP_DEFAULT=DEFAULT_$i
echo -n "GIMMME the $i (username, eg: ${!TMP_DEFAULT}), no default value:"
read -t 120 TMP_READ
echo ""
declare -g USER_$i=$TMP_READ
TMP_USER=USER_$i
if [ "${!TMP_USER}" == "" ]; then
echo -n "${TMP_USER} can not be empty. Please give it again:"
read -t 130 TMP_READ
declare -g USER_$i=$TMP_READ
TMP_USER=USER_$i
if [ "${!TMP_USER}" == "" ]; then
echo "Second try failed. Quitting..."
exit 1
fi
fi
done
}
export -f fun_get_user_variables_no_default
fun_get_user_variables_password(){
# get REMOTE_SERVER_PASSWORD, no default, suppress echoing back
local VARIABLES=("${!1}")
local TMP_DEFAULT
local TMP_USER
local TMP_READ
echo "Automatic timeout is 120 sec"
for i in ${VARIABLES[@]}; do
TMP_DEFAULT=DEFAULT_$i
echo -n "GIMMME the $i (password, eg: ${!TMP_DEFAULT}), no default value:"
read -t 120 -s TMP_READ
echo ""
declare -g USER_$i=$TMP_READ
TMP_USER=USER_$i
if [ "${!TMP_USER}" == "" ]; then
echo -n "${TMP_USER} can not be empty. Please give it again:"
read -t 130 -s TMP_READ
declare -g USER_$i=$TMP_READ
TMP_USER=USER_$i
if [ "${!TMP_USER}" == "" ]; then
echo "Second try failed. Quitting..."
exit 1
fi
fi
done
}
export -f fun_get_user_variables_password
fun_get_user_variables(){
# get USER_HOST_PORT, if not supplied, autofill with DEFAULT_HOST_PORT, etc
local VARIABLES=("${!1}")
local TMP_DEFAULT
local TMP_USER
local TMP_READ
echo "Automatic timeout is 30 sec"
for i in ${VARIABLES[@]}; do
TMP_DEFAULT=DEFAULT_$i
echo -n "GIMMME the $i (default: ${!TMP_DEFAULT}):"
read -t 30 TMP_READ
echo ""
declare -g USER_$i=$TMP_READ
TMP_USER=USER_$i
if [ "${!TMP_USER}" == "" ]; then
declare -g USER_$i=${!TMP_DEFAULT}
fi
done
}
export -f fun_get_user_variables
fun_prepare_run_command(){
# prepare the runcommand variable.
# Must be called before fun_echo_command and fun_run_command
TEMPLATE=$(cat <<EOF
-e "ORIGINAL_COMMAND=RUNCOMMAND_TEMPLATE" \
$USER_IMAGE
EOF
)
RUNCOMMAND_ADDED_ENV=${RUNCOMMAND_ORIG//$USER_IMAGE/$TEMPLATE}
RUNCOMMAND_NOPASSWD=${RUNCOMMAND_ADDED_ENV//$PASSWORD/PASSWORD}
RUNCOMMAND=${RUNCOMMAND_ADDED_ENV//RUNCOMMAND_TEMPLATE/$RUNCOMMAND_NOPASSWD}
}
export -f fun_prepare_run_command
fun_echo_command(){
# echo the command which will be launched (fun_run_command())
echo ${RUNCOMMAND//$PASSWORD/PASSWORD}
}
export -f fun_echo_command
fun_run_command(){
# execute the final command
echo `eval $RUNCOMMAND`
}
export -f fun_run_command
fun_print_help(){
USAGE=$(cat <<EOF
# Manual
# The main installation script is:
./install.sh domainname [hostname]
eg. ${GREEN}./install.sh amazeme.com mail.amazeme.com${NC}
There is a slight difference between domainname and hostname.
${ORANGE}Simplest case${NC}:
One server serves everything: company website, emails, webmails.
One ip address, and domainname is the same az hostname.
Eg. amazme.com
${GREEN}More general case${NC}:
The domainname is part of the email address:
username@domainname
The hostname is the actual machine name, eg. this machine
name is: `hostname`
On larger organizations, the company homepage is independent from
the mail servers. Or the webmail servers.
Eg. the company homepage is amazme.com [11.22.33.44],
the mail server is mail.amazme.com [11.22.33.43]
So domainname = amazme.com
hostname = mail.amazme.com
${RED}IP address${NC} case:
You can call this script with ip address instead of domain name:
./install.sh 11.22.33.44
(with the server's public IP address)
In that case both domainname and hostname becomes the IP address.
Dunno why anyone wanna that...
EOF
)
# echo -e for the colored output, "quotes" for the newline preserves
echo -e "$USAGE"
}
export -f fun_print_help
function hook_script {
echo "#!/bin/bash
git --git-dir=/var/opt/$1.git --work-tree=\"/opt/$1\" checkout "\$3" -f
cd \"/opt/$1\"
rm -rf package-lock.json
npm install --production --no-optional --no-package-lock --no-audit --ignore-scripts --no-shrinkwrap --progress=false
sudo $SYSTEMCTL_PATH restart $1 || echo \"Failed restarting service\"" > "/var/opt/$1.git/hooks/update"
chmod +x "/var/opt/$1.git/hooks/update"
}
export -f hook_script
function hook_script_bower {
echo "#!/bin/bash
git --git-dir=/var/opt/$1.git --work-tree=\"/opt/$1\" checkout "\$3" -f
cd \"/opt/$1\"
rm -rf package-lock.json
npm install --progress=false
npm run bowerdeps
sudo $SYSTEMCTL_PATH restart $1 || echo \"Failed restarting service\"" > "/var/opt/$1.git/hooks/update"
chmod +x "/var/opt/$1.git/hooks/update"
}
export -f hook_script_bower
function log_script {
SERVICE_NAME=$1
# Ensure required files and permissions
echo "d /var/log/${SERVICE_NAME} 0750 syslog adm" > /etc/tmpfiles.d/${SERVICE_NAME}-log.conf
# Redirect MongoDB log output from syslog to service specific log file
echo "if ( \$programname startswith \"$SERVICE_NAME\" ) then {
action(type=\"omfile\" file=\"/var/log/${SERVICE_NAME}/${SERVICE_NAME}.log\")
stop
}" > /etc/rsyslog.d/25-${SERVICE_NAME}.conf
# Setup log rotate
echo "/var/log/${SERVICE_NAME}/${SERVICE_NAME}.log {
daily
ifempty
missingok
rotate 7
compress
create 640 syslog adm
su root root
sharedscripts
postrotate
systemctl kill --signal=SIGHUP --kill-who=main rsyslog.service 2>/dev/null || true
endscript
}" > /etc/logrotate.d/${SERVICE_NAME}
}
Overview
The script sets up various functions and environment variables to facilitate the installation and configuration of WildDuck. It includes user prompts for necessary inputs, checks for permissions, and sets up hooks and logging.
Key Sections
Global Variables and Functions:
Hostname and Safety: AUT_HOSTNAME: Stores the current hostname. AUT_SAFETY: A flag for safety during execution. Home Directory: AUT_HOME: Sets the home directory of the user. Color Variables:
These variables are defined for colored output in the terminal (e.g., red for errors, green for success). User Password Functions:
fun_get_password: Prompts the user for their password unless they are root, in which case it assumes no password is needed. fun_check_password_boolean: Verifies the provided password by trying to execute a command with sudo. fun_check_password: Calls the previous function and exits if the password is incorrect. User Input Functions:
fun_get_user_variables_no_default: Prompts the user for certain variables without default values. fun_get_user_variables_password: Similar to the above but for sensitive information (passwords). fun_get_user_variables: Prompts for variables with the option of using defaults. Command Preparation:
fun_prepare_run_command: Prepares the command that will be executed later, handling the necessary environment variables. fun_echo_command: Displays the command that will be run, masking the password for security. fun_run_command: Executes the prepared command. Help Function:
fun_print_help: Outputs usage information for the script, explaining the difference between domain names and hostnames. Git Hook Functions:
hook_script and hook_script_bower: These functions set up Git hooks for updating the working directory and installing dependencies, respectively. Logging Setup:
log_script: Sets up logging for the service, including creating log directories, configuring rsyslog, and setting up log rotation. Important Notes The script uses bash-specific syntax and features, such as declare -g for global variable declaration and export -f for exporting functions to sub-shells. It includes error handling to ensure that the installation process is robust and user-friendly. The script is designed to be executed with root privileges or with sudo to allow it to perform system-level changes. Usage To run the script, the user would typically invoke it with:
bash コードをコピーする ./install.sh domainname [hostname] This means they would need to specify the primary domain for the email service and optionally a specific hostname.
Conclusion This installation script is a comprehensive tool for setting up the WildDuck service, ensuring that user inputs are collected securely and that system configurations are correctly applied for both functionality and logging.
¶ 01_install_commits.sh
#! /bin/bash
OURNAME=01_install_commits.sh
apt-get update
apt-get install -y lsb-release ca-certificates curl gnupg
NODE_MAJOR="20"
MONGODB="7.0"
CODENAME=`lsb_release -c -s`
WILDDUCK_COMMIT="9a61cff18689f773b01a77d00d6e309507fb5dd8"
ZONEMTA_COMMIT="2ec1ba85a44c4665a6326271c8162ee76c4d6d02" # zone-mta-template
WEBMAIL_COMMIT="40ee1ef973de33de5bdf3e6b7e877d156d87436a"
WILDDUCK_ZONEMTA_COMMIT="f029b8d69aaa255b1c4f426848f994be21be8cd0"
WILDDUCK_HARAKA_COMMIT="a209775ceac579b8ac1a4c04c052674eba763691"
HARAKA_VERSION="3.0.3"
echo -e "\n-- Executing ${ORANGE}${OURNAME}${NC} subscript --"
- 02_install_prerequisites.sh
#! /bin/bash
OURNAME=02_install_prerequisites.sh
# No $AUT_SAFETY variable present, so we have not sourced install_variables.sh yet
if [ -z ${AUT_SAFETY+x} ]
then
echo "this script ${RED}called directly${NC}, and not from the main ./install.sh script"
echo "initializing common variables ('install_variables.sh')"
source "$INSTALLDIR/install_variables.sh"
fi
echo -e "\n-- Executing ${ORANGE}${OURNAME}${NC} subscript --"
echo -e "Checking ${YELLOW}lsof${NC}"
PROGRAM_LSOF=`command -v lsof`
if ! [ -x "$PROGRAM_LSOF" ]; then
echo -e "${RED}ERROR:${NC} lsof is not installed."
echo "to know which package contains the particular executable, launch:"
echo "dpkg -S lsof |grep lsof$ # on ubuntu/debian variants"
echo -e "Launching for you:\n"
echo -e "`dpkg -S lsof | grep /lsof$`"
echo -e "\nOn ubuntu 16.04 it is: ${GREEN}apt install lsof${NC}"
fi
echo -e "Checking ${YELLOW}ps${NC}"
PROGRAM_PS=`command -v ps`
if ! [ -x "$PROGRAM_PS" ]; then
echo -e "${RED}ERROR:${NC} ps is not installed."
echo "to know which package contains the particular executable, launch:"
echo "dpkg -S ps |grep ps$ # on ubuntu/debian variants"
echo -e "Launching for you:\n"
echo -e "`dpkg -S ps | grep /ps$`"
echo -e "\nOn ubuntu 16.04 it is: ${GREEN}apt install procps${NC}"
fiE}${OURNAME}${NC} subscript --"
¶ 03_install_check_running_services.sh
#! /bin/bash
OURNAME=03_install_check_running_services.sh
# No $AUT_SAFETY variable present, so we have not sourced install_variables.sh yet
# check if $AUT_SAFETY is unset (as opposed to empty "" string)
if [ -z ${AUT_SAFETY+x} ]
then
echo "this script ${RED}called directly${NC}, and not from the main ./install.sh script"
echo "initializing common variables ('install_variables.sh')"
source "$INSTALLDIR/install_variables.sh"
fi
echo -e "\n\n-- Executing ${ORANGE}${OURNAME}${NC} subscript --"
echo -e "Checking programs listening on port 25,587,993,995,80,443"
PORT25=`lsof -Pi :25 -sTCP:LISTEN -t`
PORT587=`lsof -Pi :587 -sTCP:LISTEN -t`
PORT993=`lsof -Pi :993 -sTCP:LISTEN -t`
PORT995=`lsof -Pi :995 -sTCP:LISTEN -t`
PORT80=`lsof -Pi :80 -sTCP:LISTEN -t`
PORT443=`lsof -Pi :443 -sTCP:LISTEN -t`
# check if $PORT25 is empty "" string (as opposed to unset)
if ! [ -z $PORT25 ] ; then
echo -e "${RED}Error:${NC} SMTP server already running on port 25"
echo -e "PID: ${YELLOW}$PORT25${NC}"
BINARY=`ps -p $PORT25 -o comm=`
echo -e "binary: ${YELLOW}$BINARY${NC}"
echo -e "full command with arguments: ${YELLOW}`ps -p $PORT25 -o command=`${NC}"
echo -e "possible packages (dpkg -S $BINARY | grep /${BINARY}$):"
echo -e "`dpkg -S $BINARY | grep /${BINARY}$`"
echo -e "If it is launched by systemd, finding the service with"
echo -e "Executing ${YELLOW}systemctl status $PORT25${NC}"
echo -e "`systemctl status $PORT25`"
echo -e "\nList all enabled services:"
echo -e "systemctl list-unit-files | grep enabled"
echo -e "stop a service: systemctl stop [service]"
echo -e "${RED}QUITTING... (please stop the service and launch again)${NC}"
exit 1
else
echo -e 'OK: port 25 (SMTP) is free'
fi
if ! [ -z $PORT587 ] ; then
echo -e "${RED}Error:${NC} SMTP server already running on port 587"
echo -e "PID: ${YELLOW}$PORT587${NC}"
BINARY=`ps -p $PORT587 -o comm=`
echo -e "binary: ${YELLOW}$BINARY${NC}"
echo -e "full command with arguments: ${YELLOW}`ps -p $PORT587 -o command=`${NC}"
echo -e "possible packages (dpkg -S $BINARY | grep /${BINARY}$):"
echo -e "`dpkg -S $BINARY | grep /${BINARY}$`"
echo -e "If it is launched by systemd, finding the service with"
echo -e "Executing ${YELLOW}systemctl status $PORT587${NC}"
echo -e "`systemctl status $PORT587`"
echo -e "\nList all enabled services:"
echo -e "systemctl list-unit-files | grep enabled"
echo -e "stop a service: systemctl stop [service]"
echo -e "${RED}QUITTING... (please stop the service and launch again)${NC}"
exit 1
else
echo -e 'OK: port 587 (SMTP TLS) is free'
fi
¶ 04_install_import_keys.sh
#! /bin/bash
OURNAME=04_install_import_keys.sh
echo -e "\n-- Executing ${ORANGE}${OURNAME}${NC} subscript --"
# create user for running applications
useradd wildduck || echo "User wildduck already exists"
# remove old sudoers file
rm -rf /etc/sudoers.d/wildduck
# create user for deploying code
useradd deploy || echo "User deploy already exists"
mkdir -p /home/deploy/.ssh
# add your own key to the authorized_keys file
echo "# Add your public key here
" >> /home/deploy/.ssh/authorized_keys
chown -R deploy:deploy /home/deploy
export DEBIAN_FRONTEND=noninteractive
keyring="/usr/share/keyrings"
# nodejs
node_key_url="https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key"
local_node_key="${keyring}/nodesource.gpg"
curl -fsSL $node_key_url | gpg --dearmor | tee $local_node_key >/dev/null
echo "deb [signed-by=${local_node_key}] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" > /etc/apt/sources.list.d/nodesource.list
echo "deb-src [signed-by=${local_node_key}] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" >> /etc/apt/sources.list.d/nodesource.list
# mongodb
mongo_key_url="https://pgp.mongodb.com/server-${MONGODB}.asc"
local_mongo_key="${keyring}/mongodb-server-${MONGODB}.gpg"
curl -fsSL $mongo_key_url | gpg --dearmor | tee ${local_mongo_key} >/dev/null
echo "deb [ arch=amd64,arm64 signed-by=${local_mongo_key} ] https://repo.mongodb.org/apt/ubuntu ${CODENAME}/mongodb-org/${MONGODB} multiverse" > /etc/apt/sources.list.d/mongodb-org-${MONGODB}.list
# rspamd
rspamd_key_url="https://rspamd.com/apt-stable/gpg.key"
local_rspamd_key="${keyring}/rspamd.gpg"
curl -fsSL $rspamd_key_url | gpg --dearmor | tee ${local_rspamd_key} >/dev/null
echo "deb [signed-by=${local_rspamd_key}] http://rspamd.com/apt-stable/ $CODENAME main" > /etc/apt/sources.list.d/rspamd.list
echo "deb-src [signed-by=${local_rspamd_key}] http://rspamd.com/apt-stable/ $CODENAME main" >> /etc/apt/sources.list.d/rspamd.list
# redis
redis_key_url="https://packages.redis.io/gpg"
local_redis_key="${keyring}/redis-archive-keyring.gpg"
curl -fsSL $redis_key_url | gpg --dearmor | tee ${local_redis_key} >/dev/null
echo "deb [signed-by=${local_redis_key}] https://packages.redis.io/deb $CODENAME main" > tee /etc/apt/sources.list.d/redis.list
if ! [ -z $PORT993 ] ; then
echo -e "${RED}Error:${NC} IMAP server already running on port 993"
echo -e "PID: ${YELLOW}$PORT993${NC}"
BINARY=ps -p $PORT993 -o comm=
echo -e "binary: ${YELLOW}$BINARY${NC}"
echo -e "full command with arguments: ${YELLOW}ps -p $PORT993 -o command=
${NC}"
echo -e "possible packages (dpkg -S $BINARY | grep /${BINARY}$):"
echo -e "dpkg -S $BINARY | grep /${BINARY}$
"
echo -e "If it is launched by systemd, finding the service with"
echo -e "Executing ${YELLOW}systemctl status $PORT993${NC}"
echo -e "systemctl status $PORT993
"
echo -e "\nList all enabled services:"
echo -e "systemctl list-unit-files | grep enabled"
echo -e "stop a service: systemctl stop [service]"
echo -e "${RED}QUITTING... (please stop the service and launch again)${NC}"
exit 1
else
echo -e 'OK: port 993 (IMAP SSL/TLS) is free'
fi
if ! [ -z $PORT995 ] ; then
echo -e "${RED}Error:${NC} POP3 server already running on port 995"
echo -e "PID: ${YELLOW}$PORT995${NC}"
BINARY=ps -p $PORT995 -o comm=
echo -e "binary: ${YELLOW}$BINARY${NC}"
echo -e "full command with arguments: ${YELLOW}ps -p $PORT995 -o command=
${NC}"
echo -e "possible packages (dpkg -S $BINARY | grep /${BINARY}$):"
echo -e "dpkg -S $BINARY | grep /${BINARY}$
"
echo -e "If it is launched by systemd, finding the service with"
echo -e "Executing ${YELLOW}systemctl status $PORT995${NC}"
echo -e "systemctl status $PORT995
"
echo -e "\nList all enabled services:"
echo -e "systemctl list-unit-files | grep enabled"
echo -e "stop a service: systemctl stop [service]"
echo -e "${RED}QUITTING... (please stop the service and launch again)${NC}"
exit 1
else
echo -e 'OK: port 995 (POP3 SSL/TLS) is free'
fi
if ! [ -z $PORT80 ] ; then
echo -e "${RED}Error:${NC} HTTP server already running on port 80"
echo -e "PID: ${YELLOW}$PORT80${NC}"
BINARY=ps -p $PORT80 -o comm=
echo -e "binary: ${YELLOW}$BINARY${NC}"
echo -e "full command with arguments: ${YELLOW}ps -p $PORT80 -o command=
${NC}"
echo -e "possible packages (dpkg -S $BINARY | grep /${BINARY}$):"
echo -e "dpkg -S $BINARY | grep /${BINARY}$
"
echo -e "If it is launched by systemd, finding the service with"
echo -e "Executing ${YELLOW}systemctl status $PORT80${NC}"
echo -e "systemctl status $PORT80
"
echo -e "\nList all enabled services:"
echo -e "systemctl list-unit-files | grep enabled"
echo -e "stop a service: systemctl stop [service]"
echo -e "${RED}QUITTING... (please stop the service and launch again)${NC}"
exit 1
else
echo -e 'OK: port 80 (HTTP) is free'
fi
if ! [ -z $PORT443 ] ; then
echo -e "${RED}Error:${NC} HTTPS server already running on port 443"
echo -e "PID: ${YELLOW}$PORT443${NC}"
BINARY=ps -p $PORT443 -o comm=
echo -e "binary: ${YELLOW}$BINARY${NC}"
echo -e "full command with arguments: ${YELLOW}ps -p $PORT443 -o command=
${NC}"
echo -e "possible packages (dpkg -S $BINARY | grep /${BINARY}$):"
echo -e "dpkg -S $BINARY | grep /${BINARY}$
"
echo -e "If it is launched by systemd, finding the service with"
echo -e "Executing ${YELLOW}systemctl status $PORT443${NC}"
echo -e "systemctl status $PORT443
"
echo -e "\nList all enabled services:"
echo -e "systemctl list-unit-files | grep enabled"
echo -e "stop a service: systemctl stop [service]"
echo -e "${RED}QUITTING... (please stop the service and launch again)${NC}"
exit 1
else
echo -e 'OK: port 443 (HTTPS) is free'
fi
---
¶ 05_install_packages.sh
#! /bin/bash
OURNAME=05_install_packages.sh
echo -e "\n-- Executing ${ORANGE}${OURNAME}${NC} subscript --"
install nginx
apt-get update apt-get -q -y install pwgen git ufw build-essential libssl-dev dnsutils python3 software-properties-common nginx wget mongodb-org nodejs redis-server clamav clamav-daemon
rspamd
apt-get -q -y --no-install-recommends install rspamd apt-get clean
DMARC policy=reject rules
echo 'actions = { quarantine = "add_header"; reject = "reject"; }' > /etc/rspamd/override.d/dmarc.conf
---
¶ 06_install_enable_services.sh
#! /bin/bash
OURNAME=06_install_enable_services.sh
echo -e "\n-- Executing ${ORANGE}${OURNAME}${NC} subscript --"
NODE_PATH=command -v node
SYSTEMCTL_PATH=command -v systemctl
SRS_SECRET=pwgen 12 -1
DKIM_SECRET=pwgen 12 -1
ZONEMTA_SECRET=pwgen 12 -1
DKIM_SELECTOR=$NODE_PATH -e 'console.log(Date().toString().substr(4, 3).toLowerCase() + new Date().getFullYear())'
$SYSTEMCTL_PATH enable mongod.service $SYSTEMCTL_PATH enable redis-server.service
echo -e "\n-- These are the installed and required programs:" node -v redis-server -v mongod --version echo "HOSTNAME: $HOSTNAME"
echo -e "-- Installing ${RED}npm globally${NC} (workaround)"
See issue https://github.com/nodemailer/wildduck/issues/82
npm install npm -g
---
¶ 07_install_wildduck.sh
#! /bin/bash
OURNAME=07_install_wildduck.sh
echo -e "\n-- Executing ${ORANGE}${OURNAME}${NC} subscript --"
####### WILD DUCK #######
clear previous install
if [ -f "/etc/systemd/system/wildduck.service" ] then $SYSTEMCTL_PATH stop wildduck || true $SYSTEMCTL_PATH disable wildduck || true rm -rf /etc/systemd/system/wildduck.service fi rm -rf /var/opt/wildduck.git rm -rf /opt/wildduck rm -rf /etc/wildduck
fresh install
cd /var/opt git clone --bare https://github.com/nodemailer/wildduck.git
create update hook so we can later deploy to this location
hook_script wildduck
allow deploy user to restart wildduck service
echo "deploy ALL = (root) NOPASSWD: $SYSTEMCTL_PATH restart wildduck" >> /etc/sudoers.d/wildduck
checkout files from git to working directory
mkdir -p /opt/wildduck git --git-dir=/var/opt/wildduck.git --work-tree=/opt/wildduck checkout "$WILDDUCK_COMMIT" cp -r /opt/wildduck/config /etc/wildduck mv /etc/wildduck/default.toml /etc/wildduck/wildduck.toml
enable example message
sed -i -e 's/"disabled": true/"disabled": false/g' /opt/wildduck/emails/00-example.json
update ports
sed -i -e "s/999/99/g;s/localhost/$HOSTNAME/g" /etc/wildduck/imap.toml sed -i -e "s/999/99/g;s/localhost/$HOSTNAME/g" /etc/wildduck/pop3.toml
echo "enabled=true port=24 disableSTARTTLS=true" > /etc/wildduck/lmtp.toml
make sure that DKIM keys are not stored to database as cleartext
echo "secret="$DKIM_SECRET"" >> /etc/wildduck/dkim.toml
echo "user="wildduck" group="wildduck" emailDomain="$MAILDOMAIN"" | cat - /etc/wildduck/wildduck.toml > temp && mv temp /etc/wildduck/wildduck.toml
sed -i -e "s/localhost:3000/$HOSTNAME/g;s/localhost/$HOSTNAME/g;s/2587/587/g" /etc/wildduck/wildduck.toml sed -i -e "s/secret value/$SRS_SECRET/g;s/#loopSecret/loopSecret/g" /etc/wildduck/sender.toml
cd /opt/wildduck npm install --production --unsafe-perm --no-optional --no-package-lock --no-audit --ignore-scripts --no-shrinkwrap
chown -R deploy:deploy /var/opt/wildduck.git chown -R deploy:deploy /opt/wildduck
echo "d /opt/wildduck 0755 deploy deploy d /etc/wildduck 0755 wildduck wildduck" > /etc/tmpfiles.d/zone-mta.conf log_script "wildduck-server"
echo "[Unit] Description=WildDuck Mail Server Conflicts=cyrus.service dovecot.service After=mongod.service redis.service
[Service] Environment="NODE_ENV=production" WorkingDirectory=/opt/wildduck ExecStart=$NODE_PATH server.js --config="/etc/wildduck/wildduck.toml" ExecReload=/bin/kill -HUP $MAINPID Type=simple Restart=always SyslogIdentifier=wildduck-server
[Install] WantedBy=multi-user.target" > /etc/systemd/system/wildduck.service
$SYSTEMCTL_PATH enable wildduck.service
---
¶ 08_install_haraka.sh
#! /bin/bash
OURNAME=08_install_haraka.sh
echo -e "\n-- Executing ${ORANGE}${OURNAME}${NC} subscript --"
####### HARAKA #######
clear previous install
if [ -f "/etc/systemd/system/haraka.service" ] then $SYSTEMCTL_PATH stop haraka || true $SYSTEMCTL_PATH disable haraka || true rm -rf /etc/systemd/system/haraka.service fi rm -rf /var/opt/haraka-plugin-wildduck.git rm -rf /opt/haraka
fresh install
cd /var/opt git clone --bare https://github.com/nodemailer/haraka-plugin-wildduck.git echo "#!/bin/bash git --git-dir=/var/opt/haraka-plugin-wildduck.git --work-tree=/opt/haraka/plugins/wildduck checkout "$3" -f cd /opt/haraka/plugins/wildduck rm -rf package-lock.json npm install --production --no-optional --no-package-lock --no-audit --ignore-scripts --no-shrinkwrap --progress=false sudo $SYSTEMCTL_PATH restart haraka || echo "Failed restarting service"" > "/var/opt/haraka-plugin-wildduck.git/hooks/update" chmod +x "/var/opt/haraka-plugin-wildduck.git/hooks/update"
allow deploy user to restart wildduck service
echo "deploy ALL = (root) NOPASSWD: $SYSTEMCTL_PATH restart haraka" >> /etc/sudoers.d/wildduck
cd npm install --production --no-optional --no-package-lock --no-audit --no-shrinkwrap --unsafe-perm -g Haraka@$HARAKA_VERSION haraka -i /opt/haraka cd /opt/haraka npm install --production --no-optional --no-package-lock --no-audit --no-shrinkwrap --unsafe-perm --save haraka-plugin-rspamd haraka-plugin-redis Haraka@$HARAKA_VERSION
Haraka WildDuck plugin. Install as separate repo as it can be edited more easily later
mkdir -p plugins/wildduck git --git-dir=/var/opt/haraka-plugin-wildduck.git --work-tree=/opt/haraka/plugins/wildduck checkout "$WILDDUCK_HARAKA_COMMIT"
cd plugins/wildduck npm install --production --no-optional --no-package-lock --no-audit --ignore-scripts --no-shrinkwrap --unsafe-perm --progress=false
cd /opt/haraka mv config/plugins config/plugins.bak
echo "26214400" > config/databytes echo "$HOSTNAME" > config/me echo "WildDuck MX" > config/smtpgreeting
echo "#spf #dkim_verify
ClamAV is disabled by default. Make sure freshclam has updated all
virus definitions and clamav-daemon has successfully started before
enabling it.
#clamd
rspamd tls
WildDuck plugin handles recipient checking and queueing
wildduck" > config/plugins
echo "key=/etc/wildduck/certs/privkey.pem cert=/etc/wildduck/certs/fullchain.pem" > config/tls.ini
echo 'host = localhost port = 11333 add_headers = always [dkim] enabled = true [header] bar = X-Rspamd-Bar report = X-Rspamd-Report score = X-Rspamd-Score spam = X-Rspamd-Spam [check] authenticated=true private_ip=true [reject] spam = false [soft_reject] enabled = true [rmilter_headers] enabled = true [spambar] positive = + negative = - neutral = /' > config/rspamd.ini
echo 'clamd_socket = /var/run/clamav/clamd.ctl [reject] virus=true error=false' > config/clamd.ini
cp plugins/wildduck/config/wildduck.yaml config/wildduck.yaml sed -i -e "s/secret value/$SRS_SECRET/g;s/#loopSecret/loopSecret/g" config/wildduck.yaml
Ensure required files and permissions
echo "d /opt/haraka 0755 deploy deploy" > /etc/tmpfiles.d/haraka.conf log_script "haraka"
echo '[Unit] Description=Haraka MX Server After=mongod.service redis.service
[Service] Environment="NODE_ENV=production" WorkingDirectory=/opt/haraka ExecStart=/usr/bin/node ./node_modules/.bin/haraka -c . Type=simple Restart=always SyslogIdentifier=haraka
[Install] WantedBy=multi-user.target' > /etc/systemd/system/haraka.service
echo 'user=wildduck group=wildduck' >> config/smtp.ini
chown -R deploy:deploy /opt/haraka chown -R deploy:deploy /var/opt/haraka-plugin-wildduck.git
ensure queue folder for Haraka
mkdir -p /opt/haraka/queue chown -R wildduck:wildduck /opt/haraka/queue
$SYSTEMCTL_PATH enable haraka.service
---
¶ cat 09_install_zone_mta.sh
#! /bin/bash
OURNAME=09_install_zone_mta.sh
echo -e "\n-- Executing ${ORANGE}${OURNAME}${NC} subscript --"
ZoneMTA
clear previous install
if [ -f "/etc/systemd/system/zone-mta.service" ] then $SYSTEMCTL_PATH stop zone-mta || true $SYSTEMCTL_PATH disable zone-mta || true rm -rf /etc/systemd/system/zone-mta.service fi rm -rf /var/opt/zone-mta.git rm -rf /var/opt/zonemta-wildduck.git rm -rf /opt/zone-mta rm -rf /etc/zone-mta
fresh install
cd /var/opt git clone --bare https://github.com/zone-eu/zone-mta-template.git zone-mta.git git clone --bare https://github.com/nodemailer/zonemta-wildduck.git
create update hooks so we can later deploy to this location
hook_script zone-mta echo "#!/bin/bash git --git-dir=/var/opt/zonemta-wildduck.git --work-tree=/opt/zone-mta/plugins/wildduck checkout "$3" -f cd /opt/zone-mta/plugins/wildduck rm -rf package-lock.json npm install --production --no-optional --no-package-lock --no-audit --ignore-scripts --no-shrinkwrap --progress=false sudo $SYSTEMCTL_PATH restart zone-mta || echo "Failed restarting service"" > "/var/opt/zonemta-wildduck.git/hooks/update" chmod +x "/var/opt/zonemta-wildduck.git/hooks/update"
allow deploy user to restart zone-mta service
echo "deploy ALL = (root) NOPASSWD: $SYSTEMCTL_PATH restart zone-mta" >> /etc/sudoers.d/zone-mta
checkout files from git to working directory
mkdir -p /opt/zone-mta git --git-dir=/var/opt/zone-mta.git --work-tree=/opt/zone-mta checkout "$ZONEMTA_COMMIT"
mkdir -p /opt/zone-mta/plugins/wildduck git --git-dir=/var/opt/zonemta-wildduck.git --work-tree=/opt/zone-mta/plugins/wildduck checkout "$WILDDUCK_ZONEMTA_COMMIT"
cp -r /opt/zone-mta/config /etc/zone-mta sed -i -e 's/port=2525/port=587/g;s/host="127.0.0.1"/host="0.0.0.0"/g;s/authentication=false/authentication=true/g' /etc/zone-mta/interfaces/feeder.toml rm -rf /etc/zone-mta/plugins/dkim.toml echo '# @include "/etc/wildduck/dbs.toml"' > /etc/zone-mta/dbs-production.toml echo 'user="wildduck" group="wildduck"' | cat - /etc/zone-mta/zonemta.toml > temp && mv temp /etc/zone-mta/zonemta.toml
echo "[[default]] address="0.0.0.0" name="$HOSTNAME"" > /etc/zone-mta/pools.toml
echo "["modules/zonemta-loop-breaker"] enabled="sender" secret="$ZONEMTA_SECRET" algo="md5"" > /etc/zone-mta/plugins/loop-breaker.toml
echo "[wildduck] enabled=["receiver", "sender"]
which interfaces this plugin applies to
interfaces=["feeder"]
optional hostname to be used in headers
defaults to os.hostname()
hostname="$HOSTNAME"
SRS settings for forwarded emails
[wildduck.srs] # Handle rewriting of forwarded emails enabled=true # SRS secret value. Must be the same as in the MX side secret="$SRS_SECRET" # SRS domain, must resolve back to MX rewriteDomain="$MAILDOMAIN"
[wildduck.dkim]
share config with WildDuck installation
@include "/etc/wildduck/dkim.toml"
" > /etc/zone-mta/plugins/wildduck.toml
cd /opt/zone-mta/keys
Many registrar limits dns TXT fields to 255 char. 1024bit is almost too long:-\
openssl genrsa -out "$MAILDOMAIN-dkim.pem" 1024 chmod 400 "$MAILDOMAIN-dkim.pem" openssl rsa -in "$MAILDOMAIN-dkim.pem" -out "$MAILDOMAIN-dkim.cert" -pubout DKIM_DNS="v=DKIM1;k=rsa;p=$(grep -v -e '^-' $MAILDOMAIN-dkim.cert | tr -d "\n")"
DKIM_JSON=DOMAIN="$MAILDOMAIN" SELECTOR="$DKIM_SELECTOR" node -e 'console.log(JSON.stringify({ domain: process.env.DOMAIN, selector: process.env.SELECTOR, description: "Default DKIM key for "+process.env.DOMAIN, privateKey: fs.readFileSync("/opt/zone-mta/keys/"+process.env.DOMAIN+"-dkim.pem", "UTF-8") }))'
cd /opt/zone-mta npm install --production --no-optional --no-package-lock --no-audit --ignore-scripts --no-shrinkwrap --unsafe-perm
cd /opt/zone-mta/plugins/wildduck npm install --production --no-optional --no-package-lock --no-audit --ignore-scripts --no-shrinkwrap --unsafe-perm
chown -R deploy:deploy /var/opt/zone-mta.git chown -R deploy:deploy /var/opt/zonemta-wildduck.git chown -R deploy:deploy /opt/zone-mta chown -R wildduck:wildduck /etc/zone-mta
Ensure required files and permissions
echo "d /opt/zone-mta 0755 deploy deploy d /etc/zone-mta 0755 wildduck wildduck" > /etc/tmpfiles.d/zone-mta.conf log_script "zone-mta"
echo '[Unit] Description=Zone Mail Transport Agent Conflicts=sendmail.service exim.service postfix.service After=mongod.service redis.service
[Service] Environment="NODE_ENV=production" WorkingDirectory=/opt/zone-mta ExecStart=/usr/bin/node index.js --config="/etc/zone-mta/zonemta.toml" ExecReload=/bin/kill -HUP $MAINPID Type=simple Restart=always SyslogIdentifier=zone-mta
[Install] WantedBy=multi-user.target' > /etc/systemd/system/zone-mta.service
$SYSTEMCTL_PATH enable zone-mta.service
---
¶10_install_wildduck_webmail.sh
#! /bin/bash
OURNAME=10_install_wildduck_webmail.sh
echo -e "\n-- Executing ${ORANGE}${OURNAME}${NC} subscript --"
WWW
clear previous install
if [ -f "/etc/systemd/system/wildduck-webmail.service" ] then $SYSTEMCTL_PATH stop wildduck-webmail || true $SYSTEMCTL_PATH disable wildduck-webmail || true rm -rf /etc/systemd/system/wildduck-webmail.service fi rm -rf /var/opt/wildduck-webmail.git rm -rf /opt/wildduck-webmail
fresh install
cd /var/opt git clone --bare https://github.com/nodemailer/wildduck-webmail.git
create update hook so we can later deploy to this location
hook_script_bower wildduck-webmail chmod +x /var/opt/wildduck-webmail.git/hooks/update
allow deploy user to restart zone-mta service
echo "deploy ALL = (root) NOPASSWD: $SYSTEMCTL_PATH restart wildduck-webmail" >> /etc/sudoers.d/wildduck-webmail
checkout files from git to working directory
mkdir -p /opt/wildduck-webmail git --git-dir=/var/opt/wildduck-webmail.git --work-tree=/opt/wildduck-webmail checkout "$WEBMAIL_COMMIT" cp /opt/wildduck-webmail/config/default.toml /etc/wildduck/wildduck-webmail.toml
sed -i -e "s/localhost/$HOSTNAME/g;s/999/99/g;s/2587/587/g;s/proxy=false/proxy=true/g;s/domains=.*/domains=["$MAILDOMAIN"]/g" /etc/wildduck/wildduck-webmail.toml
cd /opt/wildduck-webmail
chown -R deploy:deploy /var/opt/wildduck-webmail.git chown -R deploy:deploy /opt/wildduck-webmail
we need to run bower which reject root
HOME=/home/deploy sudo -u deploy npm install HOME=/home/deploy sudo -u deploy npm run bowerdeps
echo "d /opt/wildduck-webmail 0755 deploy deploy" > /etc/tmpfiles.d/zone-mta.conf log_script "wildduck-www"
echo '[Unit] Description=Wildduck Webmail After=wildduck.service
[Service] Environment="NODE_ENV=production" WorkingDirectory=/opt/wildduck-webmail ExecStart=/usr/bin/node server.js --config="/etc/wildduck/wildduck-webmail.toml" ExecReload=/bin/kill -HUP $MAINPID Type=simple Restart=always SyslogIdentifier=wildduck-www
[Install] WantedBy=multi-user.target' > /etc/systemd/system/wildduck-webmail.service
$SYSTEMCTL_PATH enable wildduck-webmail.service
---
¶ 11_install_nginx.sh
#! /bin/bash
OURNAME=11_install_nginx.sh
echo -e "\n-- Executing ${ORANGE}${OURNAME}${NC} subscript --"
NGINX
Create initial certs. These will be overwritten later by Let's Encrypt certs
mkdir -p /etc/wildduck/certs cd /etc/wildduck/certs openssl req -subj "/CN=$HOSTNAME/O=My Company Name LTD./C=US" -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout privkey.pem -out fullchain.pem
chown -R wildduck:wildduck /etc/wildduck/certs chmod 0700 /etc/wildduck/certs/privkey.pem
create nginx config dirs - if missing
if [[ ! -d /etc/nginx/sites-available ]]; then mkdir -p /etc/nginx/sites-available fi if [[ ! -d /etc/nginx/sites-enabled ]]; then mkdir -p /etc/nginx/sites-enabled fi
Setup domain without SSL at first, otherwise acme.sh will fail
echo "server { listen 80;
server_name $HOSTNAME;
ssl_certificate /etc/wildduck/certs/fullchain.pem;
ssl_certificate_key /etc/wildduck/certs/privkey.pem;
# special config for EventSource to disable gzip
location /api/events {
proxy_http_version 1.1;
gzip off;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header HOST \$http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://127.0.0.1:3000;
proxy_redirect off;
}
# special config for uploads
location /webmail/send {
client_max_body_size 15M;
proxy_http_version 1.1;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header HOST \$http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://127.0.0.1:3000;
proxy_redirect off;
}
location / {
proxy_http_version 1.1;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header HOST \$http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://127.0.0.1:3000;
proxy_redirect off;
}
}" > "/etc/nginx/sites-available/$HOSTNAME" rm -rf "/etc/nginx/sites-enabled/$HOSTNAME" ln -s "/etc/nginx/sites-available/$HOSTNAME" "/etc/nginx/sites-enabled/$HOSTNAME" $SYSTEMCTL_PATH reload nginx
---
¶ 12_install_ufw_rules.sh
#! /bin/bash
OURNAME=12_install_ufw_rules.sh
echo -e "\n-- Executing ${ORANGE}${OURNAME}${NC} subscript --"
get sshd port from /etc/ssh/sshd_config
_var_sshd_port="$(cat /etc/ssh/sshd_config|grep -i -E ^port|cut -f2 -d' ')" if [[ $_var_sshd_port == "" ]]; then _var_sshd_port=22 fi
UFW
ufw allow $_var_sshd_port/tcp ufw allow 80/tcp ufw allow 443/tcp ufw allow 25/tcp ufw allow 587/tcp ufw allow 993/tcp ufw allow 995/tcp ufw --force enable
- cat 13_install_ssl_certs.sh
#! /bin/bash
OURNAME=13_install_ssl_certs.sh
echo -e "\n-- Executing ${ORANGE}${OURNAME}${NC} subscript --"
SSL CERTS
Install acme.sh
NOTE: the version 3.0.7 has a bug with Nginx certs, so version is pinned to 3.0.6
ACME_VERSION="3.0.6" wget https://raw.githubusercontent.com/acmesh-official/acme.sh/${ACME_VERSION}/acme.sh sh acme.sh --install --auto-upgrade 0 rm -rf acme.sh
WildDuck TLS config
echo 'cert="/etc/wildduck/certs/fullchain.pem" key="/etc/wildduck/certs/privkey.pem"' > /etc/wildduck/tls.toml
sed -i -e "s/key=/#key=/g;s/cert=/#cert=/g" /etc/zone-mta/interfaces/feeder.toml echo '# @include "../../wildduck/tls.toml"' >> /etc/zone-mta/interfaces/feeder.toml
vanity script as first run should not restart anything
echo '#!/bin/bash echo "OK"' > /usr/local/bin/reload-services.sh chmod +x /usr/local/bin/reload-services.sh
~/.acme.sh/acme.sh --issue --nginx --server letsencrypt
-d "$HOSTNAME"
--key-file /etc/wildduck/certs/privkey.pem
--fullchain-file /etc/wildduck/certs/fullchain.pem
--reloadcmd "/usr/local/bin/reload-services.sh"
--force || echo "Warning: Failed to generate certificates, using self-signed certs"
Update site config, make sure ssl is enabled
echo "server { listen 80; listen [::]:80; listen 443 ssl http2; listen [::]:443 ssl http2;
server_name $HOSTNAME;
ssl_certificate /etc/wildduck/certs/fullchain.pem;
ssl_certificate_key /etc/wildduck/certs/privkey.pem;
# special config for EventSource to disable gzip
location /api/events {
proxy_http_version 1.1;
gzip off;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header HOST \$http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://127.0.0.1:3000;
proxy_redirect off;
}
# special config for uploads
location /webmail/send {
client_max_body_size 15M;
proxy_http_version 1.1;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header HOST \$http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://127.0.0.1:3000;
proxy_redirect off;
}
location / {
proxy_http_version 1.1;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header HOST \$http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://127.0.0.1:3000;
proxy_redirect off;
}
}" > "/etc/nginx/sites-available/$HOSTNAME"
#See issue https://github.com/nodemailer/wildduck/issues/83 $SYSTEMCTL_PATH start nginx $SYSTEMCTL_PATH reload nginx
---
¶ 14_install_start_services.sh
#! /bin/bash
OURNAME=14_install_start_services.sh
echo -e "\n-- Executing ${ORANGE}${OURNAME}${NC} subscript --"
Run tmpfiles definitions to ensure required directories/files
systemd-tmpfiles --create --remove
Restart rsyslog for the changes to take effect
systemctl restart rsyslog
update reload script for future updates
echo "#!/bin/bash $SYSTEMCTL_PATH reload nginx $SYSTEMCTL_PATH restart wildduck $SYSTEMCTL_PATH restart zone-mta $SYSTEMCTL_PATH restart haraka $SYSTEMCTL_PATH restart wildduck-webmail" > /usr/local/bin/reload-services.sh chmod +x /usr/local/bin/reload-services.sh
start services
$SYSTEMCTL_PATH start mongod $SYSTEMCTL_PATH start redis $SYSTEMCTL_PATH start wildduck $SYSTEMCTL_PATH start haraka $SYSTEMCTL_PATH start zone-mta $SYSTEMCTL_PATH start wildduck-webmail $SYSTEMCTL_PATH reload nginx
---
¶ 15_install_deploy.sh
#! /bin/bash
OURNAME=15_install_deploy.sh
echo -e "\n-- Executing ${ORANGE}${OURNAME}${NC} subscript --"
cd "$INSTALLDIR"
echo "DEPLOY SETUP
-
Add your ssh key to /home/deploy/.ssh/authorized_keys
-
Clone application code $ git clone deploy@$HOSTNAME:/var/opt/wildduck.git $ git clone deploy@$HOSTNAME:/var/opt/zone-mta.git $ git clone deploy@$HOSTNAME:/var/opt/wildduck-webmail.git $ git clone deploy@$HOSTNAME:/var/opt/haraka-plugin-wildduck.git $ git clone deploy@$HOSTNAME:/var/opt/zonemta-wildduck.git
-
After making a change in local copy deploy to server $ git push origin master (you might need to use -f when pushing first time)
NAMESERVER SETUP
MX
Add this MX record to the $MAILDOMAIN DNS zone:
$MAILDOMAIN. IN MX 5 $HOSTNAME.
SPF
Add this TXT record to the $MAILDOMAIN DNS zone:
$MAILDOMAIN. IN TXT "v=spf1 a:$HOSTNAME a:$MAILDOMAIN ip4:$PUBLIC_IP ~all"
Or: $MAILDOMAIN. IN TXT "v=spf1 a:$HOSTNAME ip4:$PUBLIC_IP ~all" $MAILDOMAIN. IN TXT "v=spf1 ip4:$PUBLIC_IP ~all"
Some explanation: SPF is basically a DNS entry (TXT), where you can define, which server hosts (a:[HOSTNAME]) or ip address (ip4:[IP_ADDRESS]) are allowed to send emails. So the receiver server (eg. gmail's server) can look up this entry and decide if you(as a sender server) is allowed to send emails as this email address.
If you are unsure, list more a:, ip4 entries, rather then fewer.
Example: company website: awesome.com company's email server: mail.awesome.com company's reverse dns entry for this email server: mail.awesome.com -> 11.22.33.44
SPF record in this case would be: awesome.com. IN TXT "v=spf1 a:mail.awesome.com a:awesome.com ip4:11.22.33.44 ~all"
The following servers can send emails for *@awesome.com email addresses: awesome.com (company's website handling server) mail.awesome.com (company's mail server) 11.22.33.44 (company's mail server's ip address)
Please note, that a:mail.awesome.com is the same as ip4:11.22.33.44, so it is redundant. But better safe than sorry. And in this example, the company's website handling server can also send emails and in general it is an outbound only server. If a website handles email sending (confirmation emails, contact form, etc).
DKIM
Add this TXT record to the $MAILDOMAIN DNS zone:
$DKIM_SELECTOR._domainkey.$MAILDOMAIN. IN TXT "$DKIM_DNS"
The DKIM .json text we added to wildduck server: curl -i -XPOST http://localhost:8080/dkim \ -H 'Content-type: application/json' \ -d '$DKIM_JSON'
Please refer to the manual how to change/delete/update DKIM keys via the REST api (with curl on localhost) for the newest version.
List DKIM keys: curl -i http://localhost:8080/dkim Delete DKIM: curl -i -XDELETE http://localhost:8080/dkim/59ef21aef255ed1d9d790e81
Move DKIM keys to another machine:
Save the above curl command and dns entry. Also copy the following two files too: /opt/zone-mta/keys/[MAILDOMAIN]-dkim.cert /opt/zone-mta/keys/[MAILDOMAIN]-dkim.pem
pem: private key (guard it well) cert: public key
DMARC
Add this TXT record to the $MAILDOMAIN DNS zone:
_dmarc.$MAILDOMAIN. IN TXT "v=DMARC1; p=reject;"
PTR
Make sure that your public IP has a PTR record set to $HOSTNAME. If your hosting provider does not allow you to set PTR records but has assigned their own hostname, then edit /etc/zone-mta/pools.toml and replace the hostname $HOSTNAME with the actual hostname of this server.
TL;DR
Add the following DNS records to the $MAILDOMAIN DNS zone:
$MAILDOMAIN. IN MX 5 $HOSTNAME. $MAILDOMAIN. IN TXT "v=spf1 ip4:$PUBLIC_IP ~all" $DKIM_SELECTOR._domainkey.$MAILDOMAIN. IN TXT "$DKIM_DNS" _dmarc.$MAILDOMAIN. IN TXT "v=DMARC1; p=reject;"
(this text is also stored to $INSTALLDIR/$MAILDOMAIN-nameserver.txt)" > "$INSTALLDIR/$MAILDOMAIN-nameserver.txt"
printf "Waiting for the server to start up.."
until $(curl --output /dev/null --silent --fail http://localhost:8080/users); do printf '.' sleep 2 done echo "."
Ensure DKIM key
echo "Registering DKIM key for $MAILDOMAIN" echo $DKIM_JSON
curl -i -XPOST http://localhost:8080/dkim
-H 'Content-type: application/json'
-d "$DKIM_JSON"
echo "" cat "$INSTALLDIR/$MAILDOMAIN-nameserver.txt" echo "" echo "All done, open https://$HOSTNAME/ in your browser"