Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Example configuration for AIO config: mailcow +caddy +borg +mailman #6288

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions data/Dockerfiles/caddy/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# This dockerfile builds a custom caddy process, configured with optional
# modules for caddy-uwsgi-transport (for mailman) and caddy-events-exec for
# reloading dovecot/postfix when new certificates are issued
# to build, do 'docker compose build' before running 'docker compose up -d'
ARG CADDY_VERSION=2.9.1
FROM caddy:${CADDY_VERSION}-builder AS builder

RUN xcaddy build \
--with github.com/BadAimWeeb/caddy-uwsgi-transport \
--with github.com/mholt/caddy-events-exec

FROM caddy:${CADDY_VERSION}-alpine
RUN apk add jq
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
35 changes: 35 additions & 0 deletions data/conf/borgmatic/etc/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
source_directories:
- /mnt/source/vmail
- /mnt/source/crypt
- /mnt/source/redis
- /mnt/source/rspamd
- /mnt/source/postfix
- /mnt/source/docker-mailman
repositories:
- path: ssh://root@example.org/./mailcow
label: backup1
exclude_patterns:
- '/mnt/source/postfix/public/'
- '/mnt/source/postfix/private/'
- '/mnt/source/rspamd/rspamd.sock'

keep_hourly: 24
keep_daily: 7
keep_weekly: 4
keep_monthly: 6

mariadb_databases:
- name: ${MAILCOW_DBNAME}
hostname: ${MAILCOW_DBHOST}
username: ${MAILCOW_DBUSER}
password: ${MAILCOW_DBPASS}
options: "--default-character-set=utf8mb4 --skip-ssl"
list_options: "--skip-ssl"
restore_options: "--skip-ssl"

postgresql_databases:
- name: ${MAILMAN_DB_NAME}
hostname: ${MAILMAN_DB_HOST}
username: ${MAILMAN_DB_USER}
password: ${MAILMAN_DB_PASS}

1 change: 1 addition & 0 deletions data/conf/borgmatic/etc/crontab.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
14 * * * * PATH=$PATH:/usr/local/bin /usr/local/bin/borgmatic --stats -v 0 2>&1
86 changes: 86 additions & 0 deletions data/conf/caddy/bin/reload.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/bin/sh

copy_cert() {
mkdir -p "/shared/ssl/${MAILCOW_HOSTNAME}"
cp "$private_key_path" /shared/ssl/key.pem
cp "$certificate_path" /shared/ssl/cert.pem
}

init() {
COMPOSE_PROJECT_NAME_LOWER=$(echo "$COMPOSE_PROJECT_NAME" | tr '[:upper:]' '[:lower:]')
CONTAINERS=$(wget --no-check-certificate -O - "https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/json" 2>/dev/null)
NGINX=$(echo "${CONTAINERS}" | jq -r '.[] | {name: .Config.Labels["com.docker.compose.service"], project: .Config.Labels["com.docker.compose.project"], id: .Id}' | jq -rc 'select( .name | tostring | contains("nginx-mailcow")) | select( .project | tostring | contains("'"${COMPOSE_PROJECT_NAME_LOWER}"'")) | .id' | tr "\n" " ")
DOVECOT=$(echo "${CONTAINERS}" | jq -r '.[] | {name: .Config.Labels["com.docker.compose.service"], project: .Config.Labels["com.docker.compose.project"], id: .Id}' | jq -rc 'select( .name | tostring | contains("dovecot-mailcow")) | select( .project | tostring | contains("'"${COMPOSE_PROJECT_NAME_LOWER}"'")) | .id' | tr "\n" " ")
POSTFIX=$(echo "${CONTAINERS}" | jq -r '.[] | {name: .Config.Labels["com.docker.compose.service"], project: .Config.Labels["com.docker.compose.project"], id: .Id}' | jq -rc 'select( .name | tostring | contains("postfix-mailcow")) | select( .project | tostring | contains("'"${COMPOSE_PROJECT_NAME_LOWER}"'")) | .id' | tr "\n" " ")

# Ensure trimmed content when calling $NGINX etc.
NGINX=$(echo "$NGINX" | sed 's/[[:space:]]*$//')
DOVECOT=$(echo "$DOVECOT" | sed 's/[[:space:]]*$//')
POSTFIX=$(echo "$POSTFIX" | sed 's/[[:space:]]*$//')
}

reload_nginx() {
log "Reloading Nginx..."
NGINX_RELOAD_RET=$(wget --no-check-certificate -O - https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${NGINX}/exec --post-data='{"cmd":"reload", "task":"nginx"}' --header='Content-Type:application/json' | jq -r .type)
[ "${NGINX_RELOAD_RET}" != "success" ] && {
log "Could not reload Nginx, restarting container..."
restart_container "${NGINX}"
}
}

reload_dovecot() {
log "Reloading Dovecot..."
DOVECOT_RELOAD_RET=$(wget --no-check-certificate -O - "https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${DOVECOT}/exec" --post-data='{"cmd":"reload", "task":"dovecot"}' --header='Content-Type:application/json' | jq -r .type)
[ "${DOVECOT_RELOAD_RET}" != "success" ] && {
log "Could not reload Dovecot, restarting container..."
restart_container "${DOVECOT}"
}
}

