Files
technitium-console/docs/compose.shnt.example.yaml
DenozordecandClaude Opus 5.5 57e492cceb Hub stage 4: config history, rollback, drift check, baseline export and import
Every saved config, and every earlier baseline, is listed per app. You
can view it, diff it against a server's current config, and roll it back
through preview and apply. Earlier baselines can be restored.

A drift check (HUB_DRIFT_MINUTES, default 15) compares each server with
its effective config. Drift shows in the header and the matrix, with
Accept as baseline and Revert to baseline. All baselines export to one
file. Importing that file shows a dry run before it writes.

Rolling back preselects, and diffs against, the server the version came
from. The line-diff op "del" is renamed to "remove" for the language lint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 19:57:33 +07:00

224 lines
9.7 KiB
YAML

version: "3.8"
services:
traefik:
image: traefik:v3
container_name: traefik
restart: unless-stopped
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=dns-net"
- "--providers.file.directory=/etc/traefik/dynamic"
- "--providers.file.watch=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.websecure.http3"
- "--entrypoints.dot.address=:853"
- "--entrypoints.doq.address=:853/udp"
- "--entrypoints.cluster.address=:53443"
- "--certificatesresolvers.myresolver.acme.dnschallenge=true"
- "--certificatesresolvers.myresolver.acme.dnschallenge.provider=cloudflare"
- "--certificatesresolvers.myresolver.acme.dnschallenge.resolvers=1.1.1.1:53,8.8.8.8:53"
- "--certificatesresolvers.myresolver.acme.dnschallenge.delaybeforecheck=60s"
- "--certificatesresolvers.myresolver.acme.email=admin@shnt.top"
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
environment:
- CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
ports:
- target: 80
published: 80
protocol: tcp
mode: host
- target: 443
published: 443
protocol: tcp
mode: host
- target: 443
published: 443
protocol: udp
mode: host
- target: 853
published: 853
protocol: tcp
mode: host
- target: 853
published: 853
protocol: udp
mode: host
- target: 53443
published: 53443
protocol: tcp
mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
- ./traefik/dynamic:/etc/traefik/dynamic:ro
networks:
- dns-net
technitium-smart:
image: technitium/dns-server:latest
container_name: technitium-smart
restart: unless-stopped
networks:
- dns-net
cap_add:
- NET_ADMIN
- NET_RAW
- NET_BIND_SERVICE
environment:
- DNS_SERVER_ADMIN_PASSWORD=${DNS_SERVER_ADMIN_PASSWORD}
- DNS_SERVER_ENABLE_DNS_OVER_HTTP=true
- DNS_SERVER_ENABLE_DNS_OVER_TLS=false
- DNS_SERVER_WEB_SERVICE_ENABLE_HTTPS=true
- DNS_SERVER_WEB_SERVICE_HTTPS_PORT=53443
- DNS_SERVER_WEB_SERVICE_USE_SELF_SIGNED_CERT=true
# Новая консоль: папку со статикой отдаёт том technitium-console (нужен Technitium 15.5+)
- DNS_SERVER_WEB_SERVICE_WWW_FOLDER_PATH=/opt/technitium-console
- TZ=Asia/Novosibirsk
- ASPNETCORE_FORWARDEDHEADERS_ENABLED=true
- DOTNET_SYSTEM_NET_SOCKETS_INLINE_RECEIVE=1
volumes:
- technitium-data:/etc/dns
- technitium-console:/opt/technitium-console:ro
# AXFR зоны кластера (TLSA). Если 53 уже слушает не этот контейнер, удалите блок.
# Файрволом пускайте TCP/53 только с IP соседних нод.
# ports:
# - target: 53
# published: 53
# protocol: tcp
# mode: host
# - target: 53
# published: 53
# protocol: udp
# mode: host
labels:
- "traefik.enable=true"
- "traefik.docker.network=dns-net"
- "traefik.http.middlewares.old-panel-headers.headers.customrequestheaders.X-Forwarded-Proto=https"
- "traefik.http.middlewares.old-panel-headers.headers.customrequestheaders.X-Forwarded-Host=old.dns.shnt.top"
# DoH
- "traefik.http.routers.doh.rule=(Host(`dns.shnt.top`) || HostRegexp(`^[a-z0-9-]+\\.dns\\.shnt\\.top$`) || Host(`ndns.shnt.top`) || HostRegexp(`^[a-z0-9-]+\\.ndns\\.shnt\\.top$`)) && PathPrefix(`/dns-query`)"
- "traefik.http.routers.doh.entrypoints=websecure"
- "traefik.http.routers.doh.tls.certresolver=myresolver"
- "traefik.http.routers.doh.tls.domains[0].main=dns.shnt.top"
- "traefik.http.routers.doh.tls.domains[0].sans=*.dns.shnt.top"
- "traefik.http.routers.doh.tls.domains[1].main=ndns.shnt.top"
- "traefik.http.routers.doh.tls.domains[1].sans=*.ndns.shnt.top"
- "traefik.http.routers.doh.priority=1000"
- "traefik.http.routers.doh.service=doh-svc"
- "traefik.http.services.doh-svc.loadbalancer.server.port=8080"
- "traefik.http.services.doh-svc.loadbalancer.server.scheme=http"
# Панель
- "traefik.http.routers.panel-old.rule=Host(`dns.shnt.top`) || Host(`old.dns.shnt.top`) || Host(`ndns.shnt.top`) || HostRegexp(`^[a-z0-9-]+\\.ndns\\.shnt\\.top$`)"
- "traefik.http.routers.panel-old.entrypoints=websecure"
- "traefik.http.routers.panel-old.tls.certresolver=myresolver"
- "traefik.http.routers.panel-old.tls.domains[0].main=dns.shnt.top"
- "traefik.http.routers.panel-old.tls.domains[0].sans=*.dns.shnt.top"
- "traefik.http.routers.panel-old.tls.domains[1].main=ndns.shnt.top"
- "traefik.http.routers.panel-old.tls.domains[1].sans=*.ndns.shnt.top"
- "traefik.http.routers.panel-old.priority=10"
- "traefik.http.routers.panel-old.middlewares=old-panel-headers"
- "traefik.http.routers.panel-old.service=panel-old-svc"
- "traefik.http.services.panel-old-svc.loadbalancer.server.port=5380"
# DoT
- "traefik.tcp.routers.dot.rule=HostSNI(`dns.shnt.top`) || HostSNIRegexp(`^.+\\.dns\\.shnt\\.top$`) || HostSNI(`ndns.shnt.top`) || HostSNIRegexp(`^.+\\.ndns\\.shnt\\.top$`)"
- "traefik.tcp.routers.dot.entrypoints=dot"
- "traefik.tcp.routers.dot.tls=true"
- "traefik.tcp.routers.dot.tls.certresolver=myresolver"
- "traefik.tcp.routers.dot.tls.domains[0].main=dns.shnt.top"
- "traefik.tcp.routers.dot.tls.domains[0].sans=*.dns.shnt.top"
- "traefik.tcp.routers.dot.tls.domains[1].main=ndns.shnt.top"
- "traefik.tcp.routers.dot.tls.domains[1].sans=*.ndns.shnt.top"
- "traefik.tcp.routers.dot.tls.options=dot@file"
- "traefik.tcp.routers.dot.service=dot-svc"
- "traefik.tcp.services.dot-svc.loadbalancer.server.port=538"
- "traefik.tcp.services.dot-svc.loadbalancer.proxyprotocol.version=2"
# DoQ
- "traefik.udp.routers.doq.entrypoints=doq"
- "traefik.udp.routers.doq.service=doq-svc"
- "traefik.udp.services.doq-svc.loadbalancer.server.port=8530"
# Кластер: TLS passthrough на Technitium :53443 (без certresolver и Proxy Protocol)
- "traefik.tcp.routers.cluster.rule=HostSNI(`ndns.shnt.top`) || HostSNIRegexp(`^.+\\.ndns\\.shnt\\.top$`) || HostSNI(`dns.shnt.top`) || HostSNIRegexp(`^.+\\.dns\\.shnt\\.top$`)"
- "traefik.tcp.routers.cluster.entrypoints=cluster"
- "traefik.tcp.routers.cluster.tls.passthrough=true"
- "traefik.tcp.routers.cluster.service=cluster-svc"
- "traefik.tcp.services.cluster-svc.loadbalancer.server.port=53443"
# Копирует новую консоль в том technitium-console и завершается.
# Без labels Traefik: наружу этот контейнер ничего не отдаёт.
# Обновление: docker compose pull technitium-console && docker compose up -d technitium-console
technitium-console:
image: git.shx.one/denozord/technitium-console:latest
container_name: technitium-console
restart: "no"
volumes:
- technitium-console:/target
networks:
- dns-net
# Хаб: одна консоль со статистикой обоих серверов на https://dnss.shnt.top
# (docs/hub-deploy.md). Только чтение. Токены и вход хаба берутся из .env:
# MDNS_TOKEN, NDNS_TOKEN (пользователь hub-reader на каждом сервере), HUB_USER, HUB_PASSWORD.
# Обновление: docker compose pull technitium-hub && docker compose up -d technitium-hub
technitium-hub:
image: git.shx.one/denozord/technitium-hub:latest
container_name: technitium-hub
restart: unless-stopped
networks:
- dns-net
volumes:
- technitium-hub-data:/data
environment:
# id=адрес по порядку; токен каждого из <ID>_TOKEN. Сервер на ЭТОМ же хосте можно
# указать изнутри сети, мимо Traefik: ndns=http://technitium-smart:5380
- HUB_SERVERS=mdns=https://mdns.shnt.top,ndns=https://ndns.shnt.top
- MDNS_TOKEN=${MDNS_TOKEN}
- NDNS_TOKEN=${NDNS_TOKEN}
# Токены hub-admin: без них хаб только читает. Нужны для установки и обновления приложений
- MDNS_ADMIN_TOKEN=${MDNS_ADMIN_TOKEN}
- NDNS_ADMIN_TOKEN=${NDNS_ADMIN_TOKEN}
# Только пока нет /data/users.json; потом пользователи создаются через users-cli (docs/hub-deploy.md)
- HUB_USER=${HUB_USER}
- HUB_PASSWORD=${HUB_PASSWORD}
- HUB_DATA=/data
# За Traefik: брать адрес клиента из X-Forwarded-For (для ограничения попыток входа и журнала)
- HUB_TRUST_PROXY=1
# Как часто проверять, что конфиги серверов не разошлись с эталоном (минуты, 0 выключает)
# - HUB_DRIFT_MINUTES=15
- HUB_DOMAIN=dnss.shnt.top
- TZ=Asia/Novosibirsk
labels:
- "traefik.enable=true"
- "traefik.docker.network=dns-net"
- "traefik.http.routers.hub.rule=Host(`dnss.shnt.top`)"
- "traefik.http.routers.hub.entrypoints=websecure"
- "traefik.http.routers.hub.tls.certresolver=myresolver"
- "traefik.http.routers.hub.service=hub-svc"
- "traefik.http.services.hub-svc.loadbalancer.server.port=8080"
networks:
dns-net:
driver: bridge
name: dns-net
volumes:
technitium-data:
technitium-console:
technitium-hub-data: