Files
technitium-console/docs/hub-deploy.md
T
DenozordecandClaude Opus 5.5 042cb116ea
CI / check (push) Successful in 2m39s
Hub / check (push) Successful in 41s
CI / image (push) Successful in 2m11s
Hub / image (push) Successful in 1m48s
Hub docs: the two servers are mdns.shnt.top and ndns.shnt.top, the hub is on dnss.shnt.top
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-30 21:53:53 +07:00

16 KiB
Raw Blame History

Хаб: запуск по шагам

Хаб показывает статистику двух серверов Technitium DNS (mdns.shnt.top и ndns.shnt.top) в одной консоли. Это один маленький контейнер. Он отдаёт ту же консоль и сам ходит в API обоих серверов по их токенам:

  • запрос «All servers» хаб отправляет на оба сервера и складывает ответы;
  • запрос к одному серверу хаб пересылает только ему;
  • запись хаб не выполняет никогда.

Серверы Technitium не меняются, кластер не нужен. В инструкции хаб ставится на хост, где уже работают Traefik и technitium-smart (docs/compose.shnt.example.yaml). Адрес хаба: https://dnss.shnt.top.

браузер ─► Traefik (dnss.shnt.top, TLS) ─► technitium-hub:8080
                                             ├─► https://mdns.shnt.top/api/...   (MDNS_TOKEN)
                                             └─► https://ndns.shnt.top/api/...  (NDNS_TOKEN)

Что хаб показывает: Dashboard (сумма или один сервер), Logs (View Logs и Query Logs, объединённые с колонкой Server) и About. Остальные разделы скрыты, ими управляют в консоли каждого сервера.


Шаг 0. Образ хаба в реестре

Образ git.shx.one/denozord/technitium-hub собирает отдельный workflow .gitea/workflows/hub.yaml.

  1. Отправьте коммиты хаба на git.shx.one:
    git push origin main
    
  2. В репозитории на git.shx.one откройте Actions и дождитесь зелёного прогона Hub:
    • job check проверяет типы и тесты хаба;
    • job image собирает образ и кладёт его в реестр.
  3. Убедитесь, что образ появился: в репозитории или профиле откройте Packages, там должен быть technitium-hub с тегом latest.

Если job image упал на входе в реестр, проверьте секрет ACTION_PAT (или ACTIONS_PAT) в настройках репозитория: это токен Gitea с правом write:package. Он тот же, что использует образ консоли.

Выпуск версии с номером (необязательно):

git tag hub-v1.0.0 && git push origin hub-v1.0.0

Тег даёт образы 1.0.0 и 1.0 в дополнение к latest.


Шаг 1. Пользователь только для чтения на каждом сервере

Сделайте это на обоих серверах: https://mdns.shnt.top и https://ndns.shnt.top. Войдите в консоль сервера как администратор.

  1. Группа. Administration › Groups › Add Group: имя hub-readers, описание любое.

  2. Права группы. Administration › Permissions: у группы hub-readers отметьте только View в четырёх разделах:

    Раздел Зачем хабу
    Dashboard статистика, топы, график по часам, живые метрики
    Logs список файлов логов и Query Logs
    Apps список приложений логирования, из которых Query Logs читает записи
    Settings состояние блокировки на Dashboard

    Modify и Delete не отмечайте нигде. Остальные разделы оставьте без прав.

  3. Пользователь. Administration › Users › Add User: имя hub-reader, длинный случайный пароль. Затем в карточке пользователя добавьте его в группу hub-readers. Из группы Everyone можно убрать, если у неё есть лишние права.

  4. API-токен. Нужен токен именно для hub-reader, а не для администратора:

    • выйдите из консоли и войдите под hub-reader;
    • в меню пользователя внизу слева выберите Create API Token, имя токена hub;
    • скопируйте токен сразу: его показывают один раз.

    Можно и под администратором: Administration › Sessions › Create Token, пользователь hub-reader.

  5. Проверьте токен с любой машины. В ответе должно быть "status":"ok":

    curl -s "https://mdns.shnt.top/api/dashboard/stats/get?type=LastHour&token=ТОКЕН" | head -c 200
    
    • "status":"invalid-token" значит, что скопирован не тот токен;
    • "status":"error" с сообщением об отказе в доступе значит, что у группы не хватает права View.

