feat(httpapi): return replica docker install commands on speaker create
quality / commitlint (push) Skipped
quality / changes (push) Successful in 8s
quality / docker-check (push) Skipped
quality / openapi (push) Failing after 21s
quality / web (push) Successful in 55s
quality / go (push) Successful in 1m2s
quality / bird2 (push) Successful in 16s
CD / quality (push) Failing after 2m49s
CD / publish (push) Skipped
quality / commitlint (push) Skipped
quality / changes (push) Successful in 8s
quality / docker-check (push) Skipped
quality / openapi (push) Failing after 21s
quality / web (push) Successful in 55s
quality / go (push) Successful in 1m2s
quality / bird2 (push) Successful in 16s
CD / quality (push) Failing after 2m49s
CD / publish (push) Skipped
После создания реплики 201 отдаёт agent_secret, node_token и install.docker_commands (bird2 + agent + Traefik DNS-01). UI показывает шаг установки вместо закрытия диалога, чтобы секрет больше не терялся. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
+52
-6
@@ -1801,11 +1801,55 @@ components:
|
||||
type: string
|
||||
description: URL agent или https://AGENT_DOMAIN
|
||||
meta_json:
|
||||
oneOf:
|
||||
- type: string
|
||||
- type: object
|
||||
description: >
|
||||
JSON-объект (строка или object). Ключи node_ipv4, bird_bgp_source_ipv4
|
||||
(default = node_ipv4), agent_domain, agent_secret (генерируется при создании если пуст).
|
||||
letsencrypt_email:
|
||||
type: string
|
||||
description: Email ACME для Traefik на ноде. Только для генерации install.docker_commands, не сохраняется.
|
||||
cf_dns_api_token:
|
||||
type: string
|
||||
description: Cloudflare DNS API token (Zone:DNS:Edit) для LE DNS-01. Только для install-сниппета, не сохраняется.
|
||||
panel_ip_whitelist:
|
||||
type: string
|
||||
description: CIDR/IP панели для Traefik ipallowlist. Только для install-сниппета, не сохраняется.
|
||||
control_plane_url:
|
||||
type: string
|
||||
format: uri
|
||||
description: Публичный HTTPS URL панели (EVOBGP_CONTROL_PLANE_URL на реплике). Если пуст — из Origin / X-Forwarded-Host.
|
||||
additionalProperties: true
|
||||
|
||||
SpeakerInstall:
|
||||
type: object
|
||||
description: Одноразовый пакет установки реплики (только POST /v1/speakers 201).
|
||||
properties:
|
||||
docker_commands:
|
||||
type: string
|
||||
description: >
|
||||
JSON-объект. Ключи node_ipv4, bird_bgp_source_ipv4 (default = node_ipv4),
|
||||
agent_domain, agent_secret (генерируется при создании если пуст).
|
||||
additionalProperties: true
|
||||
Bash: sysctl, heredoc docker-compose.yaml (bird2 + agent + Traefik DNS-01) и docker compose up -d.
|
||||
compose_yaml:
|
||||
type: string
|
||||
description: Тело docker-compose.yaml без heredoc (превью).
|
||||
|
||||
BgpSpeakerCreated:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/BgpSpeaker"
|
||||
- type: object
|
||||
properties:
|
||||
agent_secret:
|
||||
type: string
|
||||
description: Bearer для Panel→Node (EVOBGP_AGENT_SECRET). Только в 201.
|
||||
node_token:
|
||||
type: string
|
||||
description: API-ключ role=node (EVOBGP_NODE_TOKEN). Только в 201.
|
||||
bundle_pubkey_base64:
|
||||
type: string
|
||||
description: Ed25519 pubkey для verify-bundle на ноде.
|
||||
install:
|
||||
$ref: "#/components/schemas/SpeakerInstall"
|
||||
|
||||
BgpSpeakerPatch:
|
||||
type: object
|
||||
@@ -3360,7 +3404,9 @@ paths:
|
||||
post:
|
||||
tags: [Speakers]
|
||||
summary: Зарегистрировать спикер
|
||||
description: Реплика, canary и т.д.
|
||||
description: >
|
||||
Реплика или master. Для replica 201 содержит agent_secret, node_token и
|
||||
install.docker_commands (bird2 + agent + Traefik LE DNS-01) — один раз.
|
||||
operationId: createSpeaker
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/TenantId"
|
||||
@@ -3373,11 +3419,11 @@ paths:
|
||||
$ref: "#/components/schemas/BgpSpeakerCreate"
|
||||
responses:
|
||||
"201":
|
||||
description: Ресурс создан.
|
||||
description: Ресурс создан. Для replica — одноразовый install-сниппет.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/BgpSpeaker"
|
||||
$ref: "#/components/schemas/BgpSpeakerCreated"
|
||||
default:
|
||||
$ref: "#/components/responses/DefaultProblem"
|
||||
|
||||
|
||||
+51
-37
@@ -8,32 +8,50 @@ Runbook для реплик **bird2 + evobgp-agent** на отдельных VPS
|
||||
|-----------|--------|
|
||||
| Panel → Node:PORT | CP POST `https://AGENT_DOMAIN/v1/agent/sync` |
|
||||
| SECRET_KEY | `agent_secret` (Bearer) |
|
||||
| Copy compose | Web UI → карточка спикера |
|
||||
| Copy compose | Web UI → после создания реплики: docker-команды (bird2 + agent + Traefik) |
|
||||
| Push Xray JSON | Wake-up → pull signed bundle → verify Ed25519 → apply |
|
||||
|
||||
Подробнее: [architecture.md](architecture.md).
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
1. **CP (microvps-full):** зафиксируйте `EVOBGP_BUNDLE_SEED_HEX` (32 байта hex) — стабильный ключ подписи бандлов.
|
||||
2. **Web UI → Сеть → Спикеры:** создайте спикер `role=replica`, укажите **Agent domain**, **IP ноды**, **BGP source** (по умолчанию = IP ноды).
|
||||
3. Сохраните **`agent_secret`** (показывается один раз) и скопируйте **docker-compose** из UI.
|
||||
4. Выдайте **node API-ключ** ([access.md](access.md)) для `EVOBGP_NODE_TOKEN`.
|
||||
5. `GET /v1/bundle/signing-public-key` → `EVOBGP_BUNDLE_PUBKEY_BASE64` на реплике.
|
||||
6. На VPS реплики:
|
||||
```bash
|
||||
cd deploy/compose
|
||||
cp .env.remote-speaker.example .env.remote-speaker
|
||||
cp .env.remote-speaker-tls.example .env.remote-speaker-tls
|
||||
# заполните переменные из UI
|
||||
docker compose -f docker-compose.remote-speaker.yaml \
|
||||
--env-file .env.remote-speaker --env-file .env.remote-speaker-tls \
|
||||
--profile production up -d
|
||||
```
|
||||
7. **CP:** `EVOBGP_NODE_DISPATCH_ENABLED=1` — Panel шлёт wake-up после publish.
|
||||
8. Cloudflare: `AGENT_DOMAIN` → IP VPS, **DNS only** (как Web UI в [quickstart.md](quickstart.md)).
|
||||
1. **CP (microvps-full):** зафиксируйте `EVOBGP_BUNDLE_SEED_HEX` (32 байта hex) — стабильный ключ подписи бандлов. `EVOBGP_NODE_DISPATCH_ENABLED=1`.
|
||||
2. Cloudflare: A/AAAA `AGENT_DOMAIN` → публичный IP VPS реплики, режим **DNS only** (серый облачко), как Web UI в [quickstart.md](quickstart.md).
|
||||
3. **Web UI → Сеть → Спикеры:** создайте спикер `role=replica`. Укажите **домен агента**, **IP ноды**, **BGP source** (по умолчанию = IP ноды), **email Let's Encrypt**, **Cloudflare DNS API token** (`Zone:DNS:Edit`), **IP панели** (CIDR whitelist).
|
||||
4. В диалоге «Установка на ноду» скопируйте **docker-команды** (секреты `agent_secret` и `node_token` показываются **один раз**). Репозиторий EvoBGP на ноде не нужен: команда пишет `/opt/evobgp-speaker/docker-compose.yaml` (bird2 + agent + Traefik DNS-01) и делает `docker compose up -d`.
|
||||
5. Если образы из приватного реестра — на VPS заранее `docker login git.shx.one`.
|
||||
6. Не делайте `docker compose down -v` на реплике без бэкапа тома `evobgp_speaker_traefik_letsencrypt` (`acme.json`).
|
||||
|
||||
## Compose-профили
|
||||
Эталонный compose в репозитории (lab / ручной запуск): [docker-compose.remote-speaker.yaml](../deploy/compose/docker-compose.remote-speaker.yaml). Prod-установка с панели — paste из UI.
|
||||
|
||||
## HTTPS на ноде (DNS-01)
|
||||
|
||||
Сертификат **не** выписывает Control Plane и **не** Cloudflare Origin CA. Его выпускает **Traefik на самой реплике** (`evobgp-edge`), resolver `letsencrypt`, **ACME DNS-01** через Cloudflare.
|
||||
|
||||
| Кто | Что делает |
|
||||
|-----|------------|
|
||||
| Оператор | DNS only: `AGENT_DOMAIN` → IP VPS |
|
||||
| Traefik на **ноде** | `dnschallenge=true`, `provider=cloudflare` |
|
||||
| `CF_DNS_API_TOKEN` | В env **реплики** (вшит в команду из UI). Traefik создаёт TXT `_acme-challenge.<AGENT_DOMAIN>` |
|
||||
| Let's Encrypt | Проверяет TXT, отдаёт сертификат |
|
||||
| Том | `evobgp_speaker_traefik_letsencrypt` → `/letsencrypt/acme.json` |
|
||||
| CP → нода | `https://AGENT_DOMAIN/v1/agent/*` + `Authorization: Bearer <agent_secret>` + Traefik `ipallowlist` (`PANEL_IP_WHITELIST`) |
|
||||
|
||||
Порты:
|
||||
|
||||
| Порт | Кто | Зачем |
|
||||
|------|-----|-------|
|
||||
| **443** | IP CP (`PANEL_IP_WHITELIST`) | HTTPS dispatch, health, `GET /v1/agent/bird/protocols` |
|
||||
| **179** | BGP peers | Data plane |
|
||||
| **80** | любой | редирект HTTP → HTTPS (не HTTP-01 ACME) |
|
||||
|
||||
DNS-01 ходит **исходящим** к Cloudflare API и Let's Encrypt; inbound 80 для выпуска сертификата не нужен. Agent слушает `:8443` только во внутренней docker-сети; снаружи — Traefik 443.
|
||||
|
||||
Токен Cloudflare для панели (`evobgp-edge` на CP) в процесс API **не проброшен** — для реплики его задают в форме создания.
|
||||
|
||||
Profile `plain` в файле репозитория — только lab без Traefik.
|
||||
|
||||
## Compose-профили (файл в репозитории)
|
||||
|
||||
| Profile | Состав |
|
||||
|---------|--------|
|
||||
@@ -41,19 +59,11 @@ Runbook для реплик **bird2 + evobgp-agent** на отдельных VPS
|
||||
| `plain` | bird2 + agent на хосте без Traefik (только lab) |
|
||||
| `fallback` | + `sync-bundle` polling (`scripts/sync-bundle.sh`) |
|
||||
|
||||
Файлы: [docker-compose.remote-speaker.yaml](../deploy/compose/docker-compose.remote-speaker.yaml).
|
||||
Команда из UI — самодостаточный yaml **без profiles** (эквивалент production).
|
||||
|
||||
## Firewall
|
||||
## Подготовка VPS
|
||||
|
||||
| Порт | Кто | Зачем |
|
||||
|------|-----|-------|
|
||||
| **443** | IP CP (`PANEL_IP_WHITELIST`) | HTTPS dispatch, health, **`GET /v1/agent/bird/protocols`** (live peer sessions) |
|
||||
| **179** | BGP peers | Data plane |
|
||||
| **80** | ACME | Traefik → 443 |
|
||||
|
||||
## Подготовка VPS (перед `docker compose up`)
|
||||
|
||||
`bird2` — **`network_mode: host`**. Docker **не может** задать `net.ipv4.ip_forward` в таком контейнере; включите на **хосте**:
|
||||
`bird2` — **`network_mode: host`**. Docker **не может** задать `net.ipv4.ip_forward` в таком контейнере. Команда из UI включает sysctl; для постоянства:
|
||||
|
||||
```bash
|
||||
sysctl -w net.ipv4.ip_forward=1
|
||||
@@ -66,17 +76,18 @@ sysctl --system
|
||||
## Безопасность (три участка)
|
||||
|
||||
1. **CP → реплика:** HTTPS (LE) + Traefik ipallowlist + `agent_secret`.
|
||||
2. **Реплика → CP:** HTTPS + роль `node` (только bundle/latest/enroll).
|
||||
2. **Реплика → CP:** HTTPS + роль `node` (только bundle/latest/enroll). Ключ создаётся вместе со спикером.
|
||||
3. **Конфиг:** Ed25519 `bundle.sig`, SHA-256 manifest, `bird -p`, LKG на ноде.
|
||||
|
||||
Prod checklist:
|
||||
|
||||
- [ ] `EVOBGP_CONTROL_PLANE_URL=https://...`
|
||||
- [ ] `EVOBGP_CONTROL_PLANE_URL=https://...` (в команде из UI)
|
||||
- [ ] `EVOBGP_NODE_DISPATCH_ENABLED=1` на CP
|
||||
- [ ] `EVOBGP_BUNDLE_SEED_HEX` на CP (не менять после выдачи pubkey репликам)
|
||||
- [ ] Уникальные `agent_secret` и node token на спикер
|
||||
- [ ] Не использовать profile `plain` в prod
|
||||
- [ ] Не отключать verify-bundle в agent
|
||||
- [ ] Не `docker compose down -v` без бэкапа `acme.json`
|
||||
|
||||
## Per-speaker BGP source
|
||||
|
||||
@@ -95,7 +106,8 @@ Tenant `/v1/settings` (`bird_bgp_source_ipv4`) — fallback для master / ес
|
||||
| Симптом | Проверка |
|
||||
|---------|----------|
|
||||
| `sysctl net.ipv4.ip_forward not allowed in host network` | Уберите sysctls из compose (уже так в main); включите ip_forward на VPS (см. выше) |
|
||||
| `no service selected` | `--profile production` или `COMPOSE_PROFILES=production` |
|
||||
| `CHANGE_ME_*` в yaml | В форме не заполнены email LE / CF token / IP панели / домен |
|
||||
| Traefik отдаёт дефолтный сертификат | DNS only; token `Zone:DNS:Edit`; логи `evobgp-edge`; том acme.json |
|
||||
| Offline в UI | `GET https://AGENT_DOMAIN/v1/agent/health` с CP; LE cert; whitelist |
|
||||
| dispatch error | CP logs job meta; firewall 443; `agent_secret` |
|
||||
| verify-bundle fail | pubkey совпадает с CP seed; пересоберите pubkey после смены seed |
|
||||
@@ -105,16 +117,18 @@ Tenant `/v1/settings` (`bird_bgp_source_ipv4`) — fallback для master / ес
|
||||
|
||||
- Peers **не** фильтруются по `speaker_id` — один tenant-wide peers fragment на все реплики.
|
||||
- Разные peer-наборы per site — отдельная итерация pipeline.
|
||||
- Если Panel не достучится до agent — включите profile `fallback` (polling).
|
||||
- Если Panel не достучится до agent — включите profile `fallback` (polling) в файле репозитория.
|
||||
|
||||
## Связанные env
|
||||
|
||||
| Переменная | Где |
|
||||
|------------|-----|
|
||||
| `EVOBGP_NODE_DISPATCH_ENABLED=1` | CP |
|
||||
| `EVOBGP_AGENT_SECRET` | реплика |
|
||||
| `EVOBGP_NODE_TOKEN` | реплика |
|
||||
| `EVOBGP_AGENT_SECRET` | реплика (из UI, один раз) |
|
||||
| `EVOBGP_NODE_TOKEN` | реплика (API-ключ role=node, из UI) |
|
||||
| `EVOBGP_FIREWALL_FAILOVER_ENABLED=1` | реплика (опционально: отдавать `/v1/firewall/blocklist` при недоступности CP) |
|
||||
| `EVOBGP_FIREWALL_STATE_FILE` | реплика (default `/var/lib/evobgp-agent/firewall-state.json`) |
|
||||
| `EVOBGP_BUNDLE_PUBKEY_BASE64` | реплика |
|
||||
| `EVOBGP_BUNDLE_PUBKEY_BASE64` | реплика (в команде из UI) |
|
||||
| `PANEL_IP_WHITELIST` | Traefik на реплике |
|
||||
| `CF_DNS_API_TOKEN` | Traefik на реплике |
|
||||
| `LETSENCRYPT_EMAIL` | Traefik на реплике |
|
||||
|
||||
Reference in New Issue
Block a user