changes from tomav#1599 without `start-mailserver.sh`

included all changes from the work on refactoring all scripts, but excluded one big script to make merging easier; replaced mapfile with read
This commit is contained in:
Georg Lauterbach 2020-09-05 16:19:12 +02:00
parent 14aa0cdcc3
commit bf679a5504
No known key found for this signature in database
GPG Key ID: 386D76E7AD496709
11 changed files with 650 additions and 356 deletions

View File

@ -1,23 +1,41 @@
branches: branches:
except: except:
- donttestme - donttestme
language: bash language: bash
sudo: required sudo: required
env: env:
global: global:
- HADOLINT_VERSION=1.17.1 - HADOLINT_VERSION=1.17.1
- SHELLCHECK_VERSION=latest
addons:
apt:
packages:
- xz-utils
services: services:
- docker - docker
before_install: before_install:
- sudo curl -L https://github.com/hadolint/hadolint/releases/download/v$HADOLINT_VERSION/hadolint-$(uname -s)-$(uname -m) -o /usr/local/bin/hadolint - sudo curl -L https://github.com/hadolint/hadolint/releases/download/v$HADOLINT_VERSION/hadolint-$(uname -s)-$(uname -m) -o /usr/local/bin/hadolint
- sudo chmod +rx /usr/local/bin/hadolint - sudo chmod +rx /usr/local/bin/hadolint
- sudo wget -qO- "https://github.com/koalaman/shellcheck/releases/download/${SHELLCHECK_VERSION}/shellcheck-${SHELLCHECK_VERSION}.linux.x86_64.tar.xz" | tar -xJv
- sudo cp "shellcheck-${SHELLCHECK_VERSION}/shellcheck" /usr/bin/
install: install:
- make lint - make lint
- travis_retry travis_wait make build - travis_retry travis_wait make build
script: script:
- make generate-accounts run generate-accounts-after-run fixtures tests - make shellcheck
- make generate-accounts run generate-accounts-after-run fixtures tests
after_script: after_script:
- make clean - make clean
notifications: notifications:
slack: slack:
secure: TTo1z9nbZCWcIdfPwypubNa3y+pwvfgDGlzEVAGEuK7uuIpmEoAcAUNSSPTnbewDGHnDl8t/ml93MtvP+a+IVuAKytMqF39PHyoZO7aUl9J62V+G75OmnyGjXGJm40pQosCS6LzqoRRYXotl9+fwH568Kf4ifXCrMZX1d+ir7Ww= secure: TTo1z9nbZCWcIdfPwypubNa3y+pwvfgDGlzEVAGEuK7uuIpmEoAcAUNSSPTnbewDGHnDl8t/ml93MtvP+a+IVuAKytMqF39PHyoZO7aUl9J62V+G75OmnyGjXGJm40pQosCS6LzqoRRYXotl9+fwH568Kf4ifXCrMZX1d+ir7Ww=

View File

@ -2,18 +2,22 @@
`docker-mailserver` is OpenSource. That means that you can contribute on enhancements, bug fixing or improving the documentation in the Wiki. `docker-mailserver` is OpenSource. That means that you can contribute on enhancements, bug fixing or improving the documentation in the Wiki.
## Open an issue ## Issues & PRs
### Open an issue
When opening an issue, please provide details use case to let the community reproduce your problem. When opening an issue, please provide details use case to let the community reproduce your problem.
Please start the mail server with env `DMS_DEBUG=1` and paste the ouput into the issue. Please start the mail server with env `DMS_DEBUG=1` and paste the output into the issue.
## Pull Requests ### Pull Requests
#### Project architecture #### Project architecture
├── config # User: personal configurations ``` TXT
├── target # Developer: default server configuration, used when building the image ├── config # User: personal configurations
└── test # Developer: integration tests to check that everything keeps working ├── target # Developer: default server configuration, used when building the image
└── test # Developer: integration tests to check that everything keeps working
```
#### Submit a Pull-Request #### Submit a Pull-Request
@ -35,3 +39,171 @@ The development workflow is the following:
- When changed are validated, your branch is merged into `master` - When changed are validated, your branch is merged into `master`
- `master` is automatically tested on Travis - `master` is automatically tested on Travis
- Docker builds a new `latest` image - Docker builds a new `latest` image
## Coding Style
### Bash and Shell
When refactoring, writing or altering Script, that is Shell and Bash scripts, in any way, adhere to these rules:
1. **Adjust your style of coding to the style that is already present**! Even if you do not like it, this is due to consistency. Look up the GNU coding style guide. There was a lot of work involved in making these scripts consistent.
2. **Use `shellcheck` to check your scripts**! Your contributions are checked by TravisCI with shellcheck.
3. There is a **`.editorconfig`** file. Make your IDE use it or adhere to it manually!
4. It's okay to use `/bin/bash` instead of `/bin/sh`. You can alternatively use `/usr/bin/env bash`.
5. `setup.sh` provides a good starting point to look for.
6. When appropriate, use the `set` builtin. We recommend `set -euEo pipefail` (very strong) or `set -uE` (weaker).
#### Styling rules
##### initial description
When writing a script, provide the version and the script's task like so:
``` BASH
#!/usr/bin/env bash
# version 0.1.0
#
# <TASK DESCRIPTION> -> cut this off
# to make it not longer than approx.
# 60 cols.
```
We use [semantic versioning](https://semver.org/) - so do you.
##### if-else-statements
``` BASH
if <CONDITION1>
then
<CODE TO RUN>
elif <CONDITION2>
<CODE TO TUN>
else
<CODE TO RUN>
fi
# when using braces, use double braces!
if [[ <CONDITION1> ]] && [[ <CONDITION2> ]]
then
<CODE TO RUN>
fi
# remember you do not need "" when using [[ ]]
if [[ -f $FILE ]] # is fine
then
<CODE TO RUN>
fi
# equality checks with numbers - use -eq/-ne/-lt/-ge, not != or ==
if [[ $VAR -ne <NUMBER> ]] && [[ $SOME_VAR -eq 6 ]] || [[ $SOME_VAR -lt 42 ]]
then
<CODE TO RUN>
elif [[ $SOME_VAR -ge 242 ]]
then
<CODE TO RUN>
fi
```
##### variables
Variables are always uppercase.
``` BASH
# good
local VAR="good"
# bad
var="bad"
```
##### braces
We use braces in the following way:
``` BASH
# when it's clear and unambiguous,
# you do not have to use braces,
# but you might, see shellcheck SC2248
$VAR
# or
${VAR}
# when the variable is used
# in a bigger context
echo "/some/dir/${VAR}/to/destination/"
```
##### loops
Like `if-else`, loops look like this
``` BASH
for / while <LOOP CONDITION>
do
<CODE TO RUN>
done
```
##### functions
It's always nice to see the use of functions. Not only as it's more C-style, but it also provides a clear structure. If scripts are small, this is unnecessary, but if they become larger, please consider using functions. When doing so, provide `function _main()`. When using functions, they are **always** at the top of the script!
``` BASH
function _<name_underscored_and_lowercase>()
{
<CODE TO RUN>
# variables that can be local should be local
local _<LOCAL_VARIABLE_NAME>
}
```
##### error tracing
A construct to trace error in your scripts looks like this:
``` BASH
set -euxEo pipefail
trap '_report_err $_ $LINENO $?' ERR
function _report_err()
{
echo "ERROR occurred :: source (hint) $1 ; line $2 ; exit code $3 ;;" >&2
<CODE TO RUN AFTERWARDS>
}
```
Please use it like this (copy-paste) to make errors streamlined. Remember: Remove `set -x` in the end. This of debugging purposes only.
##### comments and descriptiveness
Comments should be kept minimal and only describe non-obvious matters, i.e. not what the code does. Comments should start lowercase as most of them are not sentences. Make the code **self-descriptive** by using meaningful names! Make comments not longer than approximately 60 columns, then wrap the line.
A negative example:
``` BASH
# adds one to the first argument
# and print it to stdout
function _add_one()
{
# save the first variable
local FIRST=$1
# add one here
local RESULT=$(( _FIRST + 1 ))
# print it to stdout
echo "$_RESULT"
}
```
A positive example:
``` BASH
# writes result to stdout
function _add_one()
{
echo $(( $1 + 1 ))
}

View File

