Writes use only <ID>_ADMIN_TOKEN and need the apps.write capability. Store URLs are resolved from each server's own listing. The config is saved to history before an uninstall. Zip uploads are fanned out to the servers. Every target's result is audited. The console gets a row menu on the matrix, a target picker, a results dialog with Retry failed, the store and install from file in All servers mode, and single-server actions for writers. Co-Authored-By: Claude Opus 5.5 <[email protected]>
222 lines
9.5 KiB
YAML
222 lines
9.5 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"
|
|
- "[email protected]"
|
|
- "--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
|
|
- 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:
|