Files
technitium-console/docs/hub-deploy.md
T
DenozordecandClaude Opus 5.5 0ef2d53a65 Hub stage 0: own users with roles, login limit, audit log, permission table
Users in HUB_DATA/users.json with scrypt hashes and roles (viewer, operator,
apps-admin, admin), managed by hub/users-cli.ts; HUB_USER/HUB_PASSWORD remain a
bootstrap admin while the file does not exist. Five failed logins in 15 minutes lock
the address or the name for 15 minutes. Logins, logouts and every refused request go
to a JSON Lines audit log that admins read through hub/audit/list. Endpoints are
listed in routes.ts with their kind and capability; writes stay refused for now.
Optional <ID>_ADMIN_TOKEN is read for the coming write stages. The management plan
is in docs/hub-management-plan.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 15:25:38 +07:00

21 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). Нужен, пока не создан файл пользователей (шаг 4a)
HUB_USER=admin
HUB_PASSWORD=длинный_случайный_пароль

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

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_DATA=/data
      - HUB_TRUST_PROXY=1
      - HUB_DOMAIN=dnss.shnt.top
      - TZ=Asia/Novosibirsk
    volumes:
      - technitium-hub-data:/data
    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"

Том technitium-hub-data нужно объявить в разделе volumes: того же файла:

volumes:
  technitium-hub-data:

В нём лежат файл пользователей users.json и журнал audit.log. HUB_TRUST_PROXY=1 нужен за Traefik: хаб берёт адрес клиента из заголовка X-Forwarded-For для ограничения попыток входа и журнала. Если до хаба можно достучаться в обход Traefik, включать эту переменную нельзя: заголовок можно подделать.

Если хаб стоит в том же файле, что 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), отдельный порт не нужен.


Шаг 4a. Пользователи хаба

У хаба свои пользователи с ролями. Они лежат в /data/users.json, пароли в виде хешей.

Роль Что может
viewer смотреть
operator смотреть, блокировка и кеш (после появления записи)
apps-admin смотреть, управлять приложениями (после появления записи)
admin всё, включая журнал действий

Пока файла нет, в хаб входит один admin из HUB_USER / HUB_PASSWORD. Когда файл появился, эти две переменные игнорируются (в логе будет строка об этом). Если нет ни файла, ни переменных, хаб не запустится.

После запуска (шаг 5) создайте пользователей. Пароль команда спросит дважды, при вводе он не показывается:

docker exec -it technitium-hub node --experimental-strip-types /app/hub/users-cli.ts add denis admin
docker exec -it technitium-hub node --experimental-strip-types /app/hub/users-cli.ts add alice viewer

Остальные команды: passwd <имя>, role <имя> <роль>, disable <имя>, enable <имя>, list. Хаб замечает изменение файла на следующем запросе и завершает сессии затронутых пользователей. Перезапуск не нужен.

После создания своего admin уберите HUB_USER и HUB_PASSWORD из .env и compose, затем docker compose up -d technitium-hub.

Журнал: войдите админом и откройте /api/hub/audit/list?limit=200 (JSON, новые записи первыми) или смотрите файл docker exec technitium-hub tail -n 50 /data/audit.log. Пароли и токены в журнал не пишутся.


Шаг 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 нужны, пока нет users.json Первый admin; при наличии файла игнорируются
HUB_DATA /data Папка users.json и audit.log (на ней том)
HUB_TRUST_PROXY выключено 1: адрес клиента из X-Forwarded-For (только за Traefik)
<ID>_ADMIN_TOKEN нет Токен записи сервера (MDNS_ADMIN_TOKEN). Пока не используется, в логи не пишется
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» Неверный логин или пароль. Если есть users.json, работают только его пользователи (users-cli.ts list), а HUB_USER игнорируется. После правки .env нужен docker compose up -d technitium-hub.
«Too many failed logins. Try again in N minutes.» 5 неудач за 15 минут с одного адреса или на одно имя. Подождите; блокировка хранится в памяти, её снимает перезапуск контейнера.
Все пользователи блокируются разом Не включён HUB_TRUST_PROXY=1: хаб видит только адрес Traefik.
Контейнер пишет audit log is not writable Нет тома на /data или папка не принадлежит node. Журнал живёт в памяти (последние 1000 записей) до исправления.
В шапке «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 на хосте и в памяти контейнера. В браузер они не попадают, в ответы и логи хаба не пишутся.
  • У каждого человека свой логин и роль. Вход защищён блокировкой после 5 неудач. Нужны длинные пароли, а если хаб не должен быть виден из интернета, добавьте к роутеру hub в Traefik ipallowlist или basicAuth.