Rename: - docker-mailserver: hostname mail → mx, OVERRIDE_HOSTNAME → mx.csrx.ru - Traefik route: webmail/domain_webmail → mail/domain_mail - domain_webmail removed, domain_mail + domain_mx added to main.yml - certbot cert: mail.csrx.ru → mx.csrx.ru Email reliability improvements: - certbot renewal cron (03:15 + 15:15 daily) - deploy-hook: auto-reload Postfix+Dovecot after cert renewal - POSTFIX_MESSAGE_SIZE_LIMIT=26214400 (25 MB) - SPF hardened: ~all → -all - DMARC hardened: p=none → p=quarantine, added ruf + fo=1 + adkim/aspf strict - autodiscover/autoconfig CNAME records for mail client setup - dns-zone.zone fully updated with architecture comments Docs: - STATUS.md: full mail architecture section, client settings, DNS table - BACKLOG.md: rDNS task + DNS migration steps - DECISIONS.md: mx/mail split rationale Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
241 lines
6.5 KiB
Django/Jinja
241 lines
6.5 KiB
Django/Jinja
# Traefik dynamic routing config — generated by Ansible
|
|
# Do not edit manually; re-run ansible-playbook deploy.yml
|
|
|
|
# ── Wildcard TLS certificate via Cloudflare DNS-01 ────────────────────────────
|
|
# One *.csrx.ru cert covers all subdomains. New services = zero cert wait.
|
|
tls:
|
|
stores:
|
|
default:
|
|
defaultGeneratedCert:
|
|
resolver: letsencrypt
|
|
domain:
|
|
main: "*.{{ domain_base }}"
|
|
sans:
|
|
- "{{ domain_base }}"
|
|
|
|
http:
|
|
routers:
|
|
traefik-dashboard:
|
|
rule: "Host(`{{ domain_traefik }}`)"
|
|
entrypoints: [websecure]
|
|
tls:
|
|
certresolver: letsencrypt
|
|
service: api@internal
|
|
middlewares: [authelia@docker, rate-limit-strict]
|
|
|
|
vaultwarden:
|
|
rule: "Host(`{{ domain_vault }}`)"
|
|
entrypoints: [websecure]
|
|
tls:
|
|
certresolver: letsencrypt
|
|
service: vaultwarden
|
|
middlewares: [rate-limit-default]
|
|
|
|
forgejo:
|
|
rule: "Host(`{{ domain_git }}`)"
|
|
entrypoints: [websecure]
|
|
tls:
|
|
certresolver: letsencrypt
|
|
service: forgejo
|
|
middlewares: [rate-limit-default]
|
|
|
|
plane-api:
|
|
rule: "Host(`{{ domain_plane }}`) && (PathPrefix(`/api/`) || PathPrefix(`/auth/`))"
|
|
entrypoints: [websecure]
|
|
tls:
|
|
certresolver: letsencrypt
|
|
service: plane-api
|
|
middlewares: [rate-limit-api]
|
|
|
|
plane:
|
|
rule: "Host(`{{ domain_plane }}`)"
|
|
entrypoints: [websecure]
|
|
tls:
|
|
certresolver: letsencrypt
|
|
service: plane-web
|
|
middlewares: [rate-limit-default]
|
|
|
|
plane-godmode:
|
|
rule: "Host(`{{ domain_plane }}`) && PathPrefix(`/god-mode/`)"
|
|
entrypoints: [websecure]
|
|
tls:
|
|
certresolver: letsencrypt
|
|
service: plane-admin
|
|
middlewares: [authelia@docker, rate-limit-strict]
|
|
priority: 10
|
|
|
|
plane-spaces:
|
|
rule: "Host(`{{ domain_plane }}`) && PathPrefix(`/spaces/`)"
|
|
entrypoints: [websecure]
|
|
tls:
|
|
certresolver: letsencrypt
|
|
service: plane-space
|
|
middlewares: [rate-limit-default]
|
|
priority: 10
|
|
|
|
grafana:
|
|
rule: "Host(`{{ domain_dashboard }}`)"
|
|
entrypoints: [websecure]
|
|
tls:
|
|
certresolver: letsencrypt
|
|
service: grafana
|
|
middlewares: [rate-limit-default]
|
|
|
|
authelia:
|
|
rule: "Host(`{{ domain_auth }}`)"
|
|
entrypoints: [websecure]
|
|
tls:
|
|
certresolver: letsencrypt
|
|
service: authelia
|
|
middlewares: [rate-limit-strict]
|
|
|
|
uptime-kuma:
|
|
rule: "Host(`{{ domain_status }}`)"
|
|
entrypoints: [websecure]
|
|
tls:
|
|
certresolver: letsencrypt
|
|
service: uptime-kuma
|
|
middlewares: [rate-limit-default]
|
|
|
|
# ── Cross-server: tools ({{ ip_tools }}) ─────────────────────────────────
|
|
wiki:
|
|
rule: "Host(`{{ domain_wiki }}`)"
|
|
entrypoints: [websecure]
|
|
tls:
|
|
certresolver: letsencrypt
|
|
service: wiki
|
|
middlewares: [rate-limit-default]
|
|
|
|
n8n:
|
|
rule: "Host(`{{ domain_n8n }}`)"
|
|
entrypoints: [websecure]
|
|
tls:
|
|
certresolver: letsencrypt
|
|
service: n8n
|
|
middlewares: [rate-limit-strict]
|
|
|
|
mail:
|
|
rule: "Host(`{{ domain_mail }}`)"
|
|
entrypoints: [websecure]
|
|
tls:
|
|
certresolver: letsencrypt
|
|
service: mail
|
|
middlewares: [rate-limit-default]
|
|
|
|
services:
|
|
vaultwarden:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "http://vaultwarden:80"
|
|
|
|
forgejo:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "http://forgejo:3000"
|
|
|
|
plane-api:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "http://plane-api:8000"
|
|
|
|
plane-web:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "http://plane-web:3000"
|
|
|
|
plane-admin:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "http://plane-admin:80"
|
|
|
|
plane-space:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "http://plane-space:3000"
|
|
|
|
grafana:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "http://grafana:3000"
|
|
|
|
authelia:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "http://authelia:9091"
|
|
|
|
uptime-kuma:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "http://uptime-kuma:3001"
|
|
|
|
# ── Cross-server services ─────────────────────────────────────────────────
|
|
wiki:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "http://{{ ip_tools }}:3000"
|
|
|
|
n8n:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "http://{{ ip_tools }}:5678"
|
|
|
|
mail:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "http://{{ ip_tools }}:8888"
|
|
|
|
middlewares:
|
|
# ── Security Headers (applied globally via entrypoint) ─────────────────
|
|
security-headers:
|
|
headers:
|
|
stsSeconds: 31536000
|
|
stsIncludeSubdomains: true
|
|
stsPreload: true
|
|
forceSTSHeader: true
|
|
frameDeny: true
|
|
contentTypeNosniff: true
|
|
browserXssFilter: true
|
|
referrerPolicy: "strict-origin-when-cross-origin"
|
|
permissionsPolicy: "camera=(), microphone=(), geolocation=(), payment=()"
|
|
customResponseHeaders:
|
|
X-Robots-Tag: "noindex, nofollow"
|
|
Server: ""
|
|
|
|
# ── Rate Limiting ──────────────────────────────────────────────────────
|
|
# Default: 100 req/s burst 50 — general web traffic
|
|
rate-limit-default:
|
|
rateLimit:
|
|
average: 100
|
|
burst: 50
|
|
period: 1s
|
|
|
|
# API: 30 req/s burst 20 — API endpoints
|
|
rate-limit-api:
|
|
rateLimit:
|
|
average: 30
|
|
burst: 20
|
|
period: 1s
|
|
|
|
# Strict: 10 req/s burst 5 — admin/login interfaces
|
|
rate-limit-strict:
|
|
rateLimit:
|
|
average: 10
|
|
burst: 5
|
|
period: 1s
|
|
|
|
# ── Auth (legacy basic auth kept for direct fallback) ─────────────────
|
|
traefik-auth:
|
|
basicAuth:
|
|
users:
|
|
- "{{ traefik_dashboard_htpasswd }}"
|
|
|
|
# ── Authelia ForwardAuth ───────────────────────────────────────────────
|
|
authelia:
|
|
forwardAuth:
|
|
address: "http://authelia:9091/api/verify?rd=https://{{ domain_auth }}"
|
|
trustForwardHeader: true
|
|
authResponseHeaders:
|
|
- Remote-User
|
|
- Remote-Groups
|
|
- Remote-Email
|
|
- Remote-Name
|