@ -1,9 +1,14 @@
SHELL = /bin/bash
NAME = tvial/docker-mailserver:testing NAME = tvial/docker-mailserver:testing
VCS_REF := $(shell git rev-parse --short HEAD) VCS_REF := $(shell git rev-parse --short HEAD)
VCS_VERSION := $(shell git describe --tags --contains --always) VCS_VERSION := $(shell git describe --tags --contains --always)
SLEEP = 15s
all: build backup generate-accounts run generate-accounts-after-run fixtures tests clean all: build backup generate-accounts run generate-accounts-after-run fixtures tests clean
no-build: backup generate-accounts run generate-accounts-after-run fixtures tests clean no-build: backup generate-accounts run generate-accounts-after-run fixtures tests clean
complete_test: lint build generate-accounts run generate-accounts-after-run fixtures tests
build: build:
docker build \ docker build \
@ -12,22 +17,24 @@ build:
-t $(NAME) . -t $(NAME) .
backup: backup:
# if backup directories exist, clean hasn't been called, therefore we shouldn't overwrite it. It still contains the original content. # if backup directories exist, clean hasn't been called, therefore
@if [ ! -d config.bak ]; then\ # we shouldn't overwrite it. It still contains the original content.
cp -rp config config.bak; \ @ if [ ! -d config.bak ]; then\
cp -rp config config.bak;\
fi fi
@if [ ! -d testconfig.bak ]; then\ @ if [ ! -d testconfig.bak ]; then\
cp -rp test/config testconfig.bak ;\ cp -rp test/config testconfig.bak;\
fi fi
generate-accounts: generate-accounts:
docker run --rm -e MAIL_USER=user1@localhost.localdomain -e MAIL_PASS=mypassword -t $(NAME) /bin/sh -c 'echo "$$MAIL_USER|$$(doveadm pw -s SHA512-CRYPT -u $$MAIL_USER -p $$MAIL_PASS)"' > test/config/postfix-accounts.cf @ docker run --rm -e MAIL_USER=user1@localhost.localdomain -e MAIL_PASS=mypassword -t $(NAME) /bin/sh -c 'echo "$$MAIL_USER|$$(doveadm pw -s SHA512-CRYPT -u $$MAIL_USER -p $$MAIL_PASS)"' > test/config/postfix-accounts.cf
docker run --rm -e MAIL_USER=user2@otherdomain.tld -e MAIL_PASS=mypassword -t $(NAME) /bin/sh -c 'echo "$$MAIL_USER|$$(doveadm pw -s SHA512-CRYPT -u $$MAIL_USER -p $$MAIL_PASS)"' >> test/config/postfix-accounts.cf @ docker run --rm -e MAIL_USER=user2@otherdomain.tld -e MAIL_PASS=mypassword -t $(NAME) /bin/sh -c 'echo "$$MAIL_USER|$$(doveadm pw -s SHA512-CRYPT -u $$MAIL_USER -p $$MAIL_PASS)"' >> test/config/postfix-accounts.cf
echo "# this is a test comment, please don't delete me :'(" >> test/config/postfix-accounts.cf @ echo "# this is a test comment, please don't delete me :'(" >> test/config/postfix-accounts.cf
echo " # this is also a test comment, :O" >> test/config/postfix-accounts.cf @ echo " # this is also a test comment, :O" >> test/config/postfix-accounts.cf
run: run:
# Run containers # run containers
-@ echo "Sleeping $(SLEEP) after each container"
docker run --rm -d --name mail \ docker run --rm -d --name mail \
-v "`pwd`/test/config":/tmp/docker-mailserver \ -v "`pwd`/test/config":/tmp/docker-mailserver \
-v "`pwd`/test/test-files":/tmp/docker-mailserver-test:ro \ -v "`pwd`/test/test-files":/tmp/docker-mailserver-test:ro \
@ -50,14 +57,14 @@ run:
-e PERMIT_DOCKER=host \ -e PERMIT_DOCKER=host \
-e DMS_DEBUG=0 \ -e DMS_DEBUG=0 \
-h mail.my-domain.com -t $(NAME) -h mail.my-domain.com -t $(NAME)
sleep 15 -@ sleep $(SLEEP)
docker run --rm -d --name mail_smtponly_without_config \ docker run --rm -d --name mail_smtponly_without_config \
-e SMTP_ONLY=1 \ -e SMTP_ONLY=1 \
-e ENABLE_LDAP=1 \ -e ENABLE_LDAP=1 \
-e PERMIT_DOCKER=network \ -e PERMIT_DOCKER=network \
-e OVERRIDE_HOSTNAME=mail.mydomain.com \ -e OVERRIDE_HOSTNAME=mail.mydomain.com \
-t $(NAME) -t $(NAME)
sleep 15 -@ sleep $(SLEEP)
docker run --rm -d --name mail_override_hostname \ docker run --rm -d --name mail_override_hostname \
-v "`pwd`/test/config":/tmp/docker-mailserver \ -v "`pwd`/test/config":/tmp/docker-mailserver \
-v "`pwd`/test/test-files":/tmp/docker-mailserver-test:ro \ -v "`pwd`/test/test-files":/tmp/docker-mailserver-test:ro \
@ -67,7 +74,7 @@ run:
-e OVERRIDE_HOSTNAME=mail.my-domain.com \ -e OVERRIDE_HOSTNAME=mail.my-domain.com \
-h unknown.domain.tld \ -h unknown.domain.tld \
-t $(NAME) -t $(NAME)
sleep 15 -@ sleep $(SLEEP)
docker run --rm -d --name mail_domainname \ docker run --rm -d --name mail_domainname \
-v "`pwd`/test/config":/tmp/docker-mailserver \ -v "`pwd`/test/config":/tmp/docker-mailserver \
-v "`pwd`/test/test-files":/tmp/docker-mailserver-test:ro \ -v "`pwd`/test/test-files":/tmp/docker-mailserver-test:ro \
@ -77,7 +84,7 @@ run:
-e DOMAINNAME=my-domain.com \ -e DOMAINNAME=my-domain.com \
-h unknown.domain.tld \ -h unknown.domain.tld \
-t $(NAME) -t $(NAME)
sleep 15 -@ sleep $(SLEEP)
docker run --rm -d --name mail_srs_domainname \ docker run --rm -d --name mail_srs_domainname \
-v "`pwd`/test/config":/tmp/docker-mailserver \ -v "`pwd`/test/config":/tmp/docker-mailserver \
-v "`pwd`/test/test-files":/tmp/docker-mailserver-test:ro \ -v "`pwd`/test/test-files":/tmp/docker-mailserver-test:ro \
@ -88,7 +95,7 @@ run:
-e DOMAINNAME=my-domain.com \ -e DOMAINNAME=my-domain.com \
-h unknown.domain.tld \ -h unknown.domain.tld \
-t $(NAME) -t $(NAME)
sleep 15 -@ sleep $(SLEEP)
docker run --rm -d --name mail_disabled_clamav_spamassassin \ docker run --rm -d --name mail_disabled_clamav_spamassassin \
-v "`pwd`/test/config":/tmp/docker-mailserver \ -v "`pwd`/test/config":/tmp/docker-mailserver \
-v "`pwd`/test/test-files":/tmp/docker-mailserver-test:ro \ -v "`pwd`/test/test-files":/tmp/docker-mailserver-test:ro \
@ -96,19 +103,19 @@ run:
-e ENABLE_SPAMASSASSIN=0 \ -e ENABLE_SPAMASSASSIN=0 \
-e DMS_DEBUG=0 \ -e DMS_DEBUG=0 \
-h mail.my-domain.com -t $(NAME) -h mail.my-domain.com -t $(NAME)
sleep 15 -@ sleep $(SLEEP)
generate-accounts-after-run: generate-accounts-after-run:
docker run --rm -e MAIL_USER=added@localhost.localdomain -e MAIL_PASS=mypassword -t $(NAME) /bin/sh -c 'echo "$$MAIL_USER|$$(doveadm pw -s SHA512-CRYPT -u $$MAIL_USER -p $$MAIL_PASS)"' >> test/config/postfix-accounts.cf @ docker run --rm -e MAIL_USER=added@localhost.localdomain -e MAIL_PASS=mypassword -t $(NAME) /bin/sh -c 'echo "$$MAIL_USER|$$(doveadm pw -s SHA512-CRYPT -u $$MAIL_USER -p $$MAIL_PASS)"' >> test/config/postfix-accounts.cf
docker exec mail addmailuser pass@localhost.localdomain 'may be \a `p^a.*ssword' @ docker exec mail addmailuser pass@localhost.localdomain 'may be \a `p^a.*ssword'
@ sleep $(SLEEP)
sleep 10
fixtures: fixtures:
# Setup sieve # setup sieve
docker cp "`pwd`/test/config/sieve/dovecot.sieve" mail:/var/mail/localhost.localdomain/user1/.dovecot.sieve docker cp "`pwd`/test/config/sieve/dovecot.sieve" mail:/var/mail/localhost.localdomain/user1/.dovecot.sieve
sleep 30 sleep $(SLEEP)
# Sending test mails sleep $(SLEEP)
# sending test mails
docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/amavis-spam.txt" docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/amavis-spam.txt"
docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/amavis-virus.txt" docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/amavis-virus.txt"
docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-alias-external.txt" docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-alias-external.txt"
@ -126,35 +133,44 @@ fixtures:
docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/non-existing-user.txt" docker exec mail /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/non-existing-user.txt"
docker exec mail_disabled_clamav_spamassassin /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-user1.txt" docker exec mail_disabled_clamav_spamassassin /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-user1.txt"
docker exec mail /bin/sh -c "sendmail root < /tmp/docker-mailserver-test/email-templates/root-email.txt" docker exec mail /bin/sh -c "sendmail root < /tmp/docker-mailserver-test/email-templates/root-email.txt"
# postfix virtual transport lmtp # postfix virtual transport lmtp
docker exec mail_override_hostname /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-user1.txt" docker exec mail_override_hostname /bin/sh -c "nc 0.0.0.0 25 < /tmp/docker-mailserver-test/email-templates/existing-user1.txt"
# Wait for mails to be analyzed # wait for mails to be analyzed
sleep 80 sleep 80
tests: tests:
# Start tests
./test/bats/bin/bats test/*.bats ./test/bats/bin/bats test/*.bats
.PHONY: ALWAYS_RUN .PHONY: ALWAYS_RUN
test/%.bats: ALWAYS_RUN test/%.bats: ALWAYS_RUN
./test/bats/bin/bats $@ ./test/bats/bin/bats $@
lint: lint:
# List files which name starts with 'Dockerfile' # List files which name starts with 'Dockerfile'
# eg. Dockerfile, Dockerfile.build, etc. # eg. Dockerfile, Dockerfile.build, etc.
git ls-files --exclude='Dockerfile*' --ignored | xargs --max-lines=1 hadolint -@ git ls-files --exclude='Dockerfile*' --ignored | xargs --max-lines=1 hadolint
clean: clean:
# Remove running and stopped test containers # remove running and stopped test containers
-docker ps -a | grep -E "docker-mailserver:testing|ldap_for_mail" | cut -f 1-1 -d ' ' | xargs --no-run-if-empty docker rm -f -@ docker ps -a | grep -E "docker-mailserver:testing|ldap_for_mail" | cut -f 1-1 -d ' ' | xargs --no-run-if-empty docker rm -f
-@ if [ -d config.bak ]; then\
@if [ -d config.bak ]; then\
rm -rf config ;\ rm -rf config ;\
mv config.bak config ;\ mv config.bak config ;\
fi fi
@if [ -d testconfig.bak ]; then\ -@ if [ -d testconfig.bak ]; then\
sudo rm -rf test/config ;\ sudo rm -rf test/config ;\
mv testconfig.bak test/config ;\ mv testconfig.bak test/config ;\
fi fi
-sudo rm -rf test/onedir test/alias test/quota test/relay test/config/dovecot-lmtp/userdb test/config/key* test/config/opendkim/keys/domain.tld/ test/config/opendkim/keys/example.com/ test/config/opendkim/keys/localdomain2.com/ test/config/postfix-aliases.cf test/config/postfix-receive-access.cf test/config/postfix-receive-access.cfe test/config/dovecot-quotas.cf test/config/postfix-send-access.cf test/config/postfix-send-access.cfe test/config/relay-hosts/chksum test/config/relay-hosts/postfix-aliases.cf test/config/dhparams.pem -@ sudo rm -rf test/onedir test/alias test/quota test/relay test/config/dovecot-lmtp/userdb test/config/key* test/config/opendkim/keys/domain.tld/ test/config/opendkim/keys/example.com/ test/config/opendkim/keys/localdomain2.com/ test/config/postfix-aliases.cf test/config/postfix-receive-access.cf test/config/postfix-receive-access.cfe test/config/dovecot-quotas.cf test/config/postfix-send-access.cf test/config/postfix-send-access.cfe test/config/relay-hosts/chksum test/config/relay-hosts/postfix-aliases.cf test/config/dhparams.pem test/config/dovecot-lmtp/dh.pem test/config/relay-hosts/dovecot-quotas.cf test/config/user-patches.sh
shellcheck:
@ echo -e "Testing shell / bash scripts with shellcheck\n"
@ shellcheck --version
@ echo ''
# currently without `start-mailserver` as this is to be merged separately
@ if find -iname "*.sh" -not -path "./test/*" -not -path "./target/docker-configomat/*" -not -wholename ./target/start-mailserver.sh -exec shellcheck -S style -Cauto -e SC2250,SC2154,SC2248 -W 50 {} \; | grep .; then\
echo -e "\nError" ;\
exit 1 ;\
else\
echo -e '\nSuccess' ;\
fi

View File

@ -1,11 +1,26 @@
# docker-mailserver # docker-mailserver
[![Build Status](https://travis-ci.org/tomav/docker-mailserver.svg?branch=master)](https://travis-ci.org/tomav/docker-mailserver) [![Docker Pulls](https://img.shields.io/docker/pulls/tvial/docker-mailserver.svg)](https://hub.docker.com/r/tvial/docker-mailserver/) [![Docker layers](https://images.microbadger.com/badges/image/tvial/docker-mailserver.svg)](https://microbadger.com/images/tvial/docker-mailserver) [![Github Stars](https://img.shields.io/github/stars/tomav/docker-mailserver.svg?label=github%20%E2%98%85)](https://github.com/tomav/docker-mailserver/) [![Github Stars](https://img.shields.io/github/contributors/tomav/docker-mailserver.svg)](https://github.com/tomav/docker-mailserver/) [![Github Forks](https://img.shields.io/github/forks/tomav/docker-mailserver.svg?label=github%20forks)](https://github.com/tomav/docker-mailserver/) [![Gitter](https://img.shields.io/gitter/room/tomav/docker-mailserver.svg)](https://gitter.im/tomav/docker-mailserver) [![Build Status][build_status]][build_status::travis] [![Docker Pulls][docker_pulls]][docker_hub_pulls::hub] [![Docker layers][layers]][layers_outer::badger] [![Github Stars][gh_stars]][repo] [![Contributors][contributors]][repo] [![Github Forks][forks]][repo] [![Gitter][shields::gitter]][gitter]
[build_status]: https://travis-ci.org/tomav/docker-mailserver.svg?branch=master
[build_status::travis]: https://travis-ci.org/tomav/docker-mailserver
[docker_pulls]: https://img.shields.io/docker/pulls/tvial/docker-mailserver.svg
[docker_hub_pulls::hub]: https://hub.docker.com/r/tvial/docker-mailserver/
[layers]: https://images.microbadger.com/badges/image/tvial/docker-mailserver.svg
[layers_outer::badger]: https://microbadger.com/images/tvial/docker-mailserver
[gh_stars]: https://img.shields.io/github/stars/tomav/docker-mailserver.svg?label=github%20%E2%98%85
[repo]: https://github.com/tomav/docker-mailserver/
[contributors]: https://img.shields.io/github/contributors/tomav/docker-mailserver.svg
[forks]: https://img.shields.io/github/forks/tomav/docker-mailserver.svg?label=github%20forks
[shields::gitter]: https://img.shields.io/gitter/room/tomav/docker-mailserver.svg
[gitter]: https://gitter.im/tomav/docker-mailserver
A fullstack but simple mail server (smtp, imap, antispam, antivirus...). A fullstack but simple mail server (smtp, imap, antispam, antivirus...).
Only configuration files, no SQL database. Keep it simple and versioned. Only configuration files, no SQL database. Keep it simple and versioned.
Easy to deploy and upgrade. Easy to deploy and upgrade.
Why I created this image: [Simple mail server with Docker](http://tvi.al/simple-mail-server-with-docker/)
## ANNOUNCEMENT ## ANNOUNCEMENT
At this point we have merged the next branch based on Debian Buster into master. At this point we have merged the next branch based on Debian Buster into master.
@ -19,7 +34,7 @@ The following possibly breaking changes are known:
If you want to stick to the old version a while longer, either switch to stable or to a specific version. If you want to stick to the old version a while longer, either switch to stable or to a specific version.
If you run into problems, please raise issues and ask for help. Don't forget to provide details. If you run into problems, please raise issues and ask for help. Don't forget to provide details.
Includes: ## Includes
- [Postfix](http://www.postfix.org) with smtp or ldap auth - [Postfix](http://www.postfix.org) with smtp or ldap auth
- [Dovecot](https://www.dovecot.org) for sasl, imap (and optional pop3) with ssl support, with ldap auth, sieve and [quotas](https://github.com/tomav/docker-mailserver/wiki/Configure-Accounts#mailbox-quota) - [Dovecot](https://www.dovecot.org) for sasl, imap (and optional pop3) with ssl support, with ldap auth, sieve and [quotas](https://github.com/tomav/docker-mailserver/wiki/Configure-Accounts#mailbox-quota)
@ -42,9 +57,9 @@ Includes:
- Plus addressing (a.k.a. [extension delimiters](http://www.postfix.org/postconf.5.html#recipient_delimiter)) - Plus addressing (a.k.a. [extension delimiters](http://www.postfix.org/postconf.5.html#recipient_delimiter))
works out of the box: email for `you+extension@example.com` go to `you@example.com` works out of the box: email for `you+extension@example.com` go to `you@example.com`
Why I created this image: [Simple mail server with Docker](http://tvi.al/simple-mail-server-with-docker/) ## Issues & Contributing
Before you open an issue, please have a look this `README`, the [Wiki](https://github.com/tomav/docker-mailserver/wiki/) and Postfix/Dovecot documentation. Before you open an issue, please have a look this `README`, the [Wiki](https://github.com/tomav/docker-mailserver/wiki/) and Postfix/Dovecot documentation. If you'd like to contribute, read [`CONTRIBUTING.md`](./CONTRIBUTING.md) thoroughly.
## Requirements ## Requirements
@ -67,7 +82,8 @@ Minimum:
Download the docker-compose.yml, the .env and the setup.sh files: Download the docker-compose.yml, the .env and the setup.sh files:
``` SH
``` BASH
curl -o setup.sh https://raw.githubusercontent.com/tomav/docker-mailserver/master/setup.sh; chmod a+x ./setup.sh curl -o setup.sh https://raw.githubusercontent.com/tomav/docker-mailserver/master/setup.sh; chmod a+x ./setup.sh
curl -o docker-compose.yml https://raw.githubusercontent.com/tomav/docker-mailserver/master/docker-compose.yml.dist curl -o docker-compose.yml https://raw.githubusercontent.com/tomav/docker-mailserver/master/docker-compose.yml.dist
@ -89,7 +105,7 @@ curl -o env-mailserver https://raw.githubusercontent.com/tomav/docker-mailserver
**Note:** If you want to use a bare domain (host name equals domain name) see [FAQ](https://github.com/tomav/docker-mailserver/wiki/FAQ-and-Tips#can-i-use-nakedbare-domains-no-host-name). **Note:** If you want to use a bare domain (host name equals domain name) see [FAQ](https://github.com/tomav/docker-mailserver/wiki/FAQ-and-Tips#can-i-use-nakedbare-domains-no-host-name).
### Starting the Container ### Start the Container
``` BASH ``` BASH
docker-compose up -d mail docker-compose up -d mail
@ -794,7 +810,7 @@ you to replace both instead of just the envelope sender.
#### Default Relay Host #### Default Relay Host
#### DEFAULT_RELAY_HOST ##### DEFAULT_RELAY_HOST
- **empty** => don't set default relayhost setting in main.cf - **empty** => don't set default relayhost setting in main.cf
- default host and port to relay all mail through. - default host and port to relay all mail through.
@ -803,22 +819,22 @@ you to replace both instead of just the envelope sender.
#### Multi-domain Relay Hosts #### Multi-domain Relay Hosts
#### RELAY_HOST ##### RELAY_HOST
- **empty** => don't configure relay host - **empty** => don't configure relay host
- default host to relay mail through - default host to relay mail through
#### RELAY_PORT ##### RELAY_PORT
- **empty** => 25 - **empty** => 25
- default port to relay mail through - default port to relay mail through
#### RELAY_USER ##### RELAY_USER
- **empty** => no default - **empty** => no default
- default relay username (if no specific entry exists in postfix-sasl-password.cf) - default relay username (if no specific entry exists in postfix-sasl-password.cf)
#### RELAY_PASSWORD ##### RELAY_PASSWORD
- **empty** => no default - **empty** => no default
- password for default relay user - password for default relay user

View File

@ -169,7 +169,7 @@ function _docker_container()
fi fi
} }
function main() function _main()
{ {
if [[ -n $(command -v docker) ]] if [[ -n $(command -v docker) ]]
then then
@ -180,7 +180,7 @@ function main()
_check_root _check_root
else else
echo "No supported Container Runtime Interface detected." echo "No supported Container Runtime Interface detected."
exit 1 exit 10
fi fi
INFO=$($CRI ps \ INFO=$($CRI ps \
@ -316,5 +316,5 @@ function main()
esac esac
} }
main "$@" _main "$@"
_unset_vars _unset_vars

View File

@ -1,203 +1,237 @@
#!/bin/bash #!/bin/bash
# version 0.1.0
#
# <INSERT TASK HERE>
# shellcheck source=/dev/null
. /usr/local/bin/helper_functions.sh . /usr/local/bin/helper_functions.sh
# create date for log output LOG_DATE=$(date +"%Y-%m-%d %H:%M:%S ")
log_date=$(date +"%Y-%m-%d %H:%M:%S ") echo "$LOG_DATE Start check-for-changes script."
echo "${log_date} Start check-for-changes script."
# change directory # ? Checks ------------------------------------------------
cd /tmp/docker-mailserver
cd /tmp/docker-mailserver || exit 1
# Check postfix-accounts.cf exist else break # Check postfix-accounts.cf exist else break
if [ ! -f postfix-accounts.cf ]; then if [[ ! -f postfix-accounts.cf ]]
echo "${log_date} postfix-accounts.cf is missing! This should not run! Exit!" then
exit echo "$LOG_DATE postfix-accounts.cf is missing! This should not run! Exit!"
exit
fi fi
# Verify checksum file exists; must be prepared by start-mailserver.sh # Verify checksum file exists; must be prepared by start-mailserver.sh
if [ ! -f $CHKSUM_FILE ]; then if [[ ! -f $CHKSUM_FILE ]]
echo "${log_date} ${CHKSUM_FILE} is missing! Start script failed? Exit!" then
exit echo "$LOG_DATE $CHKSUM_FILE is missing! Start script failed? Exit!"
exit
fi fi
# ? Actual script begins ----------------------------------
# Determine postmaster address, duplicated from start-mailserver.sh # Determine postmaster address, duplicated from start-mailserver.sh
# This script previously didn't work when POSTMASTER_ADDRESS was empty # This script previously didn't work when POSTMASTER_ADDRESS was empty
if [[ -n "${OVERRIDE_HOSTNAME}" ]]; then if [[ -n $OVERRIDE_HOSTNAME ]]
DOMAINNAME=$(echo "${OVERRIDE_HOSTNAME}" | sed s/[^.]*.//) then
DOMAINNAME="${OVERRIDE_HOSTNAME#*.}"
else else
DOMAINNAME="$(hostname -d)" DOMAINNAME="$(hostname -d)"
fi fi
PM_ADDRESS="${POSTMASTER_ADDRESS:=postmaster@${DOMAINNAME}}" PM_ADDRESS="${POSTMASTER_ADDRESS:=postmaster@${DOMAINNAME}}"
echo "${log_date} Using postmaster address ${PM_ADDRESS}" echo "$LOG_DATE Using postmaster address $PM_ADDRESS"
# Wait to make sure server is up before we start
sleep 10 sleep 10
# Run forever while true
while true; do do
LOG_DATE=$(date +"%Y-%m-%d %H:%M:%S ")
# recreate logdate # get chksum and check it, no need to lock config yet
log_date=$(date +"%Y-%m-%d %H:%M:%S ") _monitored_files_checksums >"${CHKSUM_FILE}.new"
# Get chksum and check it, no need to lock config yet if ! cmp --silent -- "$CHKSUM_FILE" "$CHKSUM_FILE.new"
monitored_files_checksums >"$CHKSUM_FILE.new" then
echo "${LOG_DATE} Change detected"
changed=$(grep -Fxvf "$CHKSUM_FILE" "$CHKSUM_FILE.new" | sed 's/^[^ ]\+ //')
mv "$CHKSUM_FILE.new" "$CHKSUM_FILE"
if ! cmp --silent -- "$CHKSUM_FILE" "$CHKSUM_FILE.new"; then # Bug alert! This overwrites the alias set by start-mailserver.sh
echo "${log_date} Change detected" # Take care that changes in one script are propagated to the other
changed=$(grep -Fxvf "$CHKSUM_FILE" "$CHKSUM_FILE.new" | sed 's/^[^ ]\+ //')
mv "$CHKSUM_FILE.new" "$CHKSUM_FILE"
# Bug alert! This overwrites the alias set by start-mailserver.sh # ! NEEDS FIX -----------------------------------------
# Take care that changes in one script are propagated to the other # TODO FIX --------------------------------------------
# Also note that changes are performed in place and are not atomic # ! NEEDS EXTENSIONS ----------------------------
# We should fix that and write to temporary files, stop, swap and start # TODO Perform updates below conditionally too --
# Also note that changes are performed in place and are not atomic
# We should fix that and write to temporary files, stop, swap and start
# Lock configuration while working
(
flock -e 200
# Lock configuration while working for file in $changed
# Not fixing indentation yet to reduce diff (fix later in separate commit) do
( case $file in
flock -e 200 /etc/letsencrypt/acme.json)
for certdomain in $SSL_DOMAIN $HOSTNAME $DOMAINNAME
for file in $changed; do do
case $file in if _extract_certs_from_acme "$certdomain"
/etc/letsencrypt/acme.json) then
for certdomain in $SSL_DOMAIN $HOSTNAME $DOMAINNAME; do break
if extractCertsFromAcmeJson "$certdomain"; then fi
break done
fi ;;
* ) notify 'err' 'file not found for certificate in check_for_changes.sh' ;;
esac
done done
;;
#TODO: Perform updates below conditionally as well.
esac
done
#regen postix aliases. # regenerate postix aliases
echo "root: ${PM_ADDRESS}" > /etc/aliases echo "root: ${PM_ADDRESS}" >/etc/aliases
if [ -f /tmp/docker-mailserver/postfix-aliases.cf ]; then if [[ -f /tmp/docker-mailserver/postfix-aliases.cf ]]
cat /tmp/docker-mailserver/postfix-aliases.cf>>/etc/aliases then
fi cat /tmp/docker-mailserver/postfix-aliases.cf >>/etc/aliases
postalias /etc/aliases fi
postalias /etc/aliases
#regen postfix accounts. # regenerate postfix accounts
echo -n > /etc/postfix/vmailbox echo -n >/etc/postfix/vmailbox
echo -n > /etc/dovecot/userdb echo -n >/etc/dovecot/userdb
if [ -f /tmp/docker-mailserver/postfix-accounts.cf -a "$ENABLE_LDAP" != 1 ]; then if [[ -f /tmp/docker-mailserver/postfix-accounts.cf ]] && [[ $ENABLE_LDAP -ne 1 ]]
sed -i 's/\r//g' /tmp/docker-mailserver/postfix-accounts.cf then
echo "# WARNING: this file is auto-generated. Modify config/postfix-accounts.cf to edit user list." > /etc/postfix/vmailbox sed -i 's/\r//g' /tmp/docker-mailserver/postfix-accounts.cf
# Checking that /tmp/docker-mailserver/postfix-accounts.cf ends with a newline echo "# WARNING: this file is auto-generated. Modify config/postfix-accounts.cf to edit user list." >/etc/postfix/vmailbox
sed -i -e '$a\' /tmp/docker-mailserver/postfix-accounts.cf
chown dovecot:dovecot /etc/dovecot/userdb
chmod 640 /etc/dovecot/userdb
sed -i -e '/\!include auth-ldap\.conf\.ext/s/^/#/' /etc/dovecot/conf.d/10-auth.conf
sed -i -e '/\!include auth-passwdfile\.inc/s/^#//' /etc/dovecot/conf.d/10-auth.conf
# rebuild relay host # Checking that /tmp/docker-mailserver/postfix-accounts.cf ends with a newline
if [ ! -z "$RELAY_HOST" ]; then # shellcheck disable=SC1003
# keep old config sed -i -e '$a\' /tmp/docker-mailserver/postfix-accounts.cf
echo -n > /etc/postfix/sasl_passwd chown dovecot:dovecot /etc/dovecot/userdb
if [ ! -z "$SASL_PASSWD" ]; then chmod 640 /etc/dovecot/userdb
echo "$SASL_PASSWD" >> /etc/postfix/sasl_passwd sed -i -e '/\!include auth-ldap\.conf\.ext/s/^/#/' /etc/dovecot/conf.d/10-auth.conf
fi sed -i -e '/\!include auth-passwdfile\.inc/s/^#//' /etc/dovecot/conf.d/10-auth.conf
# add domain-specific auth from config file
if [ -f /tmp/docker-mailserver/postfix-sasl-password.cf ]; then
(grep -v "^\s*$\|^\s*\#" /tmp/docker-mailserver/postfix-sasl-password.cf || true) | while read line; do
if ! echo "$line" | grep -q -e "\s*#"; then
echo "$line" >> /etc/postfix/sasl_passwd
fi
done
fi
# add default relay
if [ ! -z "$RELAY_USER" ] && [ ! -z "$RELAY_PASSWORD" ]; then
echo "[$RELAY_HOST]:$RELAY_PORT $RELAY_USER:$RELAY_PASSWORD" >> /etc/postfix/sasl_passwd
fi
fi
# Creating users # rebuild relay host
# 'pass' is encrypted if [[ -n $RELAY_HOST ]]
# comments and empty lines are ignored then
grep -v "^\s*$\|^\s*\#" /tmp/docker-mailserver/postfix-accounts.cf | while IFS=$'|' read login pass # keep old config
do echo -n >/etc/postfix/sasl_passwd
# Setting variables for better readability if [[ -n $SASL_PASSWD ]]
user=$(echo ${login} | cut -d @ -f1) then
domain=$(echo ${login} | cut -d @ -f2) echo "$SASL_PASSWD" >>/etc/postfix/sasl_passwd
fi
user_attributes="" # add domain-specific auth from config file
# test if user has a defined quota if [[ -f /tmp/docker-mailserver/postfix-sasl-password.cf ]]
if [ -f /tmp/docker-mailserver/dovecot-quotas.cf ]; then then
user_quota=($(grep "${user}@${domain}:" -i /tmp/docker-mailserver/dovecot-quotas.cf | tr ':' '\n')) (grep -v "^\s*$\|^\s*\#" /tmp/docker-mailserver/postfix-sasl-password.cf || true) | while read -r line
do
if ! echo "$line" | grep -q -e "\s*#"
then
echo "$line" >>/etc/postfix/sasl_passwd
fi
done
fi
if [ ${#user_quota[@]} -eq 2 ]; then # add default relay
user_attributes="${user_attributes}userdb_quota_rule=*:bytes=${user_quota[1]}" if [[ -n "$RELAY_USER" ]] && [[ -n "$RELAY_PASSWORD" ]]
fi then
fi echo "[$RELAY_HOST]:$RELAY_PORT $RELAY_USER:$RELAY_PASSWORD" >>/etc/postfix/sasl_passwd
fi
fi
# Let's go! # creating users ; 'pass' is encrypted
echo "${login} ${domain}/${user}/" >> /etc/postfix/vmailbox # comments and empty lines are ignored
# User database for dovecot has the following format: grep -v "^\s*$\|^\s*\#" /tmp/docker-mailserver/postfix-accounts.cf | while IFS=$'|' read -r login pass
# user:password:uid:gid:(gecos):home:(shell):extra_fields do
# Example : user=$(echo "$login" | cut -d @ -f1)
# ${login}:${pass}:5000:5000::/var/mail/${domain}/${user}::userdb_mail=maildir:/var/mail/${domain}/${user} domain=$(echo "$login" | cut -d @ -f2)
echo "${login}:${pass}:5000:5000::/var/mail/${domain}/${user}::${user_attributes}" >> /etc/dovecot/userdb
mkdir -p /var/mail/${domain}/${user}
# Copy user provided sieve file, if present user_attributes=""
test -e /tmp/docker-mailserver/${login}.dovecot.sieve && cp /tmp/docker-mailserver/${login}.dovecot.sieve /var/mail/${domain}/${user}/.dovecot.sieve # test if user has a defined quota
echo ${domain} >> /tmp/vhost.tmp if [[ -f /tmp/docker-mailserver/dovecot-quotas.cf ]]
done then
fi IFS=':'
if [ ! -z "$RELAY_HOST" ]; then read -r -a user_quota < <(grep "${user}@${domain}:" -i /tmp/docker-mailserver/dovecot-quotas.cf)
populate_relayhost_map unset IFS
fi
if [ -f /etc/postfix/sasl_passwd ]; then
chown root:root /etc/postfix/sasl_passwd
chmod 0600 /etc/postfix/sasl_passwd
fi
if [ -f postfix-virtual.cf ]; then
# regen postfix aliases
echo -n > /etc/postfix/virtual
echo -n > /etc/postfix/regexp
if [ -f /tmp/docker-mailserver/postfix-virtual.cf ]; then
# Copying virtual file
cp -f /tmp/docker-mailserver/postfix-virtual.cf /etc/postfix/virtual
(grep -v "^\s*$\|^\s*\#" /tmp/docker-mailserver/postfix-virtual.cf || true) | while read from to
do
# Setting variables for better readability
uname=$(echo ${from} | cut -d @ -f1)
domain=$(echo ${from} | cut -d @ -f2)
# if they are equal it means the line looks like: "user1 other@domain.tld"
test "$uname" != "$domain" && echo ${domain} >> /tmp/vhost.tmp
done
fi
if [ -f /tmp/docker-mailserver/postfix-regexp.cf ]; then
# Copying regexp alias file
cp -f /tmp/docker-mailserver/postfix-regexp.cf /etc/postfix/regexp
sed -i -e '/^virtual_alias_maps/{
s/ regexp:.*//
s/$/ regexp:\/etc\/postfix\/regexp/
}' /etc/postfix/main.cf
fi
fi
# Set vhost
if [ -f /tmp/vhost.tmp ]; then
cat /tmp/vhost.tmp | sort | uniq > /etc/postfix/vhost && rm /tmp/vhost.tmp
fi
# Set right new if needed [[ ${#user_quota[@]} -eq 2 ]] && user_attributes="${user_attributes}userdb_quota_rule=*:bytes=${user_quota[1]}"
if [ `find /var/mail -maxdepth 3 -a \( \! -user 5000 -o \! -group 5000 \) | grep -c .` != 0 ]; then fi
chown -R 5000:5000 /var/mail
fi
# Restart of the postfix echo "$login ${domain}/${user}/" >>/etc/postfix/vmailbox
supervisorctl restart postfix
# Prevent restart of dovecot when smtp_only=1 # user database for dovecot has the following format:
if [ ! $SMTP_ONLY = 1 ]; then # user:password:uid:gid:(gecos):home:(shell):extra_fields
supervisorctl restart dovecot # example :
fi # ${login}:${pass}:5000:5000::/var/mail/${domain}/${user}::userdb_mail=maildir:/var/mail/${domain}/${user}
echo "${login}:${pass}:5000:5000::/var/mail/${domain}/${user}::${user_attributes}" >>/etc/dovecot/userdb
mkdir -p "/var/mail/${domain}/${user}"
) 200<postfix-accounts.cf # end lock if [[ -e /tmp/docker-mailserver/${login}.dovecot.sieve ]]
fi then
cp "/tmp/docker-mailserver/${login}.dovecot.sieve" "/var/mail/${domain}/${user}/.dovecot.sieve"
fi
sleep 1 echo "$domain" >>/tmp/vhost.tmp
done
fi
[[ -n $RELAY_HOST ]] && _populate_relayhost_map
if [[ -f /etc/postfix/sasl_passwd ]]
then
chown root:root /etc/postfix/sasl_passwd
chmod 0600 /etc/postfix/sasl_passwd
fi
if [[ -f postfix-virtual.cf ]]
then
# regenerate postfix aliases
echo -n >/etc/postfix/virtual
echo -n >/etc/postfix/regexp
if [[ -f /tmp/docker-mailserver/postfix-virtual.cf ]]
then
cp -f /tmp/docker-mailserver/postfix-virtual.cf /etc/postfix/virtual
# the `to` seems to be important; don't delete it
# shellcheck disable=SC2034
(grep -v "^\s*$\|^\s*\#" /tmp/docker-mailserver/postfix-virtual.cf || true) | while read -r from to
do
uname=$(echo "$from" | cut -d @ -f1)
domain=$(echo "$from" | cut -d @ -f2)
# if they are equal it means the line looks like: "user1 other@domain.tld"
[ "$uname" != "$domain" ] && echo "$domain" >>/tmp/vhost.tmp
done
fi
if [[ -f /tmp/docker-mailserver/postfix-regexp.cf ]]
then
cp -f /tmp/docker-mailserver/postfix-regexp.cf /etc/postfix/regexp
sed -i -e '/^virtual_alias_maps/{
s/ regexp:.*//
s/$/ regexp:\/etc\/postfix\/regexp/
}' /etc/postfix/main.cf
fi
fi
if [[ -f /tmp/vhost.tmp ]]
then
# shellcheck disable=SC2002
cat /tmp/vhost.tmp | sort | uniq >/etc/postfix/vhost && rm /tmp/vhost.tmp
fi
if [[ $(find /var/mail -maxdepth 3 -a \( \! -user 5000 -o \! -group 5000 \) | grep -c .) -ne 0 ]]
then
chown -R 5000:5000 /var/mail
fi
supervisorctl restart postfix
# prevent restart of dovecot when smtp_only=1
[[ $SMTP_ONLY -ne 1 ]] && supervisorctl restart dovecot
) 200<postfix-accounts.cf # end lock
fi
sleep 1
done done

