public
nobgit
read
NobMail
Based on mailcow: dockerized
Languages
Repository composition by tracked source files.
PHP
49%
JavaScript
35%
HTML
9%
CSS
4%
Shell
2%
Python
1%
Lua
0%
Perl
0%
Ruby
0%
SCSS
0%
Create file
Wiki Documentation
Clone
https://nobgit.com/orgs/nobgit/nobmail.git
ssh://[email protected]:2222/orgs/nobgit/nobmail.git
Commit
Various changes...
25197380
data/Dockerfiles/acme/Dockerfile | 13 +++---
data/Dockerfiles/acme/docker-entrypoint.sh | 49 +++++++++++++++-------
data/Dockerfiles/dockerapi/server.py | 41 ++++++++++++++----
data/Dockerfiles/phpfpm/Dockerfile | 4 +-
.../postfix/whitelist_forwardinghosts.sh | 2 +-
data/Dockerfiles/watchdog/watchdog.sh | 48 ++++-----------------
6 files changed, 84 insertions(+), 73 deletions(-)
Diff
diff --git a/data/Dockerfiles/acme/Dockerfile b/data/Dockerfiles/acme/Dockerfile
index 44e7030b..5c99cbf9 100644
--- a/data/Dockerfiles/acme/Dockerfile
+++ b/data/Dockerfiles/acme/Dockerfile
@@ -3,20 +3,21 @@ FROM alpine:3.6
LABEL maintainer "Andre Peters <[email protected]>"
RUN apk add --update --no-cache \
- bash \
- curl \
- openssl \
- bind-tools \
- jq \
+ bash \
+ curl \
+ openssl \
+ bind-tools \
+ jq \
libressl-dev \
libbsd-dev \
libseccomp-dev \
- mariadb-client \
+ mariadb-client \
tini \
make \
gcc \
libressl \
libc-dev \
+ redis \
linux-headers \
ca-certificates \
&& curl -s https://kristaps.bsd.lv/acme-client/snapshots/acme-client-portable.tgz | tar xfvz - \
diff --git a/data/Dockerfiles/acme/docker-entrypoint.sh b/data/Dockerfiles/acme/docker-entrypoint.sh
index d29d812e..ef466d36 100755
--- a/data/Dockerfiles/acme/docker-entrypoint.sh
+++ b/data/Dockerfiles/acme/docker-entrypoint.sh
@@ -2,16 +2,30 @@
set -o pipefail
exec 5>&1
+log_f() {
+ if [[ ${2} == "no_nl" ]]; then
+ echo -n "$(date) - ${1}"
+ elif [[ ${2} == "no_date" ]]; then
+ echo "${1}"
+ elif [[ ${2} != "redis_only" ]]; then
+ echo "$(date) - ${1}"
+ fi
+ redis-cli -h redis LPUSH ACME_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}" | \
+ tr '%&;$"_[]{}-\r\n' ' ')\"}" > /dev/null
+ redis-cli -h redis LTRIM ACME_LOG 0 9999 > /dev/null
+}
+
if [[ "${SKIP_LETS_ENCRYPT}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
log_f "SKIP_LETS_ENCRYPT=y, skipping Let's Encrypt..."
sleep 365d
exec $(readlink -f "$0")
fi
-echo "Waiting for Docker API..."
+log_f "Waiting for Docker API..." no_nl
until ping dockerapi -c1 > /dev/null; do
sleep 1
done
+log_f "Found Docker API" no_date
ACME_BASE=/var/lib/acme
SSL_EXAMPLE=/var/lib/ssl-example
@@ -20,21 +34,12 @@ mkdir -p ${ACME_BASE}/acme/private
restart_containers(){
for container in $*; do
- echo "Restarting ${container}..."
- curl -X POST http://dockerapi:8080/containers/${container}/restart
+ log_f "Restarting ${container}..." no_nl
+ C_REST_OUT=$(curl -X POST http://dockerapi:8080/containers/${container}/restart | jq -r '.msg')
+ log_f "${C_REST_OUT}" no_date
done
}
-log_f() {
- if [[ ${2} == "no_nl" ]]; then
- echo -n "$(date) - ${1}"
- elif [[ ${2} == "no_date" ]]; then
- echo "${1}"
- else
- echo "$(date) - ${1}"
- fi
-}
-
array_diff() {
# https://stackoverflow.com/questions/2312762, Alex Offshore
eval local ARR1=\(\"\${$2[@]}\"\)
@@ -123,16 +128,23 @@ while true; do
declare -a VALIDATED_CONFIG_DOMAINS
declare -a ADDITIONAL_VALIDATED_SAN
IFS=',' read -r -a ADDITIONAL_SAN_ARR <<< "${ADDITIONAL_SAN}"
- IPV4=$(get_ipv4)
+ until [[ ${IPV4} == ${EXTERNAL_IPV4} ]]; do
+ IPV4=$(get_ipv4)
+ if [[ ${IPV4} != ${EXTERNAL_IPV4} ]]; then
+ echo "Waiting for correct source ip..."
+ sleep 30s
+ fi
+ done
# Container ids may have changed
CONTAINERS_RESTART=($(curl --silent http://dockerapi:8080/containers/json | jq -r '.[] | {name: .Config.Labels["com.docker.compose.service"], id: .Id}' | jq -rc 'select( .name | tostring | contains("nginx-mailcow") or contains("postfix-mailcow") or contains("dovecot-mailcow")) | .id' | tr "\n" " "))
- log_f "Waiting for domain tables... " no_nl
+ log_f "Waiting for domain table... " no_nl
while [[ -z ${DOMAIN_TABLE} ]]; do
+ curl --silent http://nginx/ >/dev/null 2>&1
DOMAIN_TABLE=$(mysql -h mysql-mailcow -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'domain'" -Bs)
[[ -z ${DOMAIN_TABLE} ]] && sleep 10
done
- log_f "OK" no_date
+ log_f "Found domain tables." no_date
while read domains; do
SQL_DOMAIN_ARR+=("${domains}")
@@ -226,6 +238,7 @@ while true; do
case "$?" in
0) # new certs
+ log_f "${ACME_RESPONSE}" redis_only
# cp the new certificates and keys
cp ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem
cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem
@@ -239,6 +252,7 @@ while true; do
restart_containers ${CONTAINERS_RESTART[*]}
;;
1) # failure
+ log_f "${ACME_RESPONSE}" redis_only
if [[ $ACME_RESPONSE =~ "No registration exists" ]]; then
log_f "Registration keys are invalid, deleting old keys and restarting..."
rm ${ACME_BASE}/acme/private/account.key
@@ -268,6 +282,7 @@ while true; do
exec $(readlink -f "$0")
;;
2) # no change
+ log_f "${ACME_RESPONSE}" redis_only
if ! diff ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem; then
log_f "Certificate was not changed, but active certificate does not match the verified certificate, fixing and restarting containers..."
cp ${ACME_BASE}/acme/fullchain.pem ${ACME_BASE}/cert.pem
@@ -280,9 +295,11 @@ while true; do
cp ${ACME_BASE}/acme/private/privkey.pem ${ACME_BASE}/key.pem
TRIGGER_RESTART=1
fi
+ log_f "Certificate was not changed"
[[ ${TRIGGER_RESTART} == 1 ]] && restart_containers ${CONTAINERS_RESTART[*]}
;;
*) # unspecified
+ log_f "${ACME_RESPONSE}" redis_only
if [[ -f ${ACME_BASE}/acme/private/${DATE}.bak/fullchain.pem ]] && [[ -f ${ACME_BASE}/acme/private/${DATE}.bak/privkey.pem ]]; then
log_f "Error requesting certificate, restoring previous certificate from backup and restarting containers...."
cp ${ACME_BASE}/acme/private/${DATE}.bak/fullchain.pem ${ACME_BASE}/cert.pem
diff --git a/data/Dockerfiles/dockerapi/server.py b/data/Dockerfiles/dockerapi/server.py
index 63916eb8..00f7ebef 100644
--- a/data/Dockerfiles/dockerapi/server.py
+++ b/data/Dockerfiles/dockerapi/server.py
@@ -6,6 +6,9 @@ from threading import Thread
import docker
import signal
import time
+import os
+import re
+import sys
docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock', version='auto')
app = Flask(__name__)
@@ -15,7 +18,7 @@ class containers_get(Resource):
def get(self):
containers = {}
try:
- for container in docker_client.containers.list(all=True):
+ for container in docker_client.containers.list(all=True, filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME']}):
containers.update({container.attrs['Id']: container.attrs})
return containers
except Exception as e:
@@ -25,19 +28,30 @@ class container_get(Resource):
def get(self, container_id):
if container_id and container_id.isalnum():
try:
- for container in docker_client.containers.list(all=True, filters={"id": container_id}):
+ for container in docker_client.containers.list(all=True, filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME'], "id": container_id}):
return container.attrs
except Exception as e:
return jsonify(type='danger', msg=e)
else:
return jsonify(type='danger', msg='no or invalid id defined')
+class container_logs(Resource):
+ def get(self, container_id, lines):
+ if container_id and container_id.isalnum() and lines:
+ try:
+ for container in docker_client.containers.list(all=True, filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME'], "id": container_id}):
+ return container.logs(stdout=True, stderr=True, stream=False, tail=lines)
+ except Exception as e:
+ return jsonify(type='danger', msg=e)
+ else:
+ return jsonify(type='danger', msg='no or invalid id defined')
+
class container_post(Resource):
def post(self, container_id, post_action):
if container_id and container_id.isalnum() and post_action:
if post_action == 'stop':
try:
- for container in docker_client.containers.list(all=True, filters={"id": container_id}):
+ for container in docker_client.containers.list(all=True, filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME'], "id": container_id}):
container.stop()
return jsonify(type='success', msg='command completed successfully')
except Exception as e:
@@ -45,7 +59,7 @@ class container_post(Resource):
elif post_action == 'start':
try:
- for container in docker_client.containers.list(all=True, filters={"id": container_id}):
+ for container in docker_client.containers.list(all=True, filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME'], "id": container_id}):
container.start()
return jsonify(type='success', msg='command completed successfully')
except Exception as e:
@@ -53,7 +67,7 @@ class container_post(Resource):
elif post_action == 'restart':
try:
- for container in docker_client.containers.list(all=True, filters={"id": container_id}):
+ for container in docker_client.containers.list(all=True, filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME'], "id": container_id}):
container.restart()
return jsonify(type='success', msg='command completed successfully')
except Exception as e:
@@ -66,16 +80,28 @@ class container_post(Resource):
if request.json['cmd'] == 'sieve_list' and request.json['username']:
try:
- for container in docker_client.containers.list(filters={"id": container_id}):
+ for container in docker_client.containers.list(filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME'], "id": container_id}):
return container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm sieve list -u '" + request.json['username'].replace("'", "'\\''") + "'"], user='vmail')
except Exception as e:
return jsonify(type='danger', msg=e)
elif request.json['cmd'] == 'sieve_print' and request.json['script_name'] and request.json['username']:
try:
- for container in docker_client.containers.list(filters={"id": container_id}):
+ for container in docker_client.containers.list(filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME'], "id": container_id}):
return container.exec_run(["/bin/bash", "-c", "/usr/local/bin/doveadm sieve get -u '" + request.json['username'].replace("'", "'\\''") + "' '" + request.json['script_name'].replace("'", "'\\''") + "'"], user='vmail')
except Exception as e:
return jsonify(type='danger', msg=e)
+ elif request.json['cmd'] == 'worker_password' and request.json['raw']:
+ try:
+ for container in docker_client.containers.list(filters={"label": "com.docker.compose.project=" + os.environ['COMPOSE_PROJECT_NAME'], "id": container_id}):
+ hash = container.exec_run(["/bin/bash", "-c", "/usr/bin/rspamadm pw -e -p '" + request.json['raw'].replace("'", "'\\''") + "'"], user='_rspamd')
+ f = open("/access.inc", "w")
+ f.write('enable_password = "' + re.sub('[^0-9a-zA-Z\$]+', '', hash.rstrip()) + '";\n')
+ f.close()
+ container.restart()
+ return jsonify(type='success', msg='command completed successfully')
+ except Exception as e:
+ return jsonify(type='danger', msg=e)
+
else:
return jsonify(type='danger', msg='Unknown command')
@@ -99,6 +125,7 @@ def startFlaskAPI():
api.add_resource(containers_get, '/containers/json')
api.add_resource(container_get, '/containers/<string:container_id>/json')
+api.add_resource(container_logs, '/containers/<string:container_id>/logs/<int:lines>')
api.add_resource(container_post, '/containers/<string:container_id>/<string:post_action>')
if __name__ == '__main__':
diff --git a/data/Dockerfiles/phpfpm/Dockerfile b/data/Dockerfiles/phpfpm/Dockerfile
index 84a1f67b..5259e70d 100644
--- a/data/Dockerfiles/phpfpm/Dockerfile
+++ b/data/Dockerfiles/phpfpm/Dockerfile
@@ -41,8 +41,8 @@ RUN apk add -U --no-cache libxml2-dev \
Net_Sieve \
NET_SMTP \
Mail_mime \
- && pecl install redis-${REDIS_PECL} memcached-${MEMCACHED_PECL} APCu-${APCU_PECL} imagick-${IMAGICK_PECL} \
- && docker-php-ext-enable redis apcu memcached imagick \
+ && pecl install redis-${REDIS_PECL} memcached-${MEMCACHED_PECL} APCu-${APCU_PECL} imagick-${IMAGICK_PECL} mailparse \
+ && docker-php-ext-enable redis apcu memcached imagick mailparse \
&& pecl clear-cache \
&& docker-php-ext-configure intl \
&& docker-php-ext-install -j 4 intl gettext ldap sockets soap pdo pdo_mysql xmlrpc gd zip pcntl opcache \
diff --git a/data/Dockerfiles/postfix/whitelist_forwardinghosts.sh b/data/Dockerfiles/postfix/whitelist_forwardinghosts.sh
index ab066d89..4ad5ab32 100755
--- a/data/Dockerfiles/postfix/whitelist_forwardinghosts.sh
+++ b/data/Dockerfiles/postfix/whitelist_forwardinghosts.sh
@@ -6,7 +6,7 @@ while read QUERY; do
echo "500 dunno"
continue
fi
- result=$(curl -s http://172.22.1.251:8081/forwardinghosts.php?host=${QUERY[1]})
+ result=$(curl -s http://nginx:8081/forwardinghosts.php?host=${QUERY[1]})
logger -t whitelist_forwardinghosts -p mail.info "Look up ${QUERY[1]} on whitelist, result $result"
echo ${result}
done
diff --git a/data/Dockerfiles/watchdog/watchdog.sh b/data/Dockerfiles/watchdog/watchdog.sh
index 902f66dc..ec7133e5 100755
--- a/data/Dockerfiles/watchdog/watchdog.sh
+++ b/data/Dockerfiles/watchdog/watchdog.sh
@@ -28,21 +28,19 @@ progress() {
[[ ${CURRENT} -gt ${TOTAL} ]] && return
[[ ${CURRENT} -lt 0 ]] && CURRENT=0
PERCENT=$(( 200 * ${CURRENT} / ${TOTAL} % 2 + 100 * ${CURRENT} / ${TOTAL} ))
- log_msg "${SERVICE} health level: ${PERCENT}% (${CURRENT}/${TOTAL}), health trend: ${DIFF}"
- log_data "$(printf "%d,%d,%d,%d" ${PERCENT} ${CURRENT} ${TOTAL} ${DIFF})" "${SERVICE}"
+ redis-cli -h redis LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"service\":\"${SERVICE}\",\"lvl\":\"${PERCENT}\",\"hpnow\":\"${CURRENT}\",\"hptotal\":\"${TOTAL}\",\"hpdiff\":\"${DIFF}\"}" > /dev/null
+ log_msg "${SERVICE} health level: ${PERCENT}% (${CURRENT}/${TOTAL}), health trend: ${DIFF}" no_redis
}
log_msg() {
- redis-cli -h redis LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}")\"}" > /dev/null
+ if [[ ${2} != "no_redis" ]]; then
+ redis-cli -h redis LPUSH WATCHDOG_LOG "{\"time\":\"$(date +%s)\",\"message\":\"$(printf '%s' "${1}" | \
+ tr '%&;$"_[]{}-\r\n' ' ')\"}" > /dev/null
+ fi
+ redis-cli -h redis LTRIM WATCHDOG_LOG 0 9999 > /dev/null
echo $(date) $(printf '%s\n' "${1}")
}
-log_data() {
- [[ -z ${1} ]] && return 1
- [[ -z ${2} ]] && return 2
- redis-cli -h redis LPUSH WATCHDOG_DATA "{\"time\":\"$(date +%s)\",\"service\":\"data\",\"$(printf '%s' "${2}")\":\"$(printf '%s' "${1}")\"}" > /dev/null
-}
-
function mail_error() {
[[ -z ${1} ]] && return 1
[[ -z ${2} ]] && return 2
@@ -234,27 +232,6 @@ Empty
return 1
}
-dns_checks() {
- err_count=0
- diff_c=0
- THRESHOLD=28
- # Reduce error count by 2 after restarting an unhealthy container
- trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
- while [ ${err_count} -lt ${THRESHOLD} ]; do
- host_ip=$(get_container_ip unbound-mailcow)
- err_c_cur=${err_count}
- /usr/lib/nagios/plugins/check_dns -H google.com 1>&2; err_count=$(( ${err_count} + ($? * 2)))
- /usr/lib/nagios/plugins/check_dns -s ${host_ip} -H google.com 1>&2; err_count=$(( ${err_count} + ($? * 2)))
- dig +dnssec org. @${host_ip} | grep -E 'flags:.+ad' 1>&2; err_count=$(( ${err_count} + ($? * 2)))
- [ ${err_c_cur} -eq ${err_count} ] && [ ! $((${err_count} - 1)) -lt 0 ] && err_count=$((${err_count} - 1)) diff_c=1
- [ ${err_c_cur} -ne ${err_count} ] && diff_c=$(( ${err_c_cur} - ${err_count} ))
- progress "Unbound" ${THRESHOLD} $(( ${THRESHOLD} - ${err_count} )) ${diff_c}
- diff_c=0
- sleep $(( ( RANDOM % 30 ) + 10 ))
- done
- return 1
-}
-
# Create watchdog agents
(
while true; do
@@ -322,17 +299,6 @@ done
) &
BACKGROUND_TASKS+=($!)
-(
-while true; do
- if ! dns_checks; then
- log_msg "Unbound hit error limit"
- [[ ! -z ${WATCHDOG_NOTIFY_EMAIL} ]] && mail_error "${WATCHDOG_NOTIFY_EMAIL}" "unbound-mailcow"
- #echo unbound-mailcow > /tmp/com_pipe
- fi
-done
-) &
-BACKGROUND_TASKS+=($!)
-
(
while true; do
if ! rspamd_checks; then