reload_postfix() {
log "Reloading Postfix..."
POSTFIX_RELOAD_RET=$(wget --no-check-certificate -O - "https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${POSTFIX}/exec" --post-data='{"cmd":"reload", "task":"postfix"}' --header='Content-Type:application/json' | jq -r .type)
[ "${POSTFIX_RELOAD_RET}" != "success" ] && {
log "Could not reload Postfix, restarting container..."
restart_container" ${POSTFIX}"
}
}

restart_container() {
for container in $*; do
log "Restarting ${container}..."
C_REST_OUT=$(wget --no-check-certificate -O - "https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${container}/restart" --post-data='' | jq -r '.msg')
log "${C_REST_OUT}"
done
}

restart() {
#reload_nginx - no need, caddy is reverse proxy, not using nginx ssl
#reload_dovecot
log "Restarting postfix"
restart_container "${DOVECOT}"
#reload_postfix
log "Restarting postfix"
restart_container "${POSTFIX}"
}

log() {
timestamp=$(date +"%Y/%m/%d %H:%M:%S.%3N")
echo "$timestamp $@" | tee -a /var/log/caddy/cert-reload.log
}

identifier=$1
certificate_path=$2
private_key_path=$3

if [ "$identifier" = "$MAILCOW_HOSTNAME" ]; then
log "New certificate issued for $MAILCOW_HOSTNAME"
log "identifier = $identifier"
log "certificate_path = $certificate_path"
log "private_key_path = $private_key_path"
init
copy_cert
restart & # run in background, caddy kills tasks > 30s by default
else
log "Ignoring cert with identifer $identifier"
fi
74 changes: 74 additions & 0 deletions data/conf/caddy/etc/Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{
acme_ca https://acme-v02.api.letsencrypt.org/directory
# acme_ca https://acme-staging-v02.api.letsencrypt.org/directory # for staging

default_bind {$CADDY_BIND}
http_port {$CADDY_HTTP_PORT}
https_port {$CADDY_HTTPS_PORT}

# debug

events {
# this will automatically reload docker containers on cert issuance
on cert_obtained exec /bin/reload.sh {event.data.identifier} {event.data.certificate_path} {event.data.private_key_path}
}

log {
format console
output file /var/log/caddy/system.log {
roll_disabled
roll_size 512M
roll_uncompressed
roll_local_time
roll_keep 3
roll_keep_for 48h
}
}
}

{$MAILCOW_HOSTNAME} autodiscover.{$MAILCOW_HOSTNAME} autoconfig.{$MAILCOW_HOSTNAME} {
log {
format console
output file /var/log/caddy/{$MAILCOW_HOSTNAME}.log {
roll_disabled
roll_size 512M
roll_uncompressed
roll_local_time
roll_keep 3
roll_keep_for 48h
}
}

reverse_proxy nginx-mailcow:{$HTTPS_PORT} {
transport http {
tls
tls_insecure_skip_verify
}
}
}