View File

@ -1,5 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# fail2ban-wrapper.sh, version 0.0.1
# version 0.1.0
# #
# You cannot start fail2ban in some foreground mode and # You cannot start fail2ban in some foreground mode and
# it's more or less important that docker doesn't kill # it's more or less important that docker doesn't kill
@ -21,14 +22,12 @@ trap "/usr/bin/fail2ban-client stop" SIGINT
trap "/usr/bin/fail2ban-client stop" SIGTERM trap "/usr/bin/fail2ban-client stop" SIGTERM
trap "/usr/bin/fail2ban-client reload" SIGHUP trap "/usr/bin/fail2ban-client reload" SIGHUP
# start fail2ban
/usr/bin/fail2ban-client start /usr/bin/fail2ban-client start
# lets give fail2ban some time to start
sleep 5 sleep 5
# wait until fail2ban is dead (triggered by trap) # wait until fail2ban is dead (triggered by trap)
while kill -0 "`cat /var/run/fail2ban/fail2ban.pid`"; do while kill -0 "$(cat /var/run/fail2ban/fail2ban.pid)"
do
sleep 5 sleep 5
done done

View File

@ -1,45 +1,64 @@
#!/bin/bash #!/bin/bash
# expects mask prefix length and the digit # version 0.1.1
function _mask_ip_digit() { #
if [[ $1 -ge 8 ]]; then # Provides varous helpers.
# ? IP and CIDR -------------------------------------------
function _mask_ip_digit()
{
if [[ ${1} -ge 8 ]]
then
MASK=255 MASK=255
elif [[ ${1} -le 0 ]]
then
MASK=0
else else
if [[ $1 -le 0 ]]; then VALUES=(0 128 192 224 240 248 252 254 255)
MASK=0 MASK=${VALUES[$1]}
else
VALUES=('0' '128' '192' '224' '240' '248' '252' '254' '255')
MASK=${VALUES[$1]}
fi
fi fi
echo $(($2 & $MASK))
local DVAL=${2}
((DVAL&=MASK))
echo "$DVAL"
} }
# transforms a specific ip with CIDR suffix like 1.2.3.4/16 # Transforms a specific IP with CIDR suffix
# to subnet with cidr suffix like 1.2.0.0/16 # like 1.2.3.4/16 to subnet with cidr suffix
function _sanitize_ipv4_to_subnet_cidr() { # like 1.2.0.0/16.
IP=${1%%/*} # Assumes correct IP and subnet are provided.
PREFIX_LENGTH=${1#*/} function _sanitize_ipv4_to_subnet_cidr()
{
local DIGIT_PREFIX_LENGTH="${1#*/}"
# split IP by . into digits declare -a DIGITS
DIGITS=(${IP//./ }) IFS='.' ; read -r -a DIGITS < <(echo "${1%%/*}")
unset IFS
# mask digits according to prefix length declare -a MASKED_DIGITS
MASKED_DIGITS=()
DIGIT_PREFIX_LENGTH="$PREFIX_LENGTH" for ((i = 0 ; i < 4 ; i++))
for DIGIT in "${DIGITS[@]}"; do do
MASKED_DIGITS+=($(_mask_ip_digit $DIGIT_PREFIX_LENGTH $DIGIT)) MASKED_DIGITS[i]=$(_mask_ip_digit "$DIGIT_PREFIX_LENGTH" "${DIGITS[i]}")
DIGIT_PREFIX_LENGTH=$(($DIGIT_PREFIX_LENGTH - 8)) DIGIT_PREFIX_LENGTH=$((DIGIT_PREFIX_LENGTH - 8))
done done
# output masked ip plus prefix length echo "${MASKED_DIGITS[0]}.${MASKED_DIGITS[1]}.${MASKED_DIGITS[2]}.${MASKED_DIGITS[3]}/${1#*/}"
echo ${MASKED_DIGITS[0]}.${MASKED_DIGITS[1]}.${MASKED_DIGITS[2]}.${MASKED_DIGITS[3]}/$PREFIX_LENGTH
} }
export -f _sanitize_ipv4_to_subnet_cidr
# extracts certificates from acme.json and returns 0 if found
function extractCertsFromAcmeJson() {
WHAT=$1
# ? ACME certs --------------------------------------------
function _extract_certs_from_acme()
{
local KEY
# shellcheck disable=SC2002
KEY=$(cat /etc/letsencrypt/acme.json | python -c " KEY=$(cat /etc/letsencrypt/acme.json | python -c "
import sys,json import sys,json
acme = json.load(sys.stdin) acme = json.load(sys.stdin)
@ -47,10 +66,13 @@ for key, value in acme.items():
certs = value['Certificates'] certs = value['Certificates']
for cert in certs: for cert in certs:
if 'domain' in cert and 'key' in cert: if 'domain' in cert and 'key' in cert:
if 'main' in cert['domain'] and cert['domain']['main'] == '$WHAT' or 'sans' in cert['domain'] and '$WHAT' in cert['domain']['sans']: if 'main' in cert['domain'] and cert['domain']['main'] == '$1' or 'sans' in cert['domain'] and '$1' in cert['domain']['sans']:
print cert['key'] print cert['key']
break break
") ")
local CERT
# shellcheck disable=SC2002
CERT=$(cat /etc/letsencrypt/acme.json | python -c " CERT=$(cat /etc/letsencrypt/acme.json | python -c "
import sys,json import sys,json
acme = json.load(sys.stdin) acme = json.load(sys.stdin)
@ -58,26 +80,35 @@ for key, value in acme.items():
certs = value['Certificates'] certs = value['Certificates']
for cert in certs: for cert in certs:
if 'domain' in cert and 'certificate' in cert: if 'domain' in cert and 'certificate' in cert:
if 'main' in cert['domain'] and cert['domain']['main'] == '$WHAT' or 'sans' in cert['domain'] and '$WHAT' in cert['domain']['sans']: if 'main' in cert['domain'] and cert['domain']['main'] == '$1' or 'sans' in cert['domain'] and '$1' in cert['domain']['sans']:
print cert['certificate'] print cert['certificate']
break break
") ")
if [[ -n "${KEY}${CERT}" ]]; then if [[ -n "${KEY}${CERT}" ]]
mkdir -p /etc/letsencrypt/live/"$HOSTNAME"/ then
echo $KEY | base64 -d >/etc/letsencrypt/live/"$HOSTNAME"/key.pem || exit 1 mkdir -p "/etc/letsencrypt/live/${HOSTNAME}/"
echo $CERT | base64 -d >/etc/letsencrypt/live/"$HOSTNAME"/fullchain.pem || exit 1
echo "Cert found in /etc/letsencrypt/acme.json for $WHAT" echo "$KEY" | base64 -d >/etc/letsencrypt/live/"$HOSTNAME"/key.pem || exit 1
echo "$CERT" | base64 -d >/etc/letsencrypt/live/"$HOSTNAME"/fullchain.pem || exit 1
echo "Cert found in /etc/letsencrypt/acme.json for $1"
return 0 return 0
else else
return 1 return 1
fi fi
} }
export -f _extract_certs_from_acme
# ? Notification ------------------------------------------
declare -A DEFAULT_VARS declare -A DEFAULT_VARS
DEFAULT_VARS["DMS_DEBUG"]="${DMS_DEBUG:="0"}" DEFAULT_VARS["DMS_DEBUG"]="${DMS_DEBUG:="0"}"
function notify () { function notify()
{
c_red="\e[0;31m" c_red="\e[0;31m"
c_green="\e[0;32m" c_green="\e[0;32m"
c_brown="\e[0;33m" c_brown="\e[0;33m"
@ -91,95 +122,95 @@ function notify () {
msg="" msg=""
case "${notification_type}" in case "${notification_type}" in
'taskgrp') 'taskgrp' ) msg="${c_bold}${notification_msg}${c_reset}" ;;
msg="${c_bold}${notification_msg}${c_reset}" 'task' )
;; if [[ ${DEFAULT_VARS["DMS_DEBUG"]} == 1 ]]
'task') then
if [[ ${DEFAULT_VARS["DMS_DEBUG"]} == 1 ]]; then
msg=" ${notification_msg}${c_reset}" msg=" ${notification_msg}${c_reset}"
fi fi
;; ;;
'inf') 'inf' )
if [[ ${DEFAULT_VARS["DMS_DEBUG"]} == 1 ]]; then if [[ ${DEFAULT_VARS["DMS_DEBUG"]} == 1 ]]
then
msg="${c_green} * ${notification_msg}${c_reset}" msg="${c_green} * ${notification_msg}${c_reset}"
fi fi
;; ;;
'started') 'started' ) msg="${c_green} ${notification_msg}${c_reset}" ;;
msg="${c_green} ${notification_msg}${c_reset}" 'warn' ) msg="${c_brown} Warning ${notification_msg}${c_reset}" ;;
;; 'err' ) msg="${c_blue} Error ${notification_msg}${c_reset}" ;;
'warn') 'fatal' ) msg="${c_red} Fatal Error: ${notification_msg}${c_reset}" ;;
msg="${c_brown} * ${notification_msg}${c_reset}" * ) msg="" ;;
;;
'err')
msg="${c_red} * ${notification_msg}${c_reset}"
;;
'fatal')
msg="${c_red}Error: ${notification_msg}${c_reset}"
;;
*)
msg=""
;;
esac esac
case "${notification_format}" in case "${notification_format}" in
'n') 'n' ) options="-ne" ;;
options="-ne" * ) options="-e" ;;
;;
*)
options="-e"
;;
esac esac
[[ ! -z "${msg}" ]] && echo $options "${msg}" [[ -n "${msg}" ]] && echo $options "${msg}"
} }
export -f notify
# ? Relay Host Map ----------------------------------------
# setup /etc/postfix/relayhost_map # setup /etc/postfix/relayhost_map
# -- # --
# @domain1.com [smtp.mailgun.org]:587 # @domain1.com [smtp.mailgun.org]:587
# @domain2.com [smtp.mailgun.org]:587 # @domain2.com [smtp.mailgun.org]:587
# @domain3.com [smtp.mailgun.org]:587 # @domain3.com [smtp.mailgun.org]:587
function populate_relayhost_map() { function _populate_relayhost_map()
{
echo -n > /etc/postfix/relayhost_map echo -n > /etc/postfix/relayhost_map
chown root:root /etc/postfix/relayhost_map chown root:root /etc/postfix/relayhost_map
chmod 0600 /etc/postfix/relayhost_map chmod 0600 /etc/postfix/relayhost_map
if [ -f /tmp/docker-mailserver/postfix-relaymap.cf ]; then if [[ -f /tmp/docker-mailserver/postfix-relaymap.cf ]]
then
notify 'inf' "Adding relay mappings from postfix-relaymap.cf" notify 'inf' "Adding relay mappings from postfix-relaymap.cf"
# Keep lines which are not a comment *and* have a destination. # keep lines which are not a comment *and* have a destination.
sed -n '/^\s*[^#[:space:]]\S*\s\+\S/p' /tmp/docker-mailserver/postfix-relaymap.cf \ sed -n '/^\s*[^#[:space:]]\S*\s\+\S/p' /tmp/docker-mailserver/postfix-relaymap.cf >> /etc/postfix/relayhost_map
>> /etc/postfix/relayhost_map
fi fi
{ {
# Note: Won't detect domains when lhs has spaces (but who does that?!). # note: won't detect domains when lhs has spaces (but who does that?!)
sed -n '/^\s*[^#[:space:]]/ s/^[^@|]*@\([^|]\+\)|.*$/\1/p' /tmp/docker-mailserver/postfix-accounts.cf sed -n '/^\s*[^#[:space:]]/ s/^[^@|]*@\([^|]\+\)|.*$/\1/p' /tmp/docker-mailserver/postfix-accounts.cf
[ -f /tmp/docker-mailserver/postfix-virtual.cf ] &&
sed -n '/^\s*[^#[:space:]]/ s/^\s*[^@[:space:]]*@\(\S\+\)\s.*/\1/p' /tmp/docker-mailserver/postfix-virtual.cf [ -f /tmp/docker-mailserver/postfix-virtual.cf ] && sed -n '/^\s*[^#[:space:]]/ s/^\s*[^@[:space:]]*@\(\S\+\)\s.*/\1/p' /tmp/docker-mailserver/postfix-virtual.cf
} | while read domain; do } | while read -r domain
if ! grep -q -e "^@${domain}\b" /etc/postfix/relayhost_map && do
! grep -qs -e "^\s*@${domain}\s*$" /tmp/docker-mailserver/postfix-relaymap.cf; then # domain not already present *and* not ignored
# Domain not already present *and* not ignored. if ! grep -q -e "^@${domain}\b" /etc/postfix/relayhost_map && ! grep -qs -e "^\s*@${domain}\s*$" /tmp/docker-mailserver/postfix-relaymap.cf
then
notify 'inf' "Adding relay mapping for ${domain}" notify 'inf' "Adding relay mapping for ${domain}"
echo "@${domain} [$RELAY_HOST]:$RELAY_PORT" >> /etc/postfix/relayhost_map echo "@${domain} [$RELAY_HOST]:$RELAY_PORT" >> /etc/postfix/relayhost_map
fi fi
done done
} }
export -f _populate_relayhost_map
# File storing the checksums of the monitored files.
# ? File checksums ----------------------------------------
# file storing the checksums of the monitored files.
# shellcheck disable=SC2034
CHKSUM_FILE=/tmp/docker-mailserver-config-chksum CHKSUM_FILE=/tmp/docker-mailserver-config-chksum
# Compute checksums of monitored files. # Compute checksums of monitored files.
function monitored_files_checksums() { function _monitored_files_checksums()
{
( (
cd /tmp/docker-mailserver cd /tmp/docker-mailserver || exit 1
# (2>/dev/null to ignore warnings about files that don't exist)
exec sha512sum 2>/dev/null -- \ exec sha512sum 2>/dev/null -- \
postfix-accounts.cf \ postfix-accounts.cf \
postfix-virtual.cf \ postfix-virtual.cf \
postfix-aliases.cf \ postfix-aliases.cf \
dovecot-quotas.cf \ dovecot-quotas.cf \
/etc/letsencrypt/acme.json \ /etc/letsencrypt/acme.json \
"/etc/letsencrypt/live/$HOSTNAME/key.pem" \ "/etc/letsencrypt/live/$HOSTNAME/key.pem" \
"/etc/letsencrypt/live/$HOSTNAME/fullchain.pem" "/etc/letsencrypt/live/$HOSTNAME/fullchain.pem"
) )
return 0
} }
export -f _monitored_files_checksums