Результат шага: два токена, MDNS_TOKEN (с mdns.shnt.top) и NDNS_TOKEN (с ndns.shnt.top).


Шаг 2. DNS-запись для dnss.shnt.top

В Cloudflare (зона shnt.top) создайте запись dnss типа A на IP хоста, где работает хаб.

Сертификат Traefik получит сам. В compose уже настроен certresolver=myresolver с DNS-challenge через Cloudflare (CF_DNS_API_TOKEN), отдельно открывать порт 80 для проверки не нужно.


Шаг 3. Файл .env

На хосте в каталоге с docker-compose.yml откройте .env (там уже лежат CF_DNS_API_TOKEN и DNS_SERVER_ADMIN_PASSWORD) и добавьте:

# Хаб: токены пользователей hub-reader с шага 1
MDNS_TOKEN=вставьте_токен_mdns
NDNS_TOKEN=вставьте_токен_ndns

# Вход в сам хаб (это НЕ логин Technitium)
HUB_USER=admin
HUB_PASSWORD=длинный_случайный_пароль

Пароль хаба нужен длинный: у хаба нет ограничения числа попыток входа. Сгенерировать можно так:

openssl rand -base64 24

Закройте .env от посторонних:

chmod 600 .env

Шаг 4. Сервис в compose

Добавьте в docker-compose.yml в раздел services: блок из docs/compose.hub.example.yaml:

  technitium-hub:
    image: git.shx.one/denozord/technitium-hub:latest
    container_name: technitium-hub
    restart: unless-stopped
    networks:
      - dns-net
    environment:
      # id=адрес, по порядку; токен каждого берётся из <ID>_TOKEN
      - HUB_SERVERS=mdns=https://mdns.shnt.top,ndns=https://ndns.shnt.top
      - MDNS_TOKEN=${MDNS_TOKEN}
      - NDNS_TOKEN=${NDNS_TOKEN}
      - HUB_USER=${HUB_USER}
      - HUB_PASSWORD=${HUB_PASSWORD}
      - 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"

Если хаб стоит в том же файле, что Traefik, отдельный блок networks: с external: true из примера не нужен: сеть dns-net в этом файле уже объявлена.

Про имена в HUB_SERVERS:

  • имя слева от = видно в консоли в селекторе «Server»;
  • из имени получается переменная токена: mdns даёт MDNS_TOKEN, ndns даёт NDNS_TOKEN, my-dns дал бы MY_DNS_TOKEN;
  • первый в списке сервер считается основным.

