# Развёртывание CDN Manager + Traefik (один Docker Compose) Один стек: **Traefik** (HTTPS, Let's Encrypt DNS-01 через Cloudflare) + **cdnmanager** (API + SPA, SQLite). | | | |---|---| | Образ | `git.shx.one/denozord/cdnmanager` (алиас `cdn-manager`) | | Runtime | `node:22-alpine`, API + SPA + SQLite | | Публичный URL | `https://cdn.shnt.top` | | Compose | [`deploy/docker-compose.traefik.yml`](../deploy/docker-compose.traefik.yml) | | Env-шаблон | [`deploy/env.traefik.example`](../deploy/env.traefik.example) | | SSO | [`integrate-auth-portal.md`](./integrate-auth-portal.md) | | Релизы | [`releasing.md`](./releasing.md) | **CDN + MikrotikManager на одном Traefik:** [`deploy/docker-compose.cdn-mm.yml`](../deploy/docker-compose.cdn-mm.yml) + [`deploy/env.cdn-mm.example`](../deploy/env.cdn-mm.example) — `cdn.shnt.top` и `mm.shnt.top`, каталог `/opt/cdn-mm`. Документация Traefik: [Expose Docker](https://doc.traefik.io/traefik/expose/docker/basic/), [ACME DNS challenge](https://doc.traefik.io/traefik/https/acme/). ``` Internet → :80/:443 (Traefik) → cdnmanager:8080 ↑ Cloudflare DNS TXT (ACME) ``` ## Предпосылки 1. Зона домена в Cloudflare (например `shnt.top`). 2. Docker Engine + Compose plugin на VPS. 3. Свободные порты **80** и **443** на хосте (этот стек сам поднимает Traefik). 4. `docker login git.shx.one`. 5. Для SSO — работающий auth-portal с тем же `JWT_SECRET` / `AUTH_ISSUER`. > Если на сервере уже крутится другой Traefik на 80/443 — либо остановите его, либо смените `TRAEFIK_HTTP_PORT` / `TRAEFIK_HTTPS_PORT`. Два Traefik на одних портах не запустятся. --- ## 1. Cloudflare: токены и DNS ### Два разных токена | Переменная | Куда | Назначение | |------------|------|------------| | `CF_DNS_API_TOKEN` | контейнер **Traefik** | ACME DNS-01 (TXT `_acme-challenge`) | | `CLOUDFLARE_API_TOKEN` | контейнер **app** | Zone DNS Edit + Zone Read для sync флота | Можно один токен с нужными правами продублировать в обе переменные, либо разделить (рекомендуется least privilege). [API Tokens → Create Token](https://dash.cloudflare.com/profile/api-tokens) — шаблон **Edit zone DNS** или Custom: | Permission | Access | |------------|--------| | Zone → DNS | Edit | | Zone → Zone | Read (желательно) | **Zone Resources:** Include → Specific zone → ваша зона. Не Global API Key, не в Git. ### DNS-запись | Type | Name | Content | Proxy | |------|------|---------|-------| | `A` / `AAAA` | `cdn` | IP VPS | **DNS only** (серое облако) | ```bash dig +short cdn.shnt.top A ``` Traefik для сертификата создаёт TXT `_acme-challenge.cdn…` через Cloudflare API. --- ## 2. Файлы на сервере ```bash mkdir -p /opt/cdnmanager/data cd /opt/cdnmanager # из репозитория: # cp deploy/docker-compose.traefik.yml docker-compose.yml # cp deploy/env.traefik.example .env curl -fsSL -o docker-compose.yml \ https://git.shx.one/denozord/CDNManager/raw/branch/main/deploy/docker-compose.traefik.yml curl -fsSL -o .env \ https://git.shx.one/denozord/CDNManager/raw/branch/main/deploy/env.traefik.example nano .env # заполнить секреты ``` ### Обязательные переменные в `.env` | Переменная | Назначение | |------------|------------| | `CF_DNS_API_TOKEN` | Cloudflare token для ACME DNS-01 (Traefik) | | `LETSENCRYPT_EMAIL` | Email для Let's Encrypt | | `CDN_DOMAIN` | Хост в Traefik rule, по умолчанию `cdn.shnt.top` | | `CLOUDFLARE_API_TOKEN` | Token приложения для DNS sync | | `AUTH_JWT_SECRET` / `JWT_SECRET` | = `JWT_SECRET` auth-portal (HS256) | | `AUTH_ISSUER` | `https://auth.shnt.top` | | `AUTH_PORTAL_URL` | URL UI портала | Опционально: `CDN_IMAGE_TAG`, `TRAEFIK_IMAGE_TAG`, `TRAEFIK_HTTP_PORT`, `TRAEFIK_HTTPS_PORT`, `AUTH_AUDIT_INGEST_SECRET`, `AUTH_REQUIRED` (default `true`). На стороне портала добавьте origin в `RETURN_TO_ALLOWLIST` (`https://cdn.shnt.top`) и выдайте app **`cdn`** + права `cdn:*`. App Switcher URL: тот же origin. **Важно:** `AUTH_JWT_SECRET` в CDN Manager **должен совпадать** с `JWT_SECRET` auth-portal, `AUTH_ISSUER` — с `ISSUER` портала. Иначе после SSO UI зацикливается / «Страница не отвечает». --- ## 3. Запуск ```bash cd /opt/cdnmanager docker login git.shx.one docker compose pull docker compose up -d docker compose ps docker compose logs -f --tail=100 ``` Проверка: ```bash curl -fsS https://cdn.shnt.top/health # {"status":"ok",…} ``` Откройте `https://cdn.shnt.top` → редирект на auth-portal → handoff на `/auth/callback`. --- ## 4. Обновление ```bash cd /opt/cdnmanager docker compose pull docker compose up -d ``` Миграции SQLite применяются при старте API. Volume `./data` сохраняется. --- ## 5. Остановка ```bash docker compose down # с удалением ACME volume (сертификаты перевыпустятся): # docker compose down -v ``` Данные SQLite в `./data` на хосте остаются, пока не удалите каталог. --- ## Troubleshooting | Симптом | Причина | |---------|---------| | Порт 80/443 занят | другой Traefik/nginx; смените порты или хост | | ACME timeout | `CF_DNS_API_TOKEN` без DNS Edit; Cloudflare Proxy на A-записи (нужен DNS only) | | SSO loop / 401 | разный `AUTH_JWT_SECRET` / `AUTH_ISSUER` vs portal | | 403 «Нет доступа» | у пользователя нет app `cdn` | | return_to rejected | `https://cdn.shnt.top` не в `RETURN_TO_ALLOWLIST` портала | | Sync CF падает | пустой/`CLOUDFLARE_API_TOKEN` без Zone DNS Edit | Без Traefik (plain `docker run` / простой Compose): [`deploy-docker.md`](./deploy-docker.md).