diff --git a/.env.model.public b/.env.model.public index 05f2a43..5a2e534 100644 --- a/.env.model.public +++ b/.env.model.public @@ -1,2 +1,3 @@ # chat db networking -HERMES_MODEL_DB_FIXED_IP= \ No newline at end of file +HERMES_MODEL_DB_FIXED_IP= +HERMES_MODEL_CLOUD_MARIA_IP= diff --git a/README.md b/README.md index 78da3a0..dfdf440 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,94 @@ What: socket.io, Notion, OpenAI, Whatsapp/Telegram/etc APIs ## Getting Started +### Mail + Nextcloud deployment (Docker Compose now, k3s via `kompose`) + +1. Copy each `*.public` file to its matching `*.private` file and fill in the required values. +2. Run the deployment in order from the repository root: + +```bash +cp .env.common.public .env.common.private +cp .env.mail.public .env.mail.private +cp .env.cloud.public .env.cloud.private +cp .env.ingress.public .env.ingress.private +cp .env.model.public .env.model.private + +./init-networks.sh +./init-weagle.sh +./init-model.sh +./init-mail.sh # local mail storage +./init-cloud.sh ``` -``` + +Use `./init-mail.sh setup:storage` only when the mail S3 variables are set and `s3fs` is installed. For a single-command bring-up, run `./init.sh`. + +For k3s generation, run `./init-k8s-config.sh` after the same private env files are present. The generated manifests depend on the compose service ports, so the mail template now exports SMTP/Submission/IMAP/IMAPS/POP3/POP3S/Sieve explicitly for Traefik and `kompose`. + +### MUST-NOT-BE-EMPTY variables + +#### Common (`.env.common.private`) + +| Variable | Why it is required | +| --- | --- | +| `HERMES_APEX` | Base mail domain for certificates and mailserver identity. | +| `HERMES_HOSTNAME` | Public FQDN used by Traefik and Nextcloud. | +| `HERMES_CERT_FILE` | Mounted TLS certificate filename. | +| `HERMES_CERT_KEY_FILE` | Mounted TLS private key filename. | +| `HERMES_MAIL_DOMAINS` | Domains provisioned in docker-mailserver. | +| `HERMES_MAIL_MAIN_HOSTNAME` | Mail host used for mailbox creation and DNS guidance. | +| `HERMES_DNS_RESOLVER` | Resolver injected into mail/cloud containers. | +| `HERMES_INGRESS_SUBNET` | Trusted proxy subnet for Nextcloud behind Traefik. | +| `HERMES_CLOUD_DB_HOST` | MariaDB host for Nextcloud. | +| `HERMES_PORT_PREFIX` | Prefix used when compose is converted into k8s services. | + +#### Mail (`.env.mail.private`) + +| Variable | Why it is required | +| --- | --- | +| `HERMES_MAIL_CERT_TYPE` | `docker-mailserver` TLS mode (`manual` for the bundled cert mount). | +| `HERMES_MAIN_MAILBOX` | Main administrator mailbox reference. | +| `HERMES_MAIL_INITIAL_BOXES` | Comma-separated local parts for optional bootstrap mailboxes. | +| `HERMES_MAIL_INITIAL_BOXES_DEFAULT_PASSWORD` | Password used when `setup:mailboxes` is invoked. | + +Optional only when using S3-backed mail storage: `HERMES_MAIL_S3_BUCKET`, `HERMES_MAIL_S3_HOST`, `HERMES_MAIL_S3_KEY`, `HERMES_MAIL_S3_SECRET`. + +#### Cloud (`.env.cloud.private`) + +| Variable | Why it is required | +| --- | --- | +| `HERMES_CLOUD_BASEPATH` | Nextcloud path prefix (default `/cloud`). | +| `HERMES_CLOUD_DB_NAME` | Nextcloud MariaDB database name. | +| `HERMES_CLOUD_DB_USER` | Nextcloud MariaDB user. | +| `HERMES_CLOUD_DB_PASS` | Nextcloud MariaDB password. | +| `HERMES_CLOUD_DB_REDIS` | Redis hostname reachable on `hermes-net-model-cloud`. | + +Optional S3/object-store values: `HERMES_CLOUD_S3_BUCKET`, `HERMES_CLOUD_S3_HOST`, `HERMES_CLOUD_S3_REGION`, `HERMES_CLOUD_S3_KEY`, `HERMES_CLOUD_S3_SECRET`. + +#### Ingress (`.env.ingress.private`) + +| Variable | Why it is required | +| --- | --- | +| `HERMES_TRAEFIK_BASE_AUTH` | Protects the Traefik dashboard. | +| `HERMES_CLOUDFLARE_EMAIL` | ACME DNS challenge account. | +| `HERMES_CLOUDFLARE_API_KEY` | ACME DNS challenge credential. | + +#### Model (`.env.model.private`) + +| Variable | Why it is required | +| --- | --- | +| `HERMES_MODEL_CLOUD_MARIA_IP` | Static IP assigned to `hermes-model-mariadb` on `hermes-net-model-cloud`. | + +### Fresh-server notes + +- `./init-mail.sh` is idempotent for local storage and only mounts S3 when `setup:storage` is requested. +- `./init-cloud.sh` now writes `cloud/data/cloud/config/hermes.config.php` from `cloud/_config.php`, so fresh installs no longer depend on an already-existing Nextcloud `config.php`. +- The mail Traefik TCP routers use wildcard SNI matching for plain TCP/STARTTLS protocols and passthrough only on implicit TLS ports. + +### Troubleshooting + +- If `init-mail.sh` or `init-cloud.sh` exits early, fill in the missing required env variable reported by the script. +- If `setup:storage` fails, confirm `s3fs` is installed and that `mail/data/email-data` is not already mounted. +- If Nextcloud is reachable but redirects incorrectly, verify `HERMES_HOSTNAME`, `HERMES_CLOUD_BASEPATH`, and `HERMES_INGRESS_SUBNET`, then rerun `./init-cloud.sh` to regenerate `hermes.config.php`. Documentation: [Research Paper](https://angeloreale.notion.site/Lady-Science-100-Days-of-Products-Day-012-DreamLetter-Angelo-Reale-078155e635a747e8b06ba1c67ec28bfe?pvs=4) diff --git a/cloud/_config.php b/cloud/_config.php index df74f41..f67d247 100644 --- a/cloud/_config.php +++ b/cloud/_config.php @@ -1,6 +1,10 @@ '${HERMES_CLOUD_BASEPATH}', + 'overwrite.cli.url' => 'https://${HERMES_HOSTNAME}${HERMES_CLOUD_BASEPATH}', + 'overwritehost' => '${HERMES_HOSTNAME}', + 'overwriteprotocol' => 'https', + 'overwritewebroot' => '${HERMES_CLOUD_BASEPATH}', 'memcache.local' => '\OC\Memcache\APCu', 'memcache.distributed' => '\OC\Memcache\Redis', 'memcache.locking' => '\OC\Memcache\Redis', @@ -8,6 +12,10 @@ 'host' => '${HERMES_CLOUD_DB_REDIS}', 'port' => 6379, ], + 'trusted_proxies' => + array ( + 0 => '${HERMES_INGRESS_SUBNET}', + ), 'apps_paths' => array ( 0 => @@ -23,4 +31,4 @@ 'writable' => true, ), ), -); \ No newline at end of file +); diff --git a/cloud/_docker-compose.yml b/cloud/_docker-compose.yml index 0de1739..6d9427d 100644 --- a/cloud/_docker-compose.yml +++ b/cloud/_docker-compose.yml @@ -26,9 +26,6 @@ services: - "./data/cloud/custom_apps:/var/www/html/custom_apps" - "./data/cloud/config:/var/www/html/config" - "./data/cloud/data:/var/www/html/data" - external_links: - - hermes-model-mariadb - - hermes-model-redis environment: PUID: 1000 PGID: 1000 diff --git a/init-cloud.sh b/init-cloud.sh index 7df5349..9370ef4 100755 --- a/init-cloud.sh +++ b/init-cloud.sh @@ -1,26 +1,29 @@ #!/bin/bash +set -euo pipefail # init mail echo -e "\033[0;62m\033[0;49;35m" -set -a && source .env.common.private && set +a -set -a && source .env.cloud.private && set +a -root_dir="$(pwd)" +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +set -a && source "$script_dir/.env.common.private" && set +a +set -a && source "$script_dir/.env.cloud.private" && set +a +root_dir="$script_dir" log () { + local level="${2:-}" echo -e "\033[0;49;35m" # warning - if [[ "$2" == "2" ]]; then + if [[ "$level" == "2" ]]; then echo -e "\033[35;43m$1\033[0m" fi # error - if [[ "$2" == "1" ]]; then + if [[ "$level" == "1" ]]; then echo -e "\033[35;41m\033[33m$1\033[0m" fi # success - if [[ "$2" == "0" ]]; then + if [[ "$level" == "0" ]]; then echo -e "\033[35;42m$1\033[0m" fi # normal - if [[ "$2" == "" ]]; then + if [[ "$level" == "" ]]; then echo -e "\033[35;46m$1\033[0m" fi } @@ -44,26 +47,58 @@ take () { while true; do echo -n .; sleep 1; done | pv -s $1 -S -F '%t %p' > /dev/null } +require_env () { + local name="$1" + if [[ -z "${!name:-}" ]]; then + log "dp::hermes::cloud::(error)::Missing required environment variable: $name" 1 + exit 1 + fi +} + +validate_cloud_env () { + for name in \ + HERMES_HOSTNAME \ + HERMES_CLOUD_BASEPATH \ + HERMES_CLOUD_DB_NAME \ + HERMES_CLOUD_DB_USER \ + HERMES_CLOUD_DB_PASS \ + HERMES_CLOUD_DB_HOST \ + HERMES_CLOUD_DB_REDIS \ + HERMES_DNS_RESOLVER \ + HERMES_INGRESS_SUBNET + do + require_env "$name" + done +} + # Cloud +validate_cloud_env +mkdir -p "$root_dir/cloud/data/cloud/html" "$root_dir/cloud/data/cloud/custom_apps" "$root_dir/cloud/data/cloud/config" "$root_dir/cloud/data/cloud/data" + log "dp::hermes::hermes_net_cloud::(busy):: Preparing Claudia (Storage, Calendar, Mail Web Client: Nextcloud)." 2 log "dp::hermes::hermes_net_cloud::(busy):: Preparing Claudia: Consolidating files." -origin="./cloud/_docker-compose.yml" -destination="./cloud/docker-compose.yml" -tmpfile=$(mktemp --tmpdir=.) -cp -p $origin $tmpfile -cat $origin | envsubst > $tmpfile && mv $tmpfile $destination +origin="$root_dir/cloud/_docker-compose.yml" +destination="$root_dir/cloud/docker-compose.yml" +tmpfile=$(mktemp --tmpdir="$root_dir") +envsubst < "$origin" > "$tmpfile" && mv "$tmpfile" "$destination" log "dp::hermes::hermes_net_cloud::(busy):: Preparing Claudia: Consolidating files." -sed -i "s/'host' => 'redis'/'host' => '$HERMES_CLOUD_DB_REDIS'/g" cloud/data/cloud/config/config.php -sed -i "s/'htaccess.RewriteBase' => '/'/'htaccess.RewriteBase' => '$HERMES_CLOUD_BASEPATH'/g" cloud/data/cloud/config/config.php +cloud_config_destination="$root_dir/cloud/data/cloud/config/hermes.config.php" +# Limit substitution to Hermes placeholders so PHP symbols like $CONFIG stay intact. +envsubst '${HERMES_CLOUD_BASEPATH} ${HERMES_HOSTNAME} ${HERMES_CLOUD_DB_REDIS} ${HERMES_INGRESS_SUBNET}' \ + < "$root_dir/cloud/_config.php" > "$cloud_config_destination" +if [[ ! -s "$cloud_config_destination" ]]; then + log "dp::hermes::cloud::(error)::Failed to generate $cloud_config_destination" 1 + exit 1 +fi # dir setup take 5 "dp::hermes::hermes_net_cloud::(busy):: Launching Docker Compose Swarms." -cd cloud +cd "$root_dir/cloud" docker compose up -d -cd $root_dir +cd "$root_dir" log "dp::hermes::hermes_net_cloud::(idle)::all good." 0 diff --git a/init-mail.sh b/init-mail.sh index ff1ff7b..81b5040 100755 --- a/init-mail.sh +++ b/init-mail.sh @@ -1,26 +1,31 @@ #!/bin/bash +set -euo pipefail # init emailia echo -e "\033[0;62m\033[0;49;35m" -set -a && source .env.common.private && set +a -set -a && source .env.mail.private && set +a -root_dir="$(pwd)" +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +set -a && source "$script_dir/.env.common.private" && set +a +set -a && source "$script_dir/.env.mail.private" && set +a +root_dir="$script_dir" +S3_MOUNT_MAX_RETRIES=10 +S3FS_PASSWD_FILE="$HOME/.passwd-s3fs" log () { + local level="${2:-}" echo -e "\033[0;49;35m" # warning - if [[ "$2" == "2" ]]; then + if [[ "$level" == "2" ]]; then echo -e "\033[35;43m$1\033[0m" fi # error - if [[ "$2" == "1" ]]; then + if [[ "$level" == "1" ]]; then echo -e "\033[35;41m\033[33m$1\033[0m" fi # success - if [[ "$2" == "0" ]]; then + if [[ "$level" == "0" ]]; then echo -e "\033[35;42m$1\033[0m" fi # normal - if [[ "$2" == "" ]]; then + if [[ "$level" == "" ]]; then echo -e "\033[35;46m$1\033[0m" fi } @@ -44,6 +49,30 @@ take () { while true; do echo -n .; sleep 1; done | pv -s $1 -S -F '%t %p' > /dev/null } +require_env () { + local name="$1" + if [[ -z "${!name:-}" ]]; then + log "dp::hermes::mail::(error)::Missing required environment variable: $name" 1 + exit 1 + fi +} + +validate_mail_env () { + for name in \ + HERMES_HOSTNAME \ + HERMES_APEX \ + HERMES_CERT_FILE \ + HERMES_CERT_KEY_FILE \ + HERMES_MAIL_CERT_TYPE \ + HERMES_DNS_RESOLVER \ + HERMES_PORT_PREFIX \ + HERMES_MAIL_MAIN_HOSTNAME \ + HERMES_MAIL_DOMAINS + do + require_env "$name" + done +} + setup_dns () { log "dp::hermes::mail::(busy):: Preparing Aemilia (Mail: DMS): DNS Setup: Generating DKIMS." 2 docker exec -it hermes-mail-mailserver setup config dkim domain $1 @@ -59,43 +88,74 @@ setup_mailbox () { setup_storage () { log "dp::hermes::mail::(busy):: Preparing Aemilia (Mail: DMS): Mailbox Setup: Preparing cloud email storage." 2 - echo $HERMES_MAIL_S3_KEY:$HERMES_MAIL_S3_SECRET > ~/.passwd-s3fs - chmod 600 ~/.passwd-s3fs - mkdir mail/data/email-data - touch mail/data/email-data/dummy + require_env HERMES_MAIL_S3_BUCKET + require_env HERMES_MAIL_S3_HOST + require_env HERMES_MAIL_S3_KEY + require_env HERMES_MAIL_S3_SECRET + echo "$HERMES_MAIL_S3_KEY:$HERMES_MAIL_S3_SECRET" > "$S3FS_PASSWD_FILE" + chmod 600 "$S3FS_PASSWD_FILE" + mkdir -p "$root_dir/mail/data/email-data" + touch "$root_dir/mail/data/email-data/dummy" log "dp::hermes::mail::(busy):: Preparing Aemilia (Mail: DMS): Cloud email storage ready." 0 } init_storage () { + local s3_mount_log="$root_dir/mail/data/email-data/.s3fs.log" + local s3fs_pid="" + command -v s3fs >/dev/null 2>&1 || { + log "dp::hermes::mail::(error)::s3fs is required for setup:storage." 1 + exit 1 + } take 2 "dp::hermes::mail::(busy):: Preparing Aemilia (Mail: DMS): Mailbox Setup: Fusing S3 bucket." - s3fs $HERMES_MAIL_S3_BUCKET mail/data/email-data -o nonempty -o passwd_file=~/.passwd-s3fs -o use_path_request_style -o url=https://${HERMES_MAIL_S3_HOST} -f & + if ! mountpoint -q "$root_dir/mail/data/email-data"; then + s3fs "$HERMES_MAIL_S3_BUCKET" "$root_dir/mail/data/email-data" -o nonempty -o passwd_file="$S3FS_PASSWD_FILE" -o use_path_request_style -o url="https://${HERMES_MAIL_S3_HOST}" >"$s3_mount_log" 2>&1 & + s3fs_pid=$! + for _ in $(seq 1 "$S3_MOUNT_MAX_RETRIES") + do + if mountpoint -q "$root_dir/mail/data/email-data"; then + break + fi + if [[ -n "$s3fs_pid" ]] && ! kill -0 "$s3fs_pid" 2>/dev/null; then + break + fi + sleep 1 + done + fi + mountpoint -q "$root_dir/mail/data/email-data" || { + if [[ -s "$s3_mount_log" ]]; then + log "dp::hermes::mail::(error)::Cloud email storage mount log: $(tail -n 1 "$s3_mount_log")" 1 + fi + log "dp::hermes::mail::(error)::Cloud email storage mount failed." 1 + exit 1 + } log "dp::hermes::mail::(busy):: Preparing Aemilia (Mail: DMS): Cloud email storage mounted." 0 } +validate_mail_env +mkdir -p "$root_dir/mail/data/dms/mail-state" "$root_dir/mail/data/dms/mail-logs" "$root_dir/mail/data/dms/config" "$root_dir/mail/data/email-data" + log "dp::hermes::mail::(busy):: Preparing Aemilia (Mail: DMS) configuration files." 2 -origin="./mail/_docker-compose.yml" -destination="./mail/docker-compose.yml" -tmpfile=$(mktemp --tmpdir=.) -cp -p $origin $tmpfile -cat $origin | envsubst > $tmpfile && mv $tmpfile $destination +origin="$root_dir/mail/_docker-compose.yml" +destination="$root_dir/mail/docker-compose.yml" +tmpfile=$(mktemp --tmpdir="$root_dir") +envsubst < "$origin" > "$tmpfile" && mv "$tmpfile" "$destination" -if [ "$1" == "setup:storage" ]; then +if [ "${1:-}" == "setup:storage" ]; then setup_storage init_storage else - log "dp::hermes::mail::(busy):: Preparing Aemilia (Mail: DMS): Skipping Storage setup." - init_storage + log "dp::hermes::mail::(busy):: Preparing Aemilia (Mail: DMS): Skipping Storage setup, using local mail/data/email-data." fi # dir setup take 5 "dp::hermes::mail::(busy):: Launching Docker Compose Swarms." -cd mail +cd "$root_dir/mail" docker compose up -d -cd $root_dir +cd "$root_dir" # log "dp::hermes::mail::(busy):: Preparing Aemilia (Mail: DMS) Installing setup CLI." 2 # wget https://raw.githubusercontent.com/docker-mailserver/docker-mailserver/master/setup.sh @@ -104,7 +164,7 @@ cd $root_dir # log "dp::hermes::mail::(busy):: Preparing Aemilia (Mail: DMS) Adding mailboxes." 2 # ./setup.sh email add $HERMES_MAIN_MAILBOX -if [ "$1" == "setup:dns" ]; then +if [ "${1:-}" == "setup:dns" ]; then for domain in ${HERMES_MAIL_DOMAINS//,/ } do setup_dns $domain @@ -113,7 +173,9 @@ else log "dp::hermes::mail::(busy):: Preparing Aemilia (Mail: DMS): Skipping DNS setup." fi -if [ "$1" == "setup:mailboxes" ]; then +if [ "${1:-}" == "setup:mailboxes" ]; then + require_env HERMES_MAIL_INITIAL_BOXES + require_env HERMES_MAIL_INITIAL_BOXES_DEFAULT_PASSWORD for box in ${HERMES_MAIL_INITIAL_BOXES//,/ } do setup_mailbox $box $HERMES_MAIL_INITIAL_BOXES_DEFAULT_PASSWORD diff --git a/mail/_docker-compose.yml b/mail/_docker-compose.yml index 1830257..8f7bbea 100644 --- a/mail/_docker-compose.yml +++ b/mail/_docker-compose.yml @@ -19,31 +19,25 @@ services: # TCP: port layer 6 switch #- traefik.tcp.routers.mail-submissions.rule=HostSNI(`mail.example.com`) # - traefik.tcp.routers.mail-submissions.tls.passthrough=true - - "traefik.tcp.routers.smtp.rule=HostSNI(`${HERMES_HOSTNAME}`)" + - "traefik.tcp.routers.smtp.rule=HostSNI(`*`)" - "traefik.tcp.routers.smtp.entrypoints=smtp" - "traefik.tcp.routers.smtp.service=smtp" - # - "traefik.tcp.routers.smtp.tls.certresolver=le-ssl" - - "traefik.tcp.routers.smtp.tls.passthrough=true" - "traefik.tcp.services.smtp.loadbalancer.server.port=25" - "traefik.tcp.services.smtp.loadbalancer.proxyProtocol.version=2" - - "traefik.tcp.routers.submissions.rule=HostSNI(`${HERMES_HOSTNAME}`)" + - "traefik.tcp.routers.submissions.rule=HostSNI(`*`)" - "traefik.tcp.routers.submissions.entrypoints=submissions" - # - "traefik.tcp.routers.submissions.tls.certresolver=le-ssl" - - "traefik.tcp.routers.submissions.tls.passthrough=true" - "traefik.tcp.routers.submissions.service=submissions" - - "traefik.tcp.services.submissions.loadbalancer.server.port=465" + - "traefik.tcp.services.submissions.loadbalancer.server.port=587" - "traefik.tcp.services.submissions.loadbalancer.proxyProtocol.version=2" - - "traefik.tcp.routers.imap.rule=HostSNI(`${HERMES_HOSTNAME}`)" + - "traefik.tcp.routers.imap.rule=HostSNI(`*`)" - "traefik.tcp.routers.imap.entrypoints=imap" - # - "traefik.tcp.routers.imap.tls.certresolver=le-ssl" - - "traefik.tcp.routers.imap.tls.passthrough=true" - - "traefik.tcp.routers.imap.service=imaps" + - "traefik.tcp.routers.imap.service=imap" - "traefik.tcp.services.imap.loadbalancer.server.port=143" - "traefik.tcp.services.imap.loadbalancer.proxyProtocol.version=2" - - "traefik.tcp.routers.imaps.rule=HostSNI(`${HERMES_HOSTNAME}`)" + - "traefik.tcp.routers.imaps.rule=HostSNI(`*`)" - "traefik.tcp.routers.imaps.entrypoints=imaps" # - "traefik.tcp.routers.imaps.tls.certresolver=le-ssl" - "traefik.tcp.routers.imaps.tls.passthrough=true" @@ -51,15 +45,13 @@ services: - "traefik.tcp.services.imaps.loadbalancer.server.port=993" - "traefik.tcp.services.imaps.loadbalancer.proxyProtocol.version=2" - - "traefik.tcp.routers.pop3.rule=HostSNI(`${HERMES_HOSTNAME}`)" + - "traefik.tcp.routers.pop3.rule=HostSNI(`*`)" - "traefik.tcp.routers.pop3.entrypoints=pop3" - # - "traefik.tcp.routers.pop3.tls.certresolver=le-ssl" - - "traefik.tcp.routers.pop3.tls.passthrough=true" - "traefik.tcp.routers.pop3.service=pop3" - "traefik.tcp.services.pop3.loadbalancer.server.port=110" - "traefik.tcp.services.pop3.loadbalancer.proxyProtocol.version=2" - - "traefik.tcp.routers.pop3s.rule=HostSNI(`${HERMES_HOSTNAME}`)" + - "traefik.tcp.routers.pop3s.rule=HostSNI(`*`)" - "traefik.tcp.routers.pop3s.entrypoints=pop3s" # - "traefik.tcp.routers.pop3s.tls.certresolver=le-ssl" - "traefik.tcp.routers.pop3s.tls.passthrough=true" @@ -67,10 +59,8 @@ services: - "traefik.tcp.services.pop3s.loadbalancer.server.port=995" - "traefik.tcp.services.pop3s.loadbalancer.proxyProtocol.version=2" - - "traefik.tcp.routers.sieve.rule=HostSNI(`${HERMES_HOSTNAME}`)" + - "traefik.tcp.routers.sieve.rule=HostSNI(`*`)" - "traefik.tcp.routers.sieve.entrypoints=sieve" - # - "traefik.tcp.routers.sieve.tls.certresolver=le-ssl" - - "traefik.tcp.routers.sieve.tls.passthrough=true" - "traefik.tcp.routers.sieve.service=sieve" - "traefik.tcp.services.sieve.loadbalancer.server.port=4190" - "traefik.tcp.services.sieve.loadbalancer.proxyProtocol.version=2" @@ -118,12 +108,15 @@ services: # keep it for k8s/helm charts only. ports: - "${HERMES_PORT_PREFIX}12:25/tcp" - - "${HERMES_PORT_PREFIX}13:465/tcp" - - "${HERMES_PORT_PREFIX}14:995/tcp" + - "${HERMES_PORT_PREFIX}13:587/tcp" + - "${HERMES_PORT_PREFIX}14:143/tcp" - "${HERMES_PORT_PREFIX}15:993/tcp" + - "${HERMES_PORT_PREFIX}16:110/tcp" + - "${HERMES_PORT_PREFIX}17:995/tcp" + - "${HERMES_PORT_PREFIX}18:4190/tcp" networks: hermes-net-weagle: name: hermes-net-weagle driver: bridge - external: true \ No newline at end of file + external: true