diff --git a/.env.mail.example b/.env.mail.example index 9a0936a..05a7765 100644 --- a/.env.mail.example +++ b/.env.mail.example @@ -8,3 +8,6 @@ MAIL_BIND_IP=0.0.0.0 # The setup UI is bound to 127.0.0.1 only. MAIL_SETUP_PORT=8081 + +# Certbot deploys the public certificate and private key here. +MAIL_TLS_DIR=/etc/moonwell-mail/tls diff --git a/compose.mail.yaml b/compose.mail.yaml index c10c98e..3826001 100644 --- a/compose.mail.yaml +++ b/compose.mail.yaml @@ -16,6 +16,7 @@ services: volumes: - 'stalwart-config:/etc/stalwart' - 'stalwart-data:/var/lib/stalwart' + - '${MAIL_TLS_DIR:-/etc/moonwell-mail/tls}:/run/stalwart-tls:ro' networks: default: aliases: diff --git a/docs/mail-server.md b/docs/mail-server.md index be90687..f6bd816 100644 --- a/docs/mail-server.md +++ b/docs/mail-server.md @@ -134,3 +134,18 @@ docker compose exec -T laravel.test php artisan config:cache Перед восстановлением используй ту же minor-версию образа Stalwart, что указана в `compose.mail.yaml`. + +## TLS на production-сервере + +На production сертификат выпускает системный Certbot через Nginx. Контейнер +получает read-only каталог `MAIL_TLS_DIR` и использует: + +```text +/run/stalwart-tls/fullchain.pem +/run/stalwart-tls/privkey.pem +``` + +Deploy-hook Certbot копирует обновлённые файлы в этот каталог и перезапускает +только контейнер Stalwart, чтобы он перечитал сертификат. + +Hook устанавливается из `scripts/deploy-mail-certificate.sh`. diff --git a/ops/nginx-mail.conf b/ops/nginx-mail.conf new file mode 100644 index 0000000..62633ae --- /dev/null +++ b/ops/nginx-mail.conf @@ -0,0 +1,31 @@ +server { + server_name mail.moon-well.online; + listen 80; + + location / { + return 301 https://$host$request_uri; + } +} + +server { + server_name mail.moon-well.online; + listen 443 ssl; + + ssl_certificate /etc/letsencrypt/live/mail.moon-well.online/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/mail.moon-well.online/privkey.pem; + include /etc/letsencrypt/options-ssl-nginx.conf; + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; + + client_max_body_size 50M; + + location / { + proxy_pass http://127.0.0.1:8082; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +} diff --git a/resources/css/moonwell.css b/resources/css/moonwell.css index 524c44a..61d0b52 100644 --- a/resources/css/moonwell.css +++ b/resources/css/moonwell.css @@ -1560,7 +1560,7 @@ section { } .footer-grid { display: grid; - grid-template-columns: 2fr 1fr 1fr 1fr; + grid-template-columns: 2fr 1fr 1fr; gap: 48px; padding-top: 56px; border-top: 1px solid var(--panel-border); @@ -1583,6 +1583,13 @@ section { } .footer-col a:hover { color: var(--ivory); } +.join-note-link { + color: var(--moon); + text-decoration: underline; + text-decoration-color: rgba(127,184,212,0.3); +} +.join-note-link:hover { text-decoration-color: var(--moon); } + .footer-brand { max-width: 360px; } diff --git a/resources/js/Pages/Landing/Components/Footer.vue b/resources/js/Pages/Landing/Components/Footer.vue index 6627d16..ce33370 100644 --- a/resources/js/Pages/Landing/Components/Footer.vue +++ b/resources/js/Pages/Landing/Components/Footer.vue @@ -36,8 +36,12 @@ defineProps({
Приватный сервер World of Warcraft 3.3.5a с личным прогрессом, одиночными подземельями и режимом «Предатель». Играй бесплатно — покупки не дают боевого преимущества.
- - + diff --git a/scripts/deploy-mail-certificate.sh b/scripts/deploy-mail-certificate.sh new file mode 100755 index 0000000..8169dba --- /dev/null +++ b/scripts/deploy-mail-certificate.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +set -Eeuo pipefail + +CERTIFICATE_LINEAGE="${RENEWED_LINEAGE:-/etc/letsencrypt/live/mail.moon-well.online}" +TLS_DIR="${MAIL_TLS_DIR:-/etc/moonwell-mail/tls}" +MAIL_CONTAINER="${MAIL_CONTAINER:-moonwell-web-stalwart-1}" + +[[ -s "${CERTIFICATE_LINEAGE}/fullchain.pem" ]] || { + printf '[mail:tls:error] Certificate chain is missing\n' >&2 + exit 1 +} + +[[ -s "${CERTIFICATE_LINEAGE}/privkey.pem" ]] || { + printf '[mail:tls:error] Private key is missing\n' >&2 + exit 1 +} + +install -d -o root -g 2000 -m 0750 "$TLS_DIR" +install -o root -g 2000 -m 0640 \ + "${CERTIFICATE_LINEAGE}/fullchain.pem" \ + "${TLS_DIR}/fullchain.pem" +install -o root -g 2000 -m 0640 \ + "${CERTIFICATE_LINEAGE}/privkey.pem" \ + "${TLS_DIR}/privkey.pem" + +if docker container inspect "$MAIL_CONTAINER" >/dev/null 2>&1; then + docker restart "$MAIL_CONTAINER" >/dev/null +fi + +printf '[mail:tls] Certificate deployed from %s\n' "$CERTIFICATE_LINEAGE"