# Развёртывание Auth Portal + Traefik (один Docker Compose) Один стек: **Traefik** (HTTPS, Let's Encrypt DNS-01 через Cloudflare) + **auth-portal** (API + SPA, SQLite). Образ приложения: `git.shx.one/denozord/auth-portal` (тот же манифест публикуется как `authportal`) Runtime: `node:22-alpine`, API + SPA + SQLite в одном процессе. Пайплайн: [`.gitea/README.md`](../.gitea/README.md), [`docs/releasing.md`](releasing.md). Публичный URL: `https://auth.shnt.top` Compose: [`deploy/docker-compose.traefik.yml`](../deploy/docker-compose.traefik.yml) Env-шаблон: [`deploy/env.traefik.example`](../deploy/env.traefik.example) Документация 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) → auth-portal: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`. > Если на сервере уже крутится другой Traefik на 80/443 — либо остановите его, либо смените `TRAEFIK_HTTP_PORT` / `TRAEFIK_HTTPS_PORT` (и проброс снаружи). Два Traefik на одних портах не запустятся. --- ## 1. Cloudflare: токен и DNS ### API-токен [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 → ваша зона. Токен → `CF_DNS_API_TOKEN` в `.env` стека. Не Global API Key, не в Git. ### DNS-запись | Type | Name | Content | Proxy | |------|------|---------|-------| | `A` / `AAAA` | `auth` | IP VPS | **DNS only** (серое облако) | ```bash dig +short auth.shnt.top A ``` Traefik для сертификата создаёт TXT `_acme-challenge.auth…` через Cloudflare API (HTTP-01 на :80 не обязателен). --- ## 2. Файлы на сервере ```bash mkdir -p /opt/auth-portal/data cd /opt/auth-portal # из репозитория или curl: # deploy/docker-compose.traefik.yml → docker-compose.yml # deploy/env.traefik.example → .env curl -fsSL -o docker-compose.yml \ https://git.shx.one/denozord/auth-portal/raw/branch/main/deploy/docker-compose.traefik.yml curl -fsSL -o .env \ https://git.shx.one/denozord/auth-portal/raw/branch/main/deploy/env.traefik.example nano .env # заполнить секреты ``` ### Обязательные переменные в `.env` | Переменная | Назначение | |------------|------------| | `CF_DNS_API_TOKEN` | Cloudflare token для ACME DNS-01 (env контейнера **Traefik**) | | `LETSENCRYPT_EMAIL` | Email для Let's Encrypt | | `JWT_SECRET` | HS256; тот же секрет в VPS Tracker / CFDM (`AUTH_JWT_SECRET`) | | `ADMIN_PASSWORD` | Пароль bootstrap-админа (только при пустой БД) | | `AUTH_DOMAIN` | Хост в Traefik rule, по умолчанию `auth.shnt.top` | | `ISSUER` | `https://auth.shnt.top` — совпадает с `AUTH_ISSUER` приложений **и** OIDC issuer | | `RETURN_TO_ALLOWLIST` | Origins SSO / App Switcher (`.shnt.top`, `https://dns.shnt.top`, …) | | `OIDC_ISSUER` | Опционально; если пусто — берётся `ISSUER`. Публичный URL IdP для Technitium | | `OIDC_RSA_PRIVATE_KEY` | Опционально PKCS8 PEM; иначе RSA-ключ в SQLite (`./data`) | Опционально: `AUTH_IMAGE_TAG`, `TRAEFIK_IMAGE_TAG`, `TRAEFIK_HTTP_PORT`, `TRAEFIK_HTTPS_PORT`. В production cookie refresh с флагом `Secure` — нужен HTTPS. ### Проверка OIDC после старта ```bash curl -fsS https://auth.shnt.top/.well-known/openid-configuration | head curl -fsS https://auth.shnt.top/.well-known/jwks.json | head ``` Админка: `https://auth.shnt.top/admin/oidc` — создать client для Technitium. Полная инструкция SSO: [integrate-technitium.md](integrate-technitium.md). --- ## 3. Запуск (один compose) ```bash cd /opt/auth-portal docker login git.shx.one docker compose pull docker compose up -d docker compose ps docker compose logs -f --tail=100 ``` Сервисы: | Service | Контейнер | Роль | |---------|-----------|------| | `traefik` | `auth-portal-traefik` | :80 → HTTPS, ACME, роутинг | | `app` | `auth-portal` | приложение на внутренней сети `auth-portal`, порт 8080 | Первая выдача сертификата обычно 30–90 с. ### Проверка ```bash curl -fsS https://auth.shnt.top/health echo | openssl s_client -connect auth.shnt.top:443 -servername auth.shnt.top 2>/dev/null \ | openssl x509 -noout -issuer -dates -subject docker compose logs traefik 2>&1 | grep -iE 'acme|certificate|cloudflare|error' ``` ### Обновление / остановка ```bash cd /opt/auth-portal docker compose pull docker compose up -d # остановка (тома и ./data сохраняются) docker compose down # НЕ делайте down -v без бэкапа — сотрёт ACME (auth_portal_traefik_letsencrypt) ``` --- ## Что внутри compose - Сеть Docker **`auth-portal`** (внутренняя, создаётся стеком). - Том **`auth_portal_traefik_letsencrypt`** → `/letsencrypt/acme.json`. - Том хоста **`./data`** → SQLite портала. - Labels на `app`: `Host(AUTH_DOMAIN)`, `entrypoints=websecure`, `certresolver=letsencrypt`, backend port `8080`. - `CF_DNS_API_TOKEN` только у сервиса `traefik`. Полный файл: [`deploy/docker-compose.traefik.yml`](../deploy/docker-compose.traefik.yml). --- ## Связка с приложениями ### JWT apps (CFDM / VPS / BGP / FW) ```env AUTH_REQUIRED=true AUTH_JWT_SECRET=<тот же JWT_SECRET> AUTH_ISSUER=https://auth.shnt.top AUTH_PORTAL_URL=https://auth.shnt.top ``` UI: ```env VITE_AUTH_ENABLED=true VITE_AUTH_PORTAL_URL=https://auth.shnt.top ``` См. [integrate-vps-tracker.md](integrate-vps-tracker.md), [integrate-cfdm.md](integrate-cfdm.md), [integrate-evobgp.md](integrate-evobgp.md), [integrate-evofirewall.md](integrate-evofirewall.md), [integrate-cdnmanager.md](integrate-cdnmanager.md), [integrate-mikrotikmanager.md](integrate-mikrotikmanager.md). Logout SSO: `https://auth.shnt.top/logout`. ### Technitium DNS (OIDC) Portal — IdP; Technitium — RP. После `docker compose up`: 1. В `.env` добавьте origin DNS в `RETURN_TO_ALLOWLIST` (например `https://dns.shnt.top`). 2. `curl https://auth.shnt.top/.well-known/openid-configuration` — должен отвечать JSON. 3. Admin → OIDC-клиенты → redirect `https://dns.shnt.top/sso/callback`. 4. В Technitium SSO: Metadata = `https://auth.shnt.top/.well-known/openid-configuration`. Важно: Technitium резолвит IdP **своим** DNS — A/AAAA для `auth.shnt.top` должна быть видна с DNS-сервера. Детали: [integrate-technitium.md](integrate-technitium.md). --- ## Бэкап ```bash # SQLite cp /opt/auth-portal/data/app.db /opt/auth-portal/data/app.db.bak-$(date +%F) # ACME docker run --rm -v auth_portal_traefik_letsencrypt:/data -v "$PWD:/backup" alpine \ tar czf /backup/traefik-acme-$(date +%F).tgz -C /data . ``` Откат образа: в `.env` `AUTH_IMAGE_TAG=vX.Y.Z` → `docker compose pull && docker compose up -d`. --- ## Troubleshooting | Симптом | Что проверить | |---------|----------------| | `Bind for 0.0.0.0:80/443 failed` | Другой Traefik/nginx занимает порты | | ACME / нет HTTPS | `CF_DNS_API_TOKEN`, права Zone:DNS:Edit, логи `docker compose logs traefik` | | `invalid credentials` | Не Global Key; зона в scope токена; пробелы в `.env` | | Gateway Timeout / 404 | `docker compose ps`; labels; сеть `auth-portal` | | Login OK, SSO в app падает | `JWT_SECRET` / `ISSUER` | | `return_to` rejected | `RETURN_TO_ALLOWLIST` | | Cookie не держится | HTTPS; `NODE_ENV=production` | | Technitium «Failed to reach SSO provider» | A/AAAA `auth.*` в самом Technitium; `curl` с хоста DNS к discovery URL | | OIDC discovery 404 | образ без OIDC; `ISSUER`/`OIDC_ISSUER` = публичный HTTPS URL; Traefik Host | ```bash docker compose logs traefik 2>&1 | grep -iE 'acme|certificate|cloudflare|error' docker compose logs app --tail=50 ``` --- ## Альтернатива: Docker CLI (без compose) Если нужен ручной запуск — создайте сеть и два контейнера с теми же env/labels, что в compose. Предпочтительный путь — **один `docker compose up -d`** выше.