Адреса серверов указываются публичные (https://mdns.shnt.top). API Technitium доступно по тому же адресу, что и консоль (роутер panel-old в Traefik), отдельный порт не нужен.


Шаг 5. Запуск

Если реестр закрытый, один раз войдите в него. Нужен токен Gitea с правом read:package:

docker login git.shx.one

Скачайте образ и запустите хаб:

docker compose pull technitium-hub
docker compose up -d technitium-hub

Проверьте контейнер:

docker compose ps technitium-hub     # STATUS: Up ... (healthy)
docker compose logs -f technitium-hub

В логе при старте не должно быть ошибок конфигурации. Если чего-то не хватает, хаб не запустится и назовёт переменную, например NDNS_TOKEN is required for the server "ndns".


Шаг 6. Проверка

  1. Откройте https://dnss.shnt.top и войдите с HUB_USER / HUB_PASSWORD из .env.
  2. На Dashboard над заголовком должен быть селектор Server: All servers.
  3. Сверьте цифры. Total Queries в «All servers» должна равняться сумме значений в консолях mdns.shnt.top и ndns.shnt.top за тот же период. Расхождение в пределах минуты нормально: хаб кеширует ответы на 15 секунд, а Last Hour обновляется на ходу.
  4. Выберите в селекторе mdns, затем ndns: цифры каждого сервера должны совпасть с его собственной консолью.
  5. Откройте Logs › Query Logs, нажмите Query. Записи обоих серверов идут вместе, в колонке Server видно, чья запись.
  6. Кнопка статуса справа в шапке показывает dnss.shnt.top. Если какой-то сервер не ответил, там будет «1 server not answering», а по нажатию появится причина.

Проверка без браузера (подставьте пароль хаба):

T=$(curl -s -X POST https://dnss.shnt.top/api/user/login \
     --data-urlencode "user=admin" --data-urlencode "pass=ПАРОЛЬ_ХАБА" | sed -E 's/.*"token":"([^"]+)".*/\1/')
curl -s -H "Authorization: Bearer $T" "https://dnss.shnt.top/api/dashboard/stats/get?type=LastHour" | head -c 300
curl -s -H "Authorization: Bearer $T" "https://dnss.shnt.top/api/dashboard/stats/get?type=LastHour&node=ndns" | head -c 300

Обновление

После нового прогона Hub в Actions:

docker compose pull technitium-hub && docker compose up -d technitium-hub

Сессии хаба хранятся в памяти: после перезапуска контейнера нужно войти заново.

Чтобы закрепить версию, укажите в compose тег вместо latest, например technitium-hub:1.0.0.

Остановка и удаление

docker compose stop technitium-hub      # остановить
docker compose rm -f technitium-hub     # удалить контейнер

Чтобы отозвать доступ хаба к серверам, на каждом сервере удалите токен в Administration › Sessions или отключите пользователя hub-reader.


Настройки хаба

Переменная По умолчанию Что задаёт
HUB_SERVERS обязательна Серверы по порядку: id=https://адрес,...
<ID>_TOKEN обязательна для каждого API-токен сервера (MDNS_TOKEN, NDNS_TOKEN, ...)
HUB_USER, HUB_PASSWORD обязательны Вход в хаб
HUB_DOMAIN hub Имя, которое консоль показывает как имя сервера
HUB_CACHE_SECONDS 15 Сколько секунд хранить ответы серверов (0 выключает кеш)
HUB_TIMEOUT_MS 8000 Сколько ждать ответа сервера, мс
HUB_PORT 8080 Порт внутри контейнера

Третий сервер добавляется строкой в HUB_SERVERS и его токеном:

      - HUB_SERVERS=mdns=https://mdns.shnt.top,ndns=https://ndns.shnt.top,lab=https://lab.shnt.top
      - LAB_TOKEN=${LAB_TOKEN}

Если что-то не так

Признак Причина и что делать
Контейнер сразу завершается В docker compose logs technitium-hub будет названа недостающая переменная. Проверьте .env и что compose его видит (docker compose config | grep TOKEN).
https://dnss.shnt.top не открывается, ошибка сертификата Нет DNS-записи dnss (шаг 2) или Traefik ещё получает сертификат: смотрите docker logs traefik.
404 от Traefik Контейнер не в сети dns-net или опечатка в Host(\dnss.shnt.top`)`.
Вход в хаб: «Invalid username or password» Неверные HUB_USER/HUB_PASSWORD в .env. После правки .env нужен docker compose up -d technitium-hub.
В шапке «1 server not answering», в подсказке ndns: its API token was refused Токен NDNS_TOKEN отозван или скопирован с ошибкой. Создайте новый (шаг 1.4) и перезапустите хаб.
В подсказке ... did not answer: ... или ECONNREFUSED / timeout Хаб не достучался до сервера. Проверьте с хоста хаба: docker exec technitium-hub node -e "fetch('https://ndns.shnt.top/api/status').then(r=>console.log(r.status))".
В подсказке ndns: ... с сообщением об отказе в доступе У группы hub-readers на этом сервере не хватает View в каком-то разделе (шаг 1.2).
Query Logs пустой или «нет приложения» На сервере не установлено приложение логирования (Query Logs (Sqlite) и подобные) или нет права View на Apps.
Цифры «All servers» больше суммы на 1-2 клиента Clients считается по каждому серверу отдельно: клиент, который ходит на оба сервера, учтён дважды. Это ожидаемо.
Нажатие на что-то пишет «The hub is read-only.» Хаб только читает. Изменения делаются в консоли нужного сервера.

Безопасность

  • Хаб только читает: любая запись получает отказ ещё в хабе, а токены выданы пользователю с одними правами View.
  • Токены лежат только в .env на хосте и в памяти контейнера. В браузер они не попадают, в ответы и логи хаба не пишутся.
  • У хаба один общий логин и нет ограничения попыток входа. Нужен длинный пароль, а если хаб не должен быть виден из интернета, добавьте к роутеру hub в Traefik ipallowlist или basicAuth.