View File

@ -1,5 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# postfix-wrapper.sh, version 0.1.0
# version 0.1.0
# #
# You cannot start postfix in some foreground mode and # You cannot start postfix in some foreground mode and
# it's more or less important that docker doesn't kill # it's more or less important that docker doesn't kill
@ -21,14 +22,11 @@ trap "service postfix stop" SIGINT
trap "service postfix stop" SIGTERM trap "service postfix stop" SIGTERM
trap "service postfix reload" SIGHUP trap "service postfix reload" SIGHUP
# start postfix
service postfix start service postfix start
# lets give postfix some time to start
sleep 5 sleep 5
# wait until postfix is dead (triggered by trap) # wait until postfix is dead (triggered by trap)
while kill -0 "`cat /var/spool/postfix/pid/master.pid`"; do while kill -0 "$(cat /var/spool/postfix/pid/master.pid)"
do
sleep 5 sleep 5
done done

View File

@ -1,43 +1,52 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# postsrsd-wrapper.sh, version 0.2.2
if [ -n "$SRS_DOMAINNAME" ]; then # version 0.1.0
domain_name="$SRS_DOMAINNAME"
elif [ -n "$OVERRIDE_HOSTNAME" ]; then
domain_name="${OVERRIDE_HOSTNAME#*.}"
elif [ -n "$DOMAINNAME" ]; then
domain_name="$DOMAINNAME"
else
domain_name=$(hostname -d)
fi
sed -i -e "s/localdomain/${domain_name}/g" /etc/default/postsrsd function generate_secret()
{
postsrsd_secret_file='/etc/postsrsd.secret' ( umask 0077 ; dd if=/dev/urandom bs=24 count=1 2>/dev/null | base64 -w0 > "$1" )
postsrsd_state_dir='/var/mail-state/etc-postsrsd'
postsrsd_state_secret_file="${postsrsd_state_dir}/postsrsd.secret"
generate_secret() {
( umask 0077
dd if=/dev/urandom bs=24 count=1 2>/dev/null | base64 -w0 > "$1" )
} }
if [ -n "$SRS_SECRET" ]; then if [[ -n $SRS_DOMAINNAME ]]
( umask 0077 then
echo "$SRS_SECRET" | tr ',' '\n' > "$postsrsd_secret_file" ) NEW_DOMAIN_NAME="$SRS_DOMAINNAME"
elif [[ -n $OVERRIDE_HOSTNAME ]]
then
NEW_DOMAIN_NAME="${OVERRIDE_HOSTNAME#*.}"
elif [[ -n $DOMAINNAME ]]
then
NEW_DOMAIN_NAME="$DOMAINNAME"
else else
if [ "$ONE_DIR" = 1 ]; then NEW_DOMAIN_NAME=$(hostname -d)
if [ ! -f "$postsrsd_state_secret_file" ]; then fi
install -d -m 0775 "$postsrsd_state_dir"
generate_secret "$postsrsd_state_secret_file" sed -i -e "s/localdomain/${NEW_DOMAIN_NAME}/g" /etc/default/postsrsd
POSTSRSD_SECRET_FILE='/etc/postsrsd.secret'
POSTSRSD_STATE_DIR='/var/mail-state/etc-postsrsd'
POSTSRSD_STATE_SECRET_FILE="${POSTSRSD_STATE_DIR}/postsrsd.secret"
if [[ -n $SRS_SECRET ]]
then
( umask 0077 ; echo "$SRS_SECRET" | tr ',' '\n' > "$POSTSRSD_SECRET_FILE" )
else
if [[ $ONE_DIR -eq 1 ]]
then
if [[ ! -f $POSTSRSD_STATE_SECRET_FILE ]]
then
install -d -m 0775 "$POSTSRSD_STATE_DIR"
generate_secret "$POSTSRSD_STATE_SECRET_FILE"
fi fi
install -m 0400 "$postsrsd_state_secret_file" "$postsrsd_secret_file"
elif [ ! -f "$postsrsd_secret_file" ]; then install -m 0400 "$POSTSRSD_STATE_SECRET_FILE" "$POSTSRSD_SECRET_FILE"
generate_secret "$postsrsd_secret_file" elif [[ ! -f $POSTSRSD_SECRET_FILE ]]
then
generate_secret "$POSTSRSD_SECRET_FILE"
fi fi
fi fi
if [ -n "$SRS_EXCLUDE_DOMAINS" ]; then if [[ -n $SRS_EXCLUDE_DOMAINS ]]
then
sed -i -e "s/^#\?SRS_EXCLUDE_DOMAINS=.*$/SRS_EXCLUDE_DOMAINS=$SRS_EXCLUDE_DOMAINS/g" /etc/default/postsrsd sed -i -e "s/^#\?SRS_EXCLUDE_DOMAINS=.*$/SRS_EXCLUDE_DOMAINS=$SRS_EXCLUDE_DOMAINS/g" /etc/default/postsrsd
fi fi

View File

@ -6,6 +6,7 @@
[supervisord] [supervisord]
nodaemon=true nodaemon=true
strip_ansi=true
[program:mailserver] [program:mailserver]
startsecs=0 startsecs=0