{$MAILMAN_DOMAIN} {
log {
format console
output file /var/log/caddy/{$MAILMAN_DOMAIN}.log {
roll_disabled
roll_size 512M
roll_uncompressed
roll_local_time
roll_keep 3
roll_keep_for 48h
}
}
handle /static/* {
root /opt/mailman-web-data
file_server {
browse
}
}

handle {
reverse_proxy mailman-web:8080 {
transport uwsgi
}
}
}
3 changes: 3 additions & 0 deletions data/conf/mailman/core/mailman-extra.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[mailman]
default_language: de
site_owner: mailman@example.org
10 changes: 10 additions & 0 deletions data/conf/mailman/web/settings_local.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# locale
LANGUAGE_CODE = 'de-de'

# disable social authentication
MAILMAN_WEB_SOCIAL_AUTH = []

# change it
DEFAULT_FROM_EMAIL = 'mailman@example.org'

DEBUG = False
141 changes: 141 additions & 0 deletions docker-compose.caddy-mailman-borg-example.override.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
services:
postfix-mailcow:
volumes:
- ./data/conf/mailman:/opt/mailman:Z
mailman-core:
# image: maxking/mailman-core:0.4 # Use a specific version tag (tag latest is not published)
image: ghcr.io/midzelis/mailman-core:rolling # custom build of dockerfile for aarch64, see https://github.com/maxking/docker-mailman/pull/741
hostname: mailman-core
restart: always
volumes:
- ./data/conf/mailman/core:/opt/mailman/
stop_grace_period: 30s
depends_on:
mailman-database:
condition: service_healthy
environment:
- DATABASE_URL=postgresql://${MAILMAN_DB_USER}:${MAILMAN_DB_PASS}@mailman-database/${MAILMAN_DB_NAME}
- DATABASE_TYPE=postgres
- DATABASE_CLASS=mailman.database.postgresql.PostgreSQLDatabase
- HYPERKITTY_API_KEY=${HYPERKITTY_API_KEY}
- MTA=postfix
networks:
mailcow-network:
aliases:
- mailman-core

mailman-web:
# image: maxking/mailman-core:0.4 # Use a specific version tag (tag latest is not published)
image: ghcr.io/midzelis/mailman-web:rolling # custom build of dockerfile for aarch64, see https://github.com/maxking/docker-mailman/pull/741
hostname: mailman-web
restart: always
depends_on:
mailman-database:
condition: service_healthy
volumes:
- ./data/conf/mailman/web/settings_local.py:/opt/mailman-web/settings_local.py
- mailman-web-vol-1:/opt/mailman-web-data:Z
environment:
- DATABASE_TYPE=postgres
- DATABASE_URL=postgresql://${MAILMAN_DB_USER}:${MAILMAN_DB_PASS}@mailman-database/${MAILMAN_DB_NAME}
- HYPERKITTY_API_KEY=${HYPERKITTY_API_KEY}
- SECRET_KEY=${DJANGO_KEY}
- SERVE_FROM_DOMAIN=${MAILMAN_DOMAIN}
networks:
mailcow-network:
aliases:
- mailman-web

mailman-database:
environment:
- POSTGRES_DB=${MAILMAN_DB_NAME}
- POSTGRES_USER=${MAILMAN_DB_USER}
- POSTGRES_PASSWORD=${MAILMAN_DB_PASS}
image: postgres:12-alpine
volumes:
- postgresql-vol-1:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready --dbname ${MAILMAN_DB_NAME} --username ${MAILMAN_DB_USER}"]
interval: 10s
timeout: 5s
retries: 5
networks:
mailcow-network:
aliases:
- mailman

# borgmatic configuration
borgmatic-mailcow:
image: ghcr.io/borgmatic-collective/borgmatic
hostname: mailcow
restart: always
dns: ${IPV4_NETWORK:-172.22.1}.254
volumes:
- vmail-vol-1:/mnt/source/vmail:ro
- crypt-vol-1:/mnt/source/crypt:ro
- redis-vol-1:/mnt/source/redis:ro
- rspamd-vol-1:/mnt/source/rspamd:ro
- postfix-vol-1:/mnt/source/postfix:ro
- borg-config-vol-1:/root/.config/borg
- borg-cache-vol-1:/root/.cache/borg
- ./data/conf/borgmatic/etc:/etc/borgmatic.d:Z
- ./data/conf/borgmatic/ssh:/root/.ssh:Z
# also backup docker-mailman configuration
- .:/mnt/source/docker-mailman:ro
environment:
- TZ=${TZ}
- BORG_PASSPHRASE=${BORG_PASSPHRASE}
- MAILCOW_DBNAME=${DBNAME}
- MAILCOW_DBUSER=${DBUSER}
- MAILCOW_DBPASS=${DBPASS}
- MAILCOW_DBHOST=mysql-mailcow
- MAILMAN_DB_NAME=${MAILMAN_DB_NAME}
- MAILMAN_DB_USER=${MAILMAN_DB_USER}
- MAILMAN_DB_PASS=${MAILMAN_DB_PASS}
- MAILMAN_DB_HOST=mailman-database
networks:
mailcow-network:
aliases:
- borgmatic
nginx-mailcow:
depends_on:
- redis-mailcow
- php-fpm-mailcow
- sogo-mailcow
- rspamd-mailcow
- caddy

# caddy reverse proxy
caddy:
build:
dockerfile: ./data/Dockerfiles/caddy/Dockerfile
hostname: caddy
restart: always
dns: ${IPV4_NETWORK:-172.22.1}.254
volumes:
- ./data/conf/caddy/etc:/etc/caddy:Z
- ./data/conf/caddy/data:/data
- ./data/conf/caddy/bin/reload.sh:/bin/reload.sh
- ./data/assets/ssl:/shared/ssl:Z
- mailman-web-vol-1:/opt/mailman-web-data:Z
ports:
- "${CADDY_BIND:-}:${CADDY_HTTPS_PORT:-443}:${CADDY_HTTPS_PORT:-443}"
- "${CADDY_BIND:-}:${CADDY_HTTP_PORT:-80}:${CADDY_HTTP_PORT:-80}"
environment:
- HTTPS_PORT=${HTTPS_PORT:-443}
- HTTP_PORT=${HTTP_PORT:-80}
- MAILCOW_HOSTNAME=${MAILCOW_HOSTNAME}
- MAILMAN_DOMAIN=${MAILMAN_DOMAIN}
- CADDY_BIND=${CADDY_BIND}
- CADDY_HTTP_PORT=${CADDY_HTTP_PORT}
- CADDY_HTTPS_PORT=${CADDY_HTTPS_PORT}
- COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME:-mailcow-dockerized}
networks:
mailcow-network:
aliases:
- caddy
volumes:
borg-cache-vol-1:
borg-config-vol-1:
postgresql-vol-1:
mailman-web-vol-1: