Files
pipistrelloandClaude Opus 5 445bb83072 traefik: enable the dashboard router now that traefik.shcnw.ru exists
Both pre-cutover prerequisites are satisfied:

- Port 80 NAT moved back to the docker host (192.168.0.9); verified externally
  that :80 is answered there. This is what lets the exchange-acme router forward
  /.well-known/acme-challenge/ to Exchange and replace the manual NAT repoint that
  every past win-acme renewal has needed.
- traefik.shcnw.ru A 217.15.22.194 created, so the dashboard router is uncommented.

The router was shipped disabled because ACME validates from the internet even for
a LAN-only router — ipAllowList gates access, not issuance. That rule is now
stated explicitly in the README rather than left as a one-off note, since it
applies to every LAN-only host in this stack (start, portainer, cockpit).

The earlier pre-flight already parsed this exact router (it got as far as
attempting issuance for traefik.shcnw.ru and failing on NXDOMAIN), so the
uncomment is not introducing anything unvalidated.

Host restage of dynamic/services.yml is still PENDING — the 43009 tunnel went down
before it could be copied. The host currently holds the previous revision, in
which the dashboard router is absent. Everything else is identical, so a deploy
before the restage would come up correctly minus the dashboard.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 16:17:04 +03:00

185 lines
8.3 KiB
YAML

# ─────────────────────────────────────────────────────────────────────────────
# Traefik DYNAMIC configuration (file provider) — 43-meditsina
# Host path: /mnt/containers/traefik/container-data/dynamic/services.yml
# Hot-reloaded on save (watch: true) — no container restart needed for edits here.
#
# Replaces 13 nginx-proxy-manager proxy hosts. Two are deliberately NOT migrated:
# nginx.shcnw.ru — NPM's own admin UI; NPM is gone
# nginxlogs.shcnw.ru — goaccess; it parses NPM's logs, and Exchange (99% of traffic)
# moves to L4 where there is no HTTP access log. Retire it.
# ─────────────────────────────────────────────────────────────────────────────
tcp:
# ===========================================================================
# Exchange — SNI PASS-THROUGH (Layer 4).
# TLS terminates ON Exchange (192.168.0.6), so connection-bound Windows auth
# (NTLM / Negotiate) survives end-to-end → desktop Outlook can be configured
# from outside. Also removes the ActiveSync `Cmd=Ping` 504s: IIS owns the
# long-poll, so nginx's 60s proxy_read_timeout no longer truncates it.
#
# Exchange presents its own 24-SAN public LE cert (C178CFB8…, exp 2026-11-24);
# Traefik must NOT terminate or manage a cert for these names.
#
# The 24 SNIs below are exactly that certificate's SAN list. Three of them
# (mail/autodiscover.alisailina.com, autodiscover.neviol.ru) have no NPM vhost
# today and currently fail the TLS handshake outright — including them here
# fixes them at no cost. See the migration plan §5.
# ===========================================================================
routers:
exchange:
entryPoints: [websecure]
# v3 REJECTS HostSNI(`a`,`b`) — they must be OR-ed.
rule: >-
HostSNI(`mail.shcnw.ru`) || HostSNI(`autodiscover.shcnw.ru`)
|| HostSNI(`mail.exprinta.ru`) || HostSNI(`autodiscover.exprinta.ru`)
|| HostSNI(`mail.inkam.navy`) || HostSNI(`autodiscover.inkam.navy`)
|| HostSNI(`mail.inkam.parts`) || HostSNI(`autodiscover.inkam.parts`)
|| HostSNI(`mail.nt.parts`) || HostSNI(`autodiscover.nt.parts`)
|| HostSNI(`mail.ankil.ltd`) || HostSNI(`autodiscover.ankil.ltd`)
|| HostSNI(`mail.pda.ae`) || HostSNI(`autodiscover.pda.ae`)
|| HostSNI(`mail.nordtextile.ru`) || HostSNI(`autodiscover.nordtextile.ru`)
|| HostSNI(`mail.setto.ru`) || HostSNI(`autodiscover.setto.ru`)
|| HostSNI(`mail.smartdtl.ru`) || HostSNI(`autodiscover.smartdtl.ru`)
|| HostSNI(`mail.neviol.ru`) || HostSNI(`autodiscover.neviol.ru`)
|| HostSNI(`mail.alisailina.com`) || HostSNI(`autodiscover.alisailina.com`)
tls:
passthrough: true
service: exchange
services:
exchange:
loadBalancer:
servers:
- address: "192.168.0.6:443"
http:
# ===========================================================================
# Middlewares
# ===========================================================================
middlewares:
lan-only: # = NPM "Access List: allow 192.168.0.0/22; deny all"
ipAllowList:
sourceRange:
- "192.168.0.0/22"
dash-auth:
basicAuth:
usersFile: /etc/traefik/users/dashboard.htpasswd
redirect-https:
redirectScheme:
scheme: https
permanent: true
# Backends presenting self-signed certs (NPM forwarded with "verify SSL" off).
serversTransports:
insecure:
insecureSkipVerify: true
# ===========================================================================
# Routers on :80 (web)
# ===========================================================================
routers:
# --- Forward the ACME challenge for the Exchange names to Exchange itself. ---
# This is what makes win-acme's HTTP-01 SelfHosting validation on SERVERMAIL work
# WITHOUT the manual port-80 NAT repoint that every past renewal has needed.
# REQUIRES the edge NAT for :80 to point at this docker host (192.168.0.9).
# Priority must beat the catch-all redirect below.
exchange-acme:
entryPoints: [web]
priority: 1000
rule: >-
PathPrefix(`/.well-known/acme-challenge/`) && (
Host(`mail.shcnw.ru`) || Host(`autodiscover.shcnw.ru`)
|| Host(`mail.exprinta.ru`) || Host(`autodiscover.exprinta.ru`)
|| Host(`mail.inkam.navy`) || Host(`autodiscover.inkam.navy`)
|| Host(`mail.inkam.parts`) || Host(`autodiscover.inkam.parts`)
|| Host(`mail.nt.parts`) || Host(`autodiscover.nt.parts`)
|| Host(`mail.ankil.ltd`) || Host(`autodiscover.ankil.ltd`)
|| Host(`mail.pda.ae`) || Host(`autodiscover.pda.ae`)
|| Host(`mail.nordtextile.ru`) || Host(`autodiscover.nordtextile.ru`)
|| Host(`mail.setto.ru`) || Host(`autodiscover.setto.ru`)
|| Host(`mail.smartdtl.ru`) || Host(`autodiscover.smartdtl.ru`)
|| Host(`mail.neviol.ru`) || Host(`autodiscover.neviol.ru`)
|| Host(`mail.alisailina.com`) || Host(`autodiscover.alisailina.com`)
)
service: exchange-http
# --- Everything else on :80 redirects to https (replaces the entrypoint-level
# redirect, which would outrank exchange-acme). Lowest priority. ---
http-catchall:
entryPoints: [web]
priority: 1
rule: "PathPrefix(`/`)"
middlewares: [redirect-https]
service: noop
# =========================================================================
# Routers on :443 (websecure) — L7, Traefik terminates and manages the cert
# =========================================================================
crm:
entryPoints: [websecure]
rule: "Host(`crm.shcnw.ru`) || Host(`crm.inkam.navy`)"
service: crm
tls: { certResolver: le }
onec:
entryPoints: [websecure]
rule: "Host(`1c.exprinta.ru`)"
service: onec
tls: { certResolver: le }
# ---- LAN-only (internal admin) ----
start:
entryPoints: [websecure]
rule: "Host(`start.shcnw.ru`)"
service: flame
middlewares: [lan-only]
tls: { certResolver: le }
portainer:
entryPoints: [websecure]
rule: "Host(`portainer.shcnw.ru`)"
service: portainer
middlewares: [lan-only]
tls: { certResolver: le }
cockpit:
entryPoints: [websecure]
rule: "Host(`cockpit.shcnw.ru`)"
service: cockpit
middlewares: [lan-only]
tls: { certResolver: le }
# ---- Traefik dashboard (LAN-only + basic-auth) ----
# Needs a PUBLIC A-record even though access is LAN-only, because ACME validates
# from the internet. traefik.shcnw.ru -> 217.15.22.194 created 2026-08-26.
dashboard:
entryPoints: [websecure]
rule: "Host(`traefik.shcnw.ru`)"
service: api@internal
middlewares: [lan-only, dash-auth]
tls: { certResolver: le }
# ===========================================================================
# Services (backends) — mirror the NPM proxy-host forward targets
# ===========================================================================
services:
exchange-http: # :80 ACME challenge forward only (see exchange-acme)
loadBalancer:
servers: [{ url: "http://192.168.0.6:80" }]
crm: # Bitrix — self-signed cert (CN=Bitrix, 2023→2033)
loadBalancer:
serversTransport: insecure
servers: [{ url: "https://192.168.0.10:443" }]
onec: # Apache/2.4.23 (Win64), plain HTTP
loadBalancer:
servers: [{ url: "http://192.168.0.4:80" }]
flame: # sibling container on reverseproxy-nw
loadBalancer:
servers: [{ url: "http://flame:5005" }]
portainer:
loadBalancer:
serversTransport: insecure
servers: [{ url: "https://192.168.0.9:9443" }]
cockpit:
loadBalancer:
serversTransport: insecure
servers: [{ url: "https://192.168.0.9:9090" }]
noop: # never reached — http-catchall always redirects first
loadBalancer:
servers: [{ url: "http://127.0.0.1:1" }]