- C# 54%
- Vue 32.3%
- TypeScript 5.5%
- Shell 1.8%
- Go 1.7%
- Other 4.7%
|
All checks were successful
build-push / build (./deploy/anubis-fork, deploy/anubis-fork/Dockerfile, anubis) (push) Successful in 34s
build-push / build (./deploy/caddy, deploy/caddy/Dockerfile, caddy) (push) Successful in 22s
build-push / build (., src/CaviCode.Dns.Api/Dockerfile, api) (push) Successful in 1m4s
build-push / build (./deploy/powerdns, deploy/powerdns/Dockerfile, powerdns) (push) Successful in 32s
build-push / build (./src/CaviCode.Dns.Web, src/CaviCode.Dns.Web/Dockerfile, web) (push) Successful in 1m9s
|
||
|---|---|---|
| .config | ||
| .forgejo/workflows | ||
| deploy | ||
| docs | ||
| scripts | ||
| src | ||
| tests | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| CaviCode.Dns.sln | ||
| Changelog.md | ||
| docker-compose.override.example.yml | ||
| docker-compose.yml | ||
| README.md | ||
CaviCode-DNS
Self-hosted панель управления инфраструктурой: DNS, reverse-proxy, Minecraft-прокси и защита от ботов. Замена Cloudflare для одного сервера с одним публичным IP.
UI на Vue 3 закрывает CRUD для DNS-зон, RRset'ов, Reverse Proxy,
Minecraft-маршрутов, статистику Anubis, бэкапы / Audit Log и получает
live-обновления через SignalR-канал /hubs/live
(см. src/CaviCode.Dns.Web/README.md).
Короткий обзор проекта, функций и технологий: docs/project-overview.md.
Брендовые ассеты (палитры, маскоты, логотип, шрифты) скопированы в
src/CaviCode.Dns.Web/src/assets/branding, а публичные страницы Caddy и
Anubis держат свои runtime-копии в deploy/.
Архитектура
┌─────────────┐
53/UDP, 53/TCP ── publicly ──▶ │ PowerDNS │ ◀── HTTP API 8081
│ (gpgsql) │ (Headscale only)
└─────┬───────┘
▲
│ REST + X-API-Key
┌─────┴───────┐
80, 443 ────── publicly ────▶ │ Caddy │ ◀── admin 2019
│ +powerdns │ (Headscale only)
│ ACME │
└─────┬───────┘
▲
│ admin-API (JSON)
┌─────┴───────┐
│ api │ ◀── 5000
│ (ASP.NET 9) │ (Headscale only)
└─────┬───────┘
25565/TCP ──── publicly ────▶ ┌─────────────┐
│ Infrared │ ← proxies/*.yml (rw vol,
└─────┬───────┘ пишется backend'ом)
│
┌─────┴───────┐
│ Anubis │ ◀── REST 8924
│ (форк) │ (metrics 8923, REST 8924)
└─────┬───────┘
│
┌─────┴───────┐
│ PostgreSQL │ схемы pdns + panel
└─────────────┘
Управляющие порты (PowerDNS API 8081, Caddy admin 2019, api 5000,
Anubis metrics 8923, Anubis REST 8924) биндятся только на интерфейсе
Headscale-сети (HEADSCALE_INTERFACE_IP). Снаружи доступны только
53, 80, 443, 25565.
Карта портов
Полный список портов, которые открывает стек:
Публичные (на всех интерфейсах, доступны из Интернета)
| Порт | Сервис | За что отвечает |
|---|---|---|
| 53/UDP | PowerDNS | Authoritative DNS. Сюда стучатся резолверы со всего мира за записями наших зон |
| 53/TCP | PowerDNS | Тот же DNS, но TCP — для ответов >512 байт и AXFR-передач |
| 80/TCP | Caddy | HTTP. Редирект на 443 + landing-страница для припаркованных доменов |
| 443/TCP | Caddy | HTTPS. Все настроенные через панель сайты + UI самой панели |
| 443/UDP | Caddy | HTTP/3 (QUIC). Альтернатива TCP/443 для современных браузеров |
| 25565/TCP | Infrared | Minecraft-прокси. Единая точка входа для всех MC-серверов, маршрутизация по hostname |
Приватные (биндятся только на ${HEADSCALE_INTERFACE_IP})
Доступны только из Headscale-оверлейной сети. Снаружи закрыты — nmap с публичного IP их не увидит.
| Порт | Сервис | За что отвечает |
|---|---|---|
| 8081/TCP | PowerDNS HTTP API | CRUD зон/записей. Используется backend'ом панели. Защищён X-API-Key (PDNS_API_KEY) |
| 2019/TCP | Caddy admin API | Динамическая конфигурация прокси-сайтов. Используется backend'ом панели. Без auth — защита по сети |
| 8923/TCP | Anubis metrics | Prometheus-метрики upstream-anubis (PoW counters, latency). Сейчас не используются панелью, но видны для grafana |
| 8924/TCP | Anubis REST/API gateway | Side-car cavicode-api: /api/health, /api/stats, /api/reload-acl + Caddy forward_auth gateway |
| 5000/TCP | api (ASP.NET Core) | REST API панели + SignalR-хаб /hubs/live. Снаружи доступен только через Caddy reverse-proxy |
| 8080/TCP | web (nginx + SPA) | Vue 3 фронт. Снаружи попадает через Caddy. Доступен напрямую внутри сети для отладки |
Внутренние (только Docker-сеть internal, не expose'ятся даже на хост)
Порты, которыми сервисы общаются между собой через internal Docker network. На хост не выставлены — обратиться к ним можно только из других контейнеров стека.
| Порт | Сервис | За что отвечает |
|---|---|---|
| 5432/TCP | postgres | PostgreSQL. Две схемы: pdns (для PowerDNS) и panel (для backend'а) |
| 8080/TCP | anubis | Internal auth-check upstream внутри anubis-контейнера. Caddy ходит в sidecar anubis:8924, а sidecar проксирует unknown IP на 127.0.0.1:8080 |
Что нужно открыть на firewall'е публичного сервера
inbound: 53/udp, 53/tcp, 80/tcp, 443/tcp, 443/udp, 25565/tcp
outbound: всё (нужно для PowerDNS NOTIFY и Caddy ACME-DNS-01)
Headscale-интерфейс firewall'ом обычно не ограничивают — он уже изолирован сетевой топологией оверлея.
Live-обновления
UI получает события доменных изменений через SignalR-хаб /hubs/live
(WebSocket-соединение проксируется через Caddy → api:5000). События:
| Событие | Когда срабатывает |
|---|---|
ZoneUpdated |
создание / удаление зоны, upsert/delete RRset, restore-бэкап |
ProxyUpdated |
CRUD прокси-сайта |
CertRenewed |
завершён cert-renewal через Caddy admin API |
McStatusChanged |
CRUD MC-маршрута, обновлён статус через ping |
AnubisAclChanged |
добавлена/удалена запись в whitelist/blacklist |
AnubisChallengeCompleted |
агрегированный апдейт статистики (с тротлингом) |
BackupCompleted |
создан manual или scheduled бэкап (success/failure) |
Контракты в src/CaviCode.Dns.Contracts/Live/LiveEvents.cs; клиент в src/CaviCode.Dns.Web/src/api/liveHub.ts. Доставка best-effort — ошибки публикации логируются и не откатывают бизнес-операцию.
Health-check
GET /api/v1/health параллельно проверяет 5 зависимостей: database,
powerdns, caddy, anubis, infrared. Возвращает 200 OK при всех
healthy, 503 при любой деградации. Эндпоинт требует обычную panel session
или external integration token со scope health:read.
External Integration Tokens
В Settings → External integration tokens администратор создаёт scoped bearer
tokens для внешних сервисов, например CaviCode-Tunnels. Plain token показывается
только один раз при create/rotate; в БД хранится hash и prefix.
Token может иметь IP/CIDR allowlist, expiry или infinite lifetime, scopes и per-token rate limit. Для CaviCode-Tunnels обычно нужны scopes:
health:readproxy:readproxy:writeminecraft:readminecraft:writedns:readdns:create
External token допускается только к ограниченной поверхности:
GET /api/v1/healthGET/POST/PUT /api/v1/proxy/sites...при соответствующем proxy scopeGET/POST/PUT /api/v1/mc/routes...при соответствующем Minecraft scopeGET/POST/PUT/DELETE /api/v1/zones...при соответствующем DNS scope
Users, settings, audit, backups, auth и destructive route operations вроде proxy/MC delete/reload/renew остаются доступны только обычной panel session.
Security tests cover token creation without plain persistence, expiry, revoke, rotate invalidation, IP allowlist, rate limiting and scope-denied permission flows used by CaviCode-Tunnels.
Темизация и язык
- Темы панели:
DemonesEngineerиSleepyPrincessизsrc/CaviCode.Dns.Web/src/assets/branding/themes/. Выбор сохраняется вlocalStorageс ключомcavicode-dns.themeи переключается в TopBar или вSettings → Внешний вид. - Публичные темы: в
Settings → Публичные страницыадмин выбирает отдельную тему для Anubis challenge/error UI и для страниц Caddy о парковке/служебных ошибках. Настройка хранится вpanel_settings(public_pages.theme), а API генерирует CSS/JSON вdeploy/public-theme/. - Языки:
ru(по умолчанию) иen. Стандартные страницы (setup, login, 2FA, TopBar) полностью локализованы; остальные views пока на русском, переводы добавляются по мере правок. Auto-detect черезnavigator.languageпри первом заходе. Переключатель в TopBar (dropdown «RU/EN») и в шаге 1 setup-wizard'а.
Тема панели и язык сохраняются в localStorage per-browser; публичные
темы сохраняются в БД и применяются ко всему стеку.
CI/CD
Сборка и публикация образов в Forgejo Container Registry автоматизирована
через Forgejo Actions (.forgejo/workflows/build-push.yml):
- Триггеры: push git-тега
v*(релиз) или ручнойworkflow_dispatch. - Matrix-сборка пяти образов параллельно:
powerdns,caddy,anubis,api,web. Тегаются как:<version>+:latest+:buildcache(для registry-cache следующих сборок). - На проде:
docker compose pull && docker compose up -dтянет готовые образы, не пересобирает.
Подробнее — docs/deployment-registry.md.
Быстрый старт
1. Предварительные требования
- Docker 24+ и Docker Compose v2.
- Headscale-сеть настроена и активна (для управляющих API).
- Первый администратор создаётся через UI-wizard на
/setupпри первом заходе — никаких секретов в.envне нужно. - Зарегистрированный домен с прописанными NS и glue records (см. docs/deployment.md).
- Статический белый IP.
2. Подготовка окружения
# Клонировать репозиторий
git clone <repo-url> CaviCode-DNS
cd CaviCode-DNS
# Заполнить .env (пароли, API-ключи, домен)
cp .env.example .env
$EDITOR .env
# Сгенерировать секреты:
# PDNS_API_KEY — openssl rand -hex 32
# POSTGRES_*_PASSWORD — openssl rand -base64 24
# Первый admin создаётся через UI-wizard при первом открытии браузера
# на /setup — никаких пароль-в-plain в .env держать не нужно.
# Опционально: подкрутить пути / отладочные настройки
cp docker-compose.override.example.yml docker-compose.override.yml
$EDITOR docker-compose.override.yml
3. Скачать схему PowerDNS
PowerDNS-схема не лежит в репо — берём из upstream. У PowerDNS нет
плавающей ветки auth-4.9.x, есть только конкретные теги
(auth-4.9.0 … auth-4.9.14); подставляем последний из 4.9-серии,
который совпадает с версией образа powerdns/pdns-auth-49:
curl -fsSL \
https://raw.githubusercontent.com/PowerDNS/pdns/auth-4.9.14/modules/gpgsqlbackend/schema.pgsql.sql \
-o deploy/postgres/pdns-schema.sql
# проверка — должно быть несколько KB SQL'я, не пустой файл и не HTML с 404
ls -lh deploy/postgres/pdns-schema.sql
head -3 deploy/postgres/pdns-schema.sql
Свежие теги: git ls-remote --tags https://github.com/PowerDNS/pdns 'auth-4.9.*'.
4. Поднять стек
# Сначала БД — она инициализируется первой и подтягивает схемы.
docker compose up -d postgres
# Дождаться, пока postgres станет healthy.
docker compose ps postgres
# Поднимаем остальное (PowerDNS, Caddy, Infrared).
docker compose up -d
Альтернативно — собрать образы у себя на dev-машине и тянуть из своего OCI-реестра (например, Forgejo Container Registry на
git.dsvinka.ru). Тогда на проде нужны толькоdocker-compose.yml+.env+docker compose pull. См. docs/deployment-registry.md.
5. Первый вход в админ-панель
После старта панель доступна только из Headscale-сети на
http://${HEADSCALE_INTERFACE_IP}:8080/. Снаружи — никак, и это
сознательный дефолт: админка не должна торчать в публичный интернет
без явного намерения оператора.
Если временно нет Headscale-клиента — пробрось ssh-туннель:
ssh -L 8080:${HEADSCALE_INTERFACE_IP}:8080 user@server
# затем в браузере → http://localhost:8080/
Setup wizard
При первом заходе фронтенд видит, что в БД нет ни одного пользователя,
и редиректит на /setup — мастер первичной настройки на 5 шагов:
- Внешний вид — выбор темы (
DemonesEngineer/SleepyPrincess) и языка (ru / en). Применяется мгновенно, дальнейшие шаги уже на выбранном языке. - Создание администратора — email, имя, пароль (≥10 символов). После submit cookie выдаётся сразу, дальнейшие шаги уже под этим аккаунтом.
- Двухфакторная аутентификация (опционально) — QR для Google Authenticator / Authy / 1Password / Яндекс.Ключ + 10 recovery-кодов. Можно пропустить и включить позже в Settings.
- Первая DNS-зона (опционально) — имя зоны (
example.com) и NS-записи (заполняются автоматом из имени зоны). - Публичный URL панели (опционально) — hostname (
panel.example.com) и публичный IP сервера (определяется через whoami-сервис, можно изменить вручную). Создаётся сайт в Caddy с автоматическим TLS; если на шаге 4 создавалась зона и hostname в неё попадает — добавится A-запись, иначе wizard покажет warning, что DNS-запись надо завести вручную (или панель открывается через домен из стороннего DNS).
После завершения — переход в Dashboard.
⚠️ Wizard виден, только пока БД пуста. После создания первого администратора endpoint
/api/v1/setup/adminвозвращает 409 и редирект происходит на/login. Пройти повторно не получится — это by design.
(Опционально) Публичный домен для панели
Если хочется заходить в админку из обычного браузера — нужно добавить
сайт panel.example.com → http://web:8080 в Caddy. Делается либо
через UI самой панели после первого входа (раздел Reverse Proxy →
добавить сайт с upstream http://web:8080), либо одной curl-командой
из Headscale-сети:
curl -X POST http://${HEADSCALE_INTERFACE_IP}:2019/config/apps/http/servers/panel/routes \
-H "Content-Type: application/json" \
-d '{
"match": [{"host": ["panel.example.com"]}],
"handle": [{
"handler": "subroute",
"routes": [{
"handle": [{
"handler": "reverse_proxy",
"upstreams": [{"dial": "web:8080"}]
}]
}]
}]
}'
Перед этим в PowerDNS должна быть запись panel.example.com IN A <PUBLIC_IP> —
её можно добавить через UI панели после первого входа изнутри.
⚠️ Если открываешь панель в публичный интернет — обязательно включи 2FA
(шаг 2 wizard'а), и используй длинный пароль (≥16 символов) для админа. Брутфорс
поймает fail2ban (см. FAIL2BAN_* в .env.example), но первый барьер —
длинный пароль.
6. Проверка инфраструктуры
См. подробный чек-лист в docs/deployment.md. Краткий smoke-test:
# DNS отвечает на запросы
dig @127.0.0.1 example.com SOA
# Caddy admin API отвечает (только из Headscale-сети)
curl http://${HEADSCALE_INTERFACE_IP}:2019/config/
# PowerDNS API отвечает
curl -H "X-API-Key: ${PDNS_API_KEY}" \
http://${HEADSCALE_INTERFACE_IP}:8081/api/v1/servers/localhost/zones
# Infrared слушает MC-клиента
nc -zv 127.0.0.1 25565
# Health-check панели — все 5 проверок healthy
curl http://${HEADSCALE_INTERFACE_IP}:5000/api/v1/health | jq
# UI панели — изнутри Headscale-сети
curl -I http://${HEADSCALE_INTERFACE_IP}:8080/
7. Локальная разработка фронтенда (опционально)
cd src/CaviCode.Dns.Web
npm install
npm run dev # http://localhost:5173, проксирует /api → http://localhost:5000
Подробнее — в src/CaviCode.Dns.Web/README.md.
Структура проекта
CaviCode-DNS/
├── .forgejo/workflows/ — Forgejo Actions: matrix-сборка 5 образов
├── docker-compose.yml — инфраструктура + api
├── docker-compose.override.example.yml
├── .env.example — переменные окружения
├── CaviCode.Dns.sln — solution с 5 src + 3 test проектами
├── CLAUDE.md — заметки для Claude Code (архитектура + gotchas)
├── deploy/
│ ├── postgres/ — init.sh + pdns-schema-init compose-сервис
│ ├── powerdns/ — Dockerfile + pdns.conf.template
│ ├── caddy/ — Dockerfile + Caddyfile.bootstrap + landing/
│ ├── public-theme/ — CSS/JSON выбранных публичных тем
│ ├── infrared/
│ │ ├── config.yml
│ │ └── proxies/ — YAML-маршруты, пишутся панелью (rw bind-mount)
│ └── anubis-fork/ — overlay-форк Anubis + cavicode-api sidecar
├── src/
│ ├── CaviCode.Dns.Domain/ — сущности и enum'ы (POCO)
│ ├── CaviCode.Dns.Contracts/ — DTO для REST + SignalR-события
│ ├── CaviCode.Dns.Application/ — бизнес-логика (Auth, Setup, Dns, Proxy, ...)
│ ├── CaviCode.Dns.Infrastructure/ — EF Core + HTTP-клиенты PowerDNS / Caddy / Anubis
│ ├── CaviCode.Dns.Api/ — ASP.NET Core endpoints + cookie auth + 2FA + SignalR
│ └── CaviCode.Dns.Web/ — Vue 3 SPA + i18n + брендовые ассеты
├── tests/
│ ├── CaviCode.Dns.Application.Tests/ — ~73 теста (services, fail2ban, totp, setup)
│ ├── CaviCode.Dns.Infrastructure.Tests/ — ~21 (HTTP-клиенты, infrared)
│ └── CaviCode.Dns.Api.Tests/ — SignalR publisher
├── docs/
│ ├── project-overview.md — краткое описание проекта и технологий
│ ├── deployment.md — пошаговая установка + smoke-test
│ └── deployment-registry.md — CI/CD через Forgejo Actions
└── scripts/
├── backup-zones.sh — ручной экспорт зон в tar.gz
├── restore-zone.sh — ручной импорт zonefile
└── build-and-push.sh — ручная сборка/публикация (альтернатива CI)
Лицензия и происхождение
Форк Anubis (deploy/anubis-fork/) использует upstream-проект
TecharoHQ/anubis под MIT-лицензией.
Подробности модификаций — в deploy/anubis-fork/CHANGELOG.md